mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat: store and display template creator (#2228)
* design commit * add owner_id to templates table * add owner information in apis and ui * update minWidth for statItem * rename owner to created_by * missing refactor to created_by * handle errors in fetching created_by names
This commit is contained in:
@ -88,6 +88,7 @@ func TestDiff(t *testing.T) {
|
|||||||
ActiveVersionID: uuid.UUID{3},
|
ActiveVersionID: uuid.UUID{3},
|
||||||
MaxTtl: int64(time.Hour),
|
MaxTtl: int64(time.Hour),
|
||||||
MinAutostartInterval: int64(time.Minute),
|
MinAutostartInterval: int64(time.Minute),
|
||||||
|
CreatedBy: uuid.NullUUID{UUID: uuid.UUID{4}, Valid: true},
|
||||||
},
|
},
|
||||||
exp: audit.Map{
|
exp: audit.Map{
|
||||||
"id": uuid.UUID{1}.String(),
|
"id": uuid.UUID{1}.String(),
|
||||||
@ -97,6 +98,7 @@ func TestDiff(t *testing.T) {
|
|||||||
"active_version_id": uuid.UUID{3}.String(),
|
"active_version_id": uuid.UUID{3}.String(),
|
||||||
"max_ttl": int64(3600000000000),
|
"max_ttl": int64(3600000000000),
|
||||||
"min_autostart_interval": int64(60000000000),
|
"min_autostart_interval": int64(60000000000),
|
||||||
|
"created_by": uuid.UUID{4}.String(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -72,6 +72,7 @@ var AuditableResources = auditMap(map[any]map[string]Action{
|
|||||||
"description": ActionTrack,
|
"description": ActionTrack,
|
||||||
"max_ttl": ActionTrack,
|
"max_ttl": ActionTrack,
|
||||||
"min_autostart_interval": ActionTrack,
|
"min_autostart_interval": ActionTrack,
|
||||||
|
"created_by": ActionTrack,
|
||||||
},
|
},
|
||||||
&database.TemplateVersion{}: {
|
&database.TemplateVersion{}: {
|
||||||
"id": ActionTrack,
|
"id": ActionTrack,
|
||||||
|
@ -1341,6 +1341,7 @@ func (q *fakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl
|
|||||||
Description: arg.Description,
|
Description: arg.Description,
|
||||||
MaxTtl: arg.MaxTtl,
|
MaxTtl: arg.MaxTtl,
|
||||||
MinAutostartInterval: arg.MinAutostartInterval,
|
MinAutostartInterval: arg.MinAutostartInterval,
|
||||||
|
CreatedBy: arg.CreatedBy,
|
||||||
}
|
}
|
||||||
q.templates = append(q.templates, template)
|
q.templates = append(q.templates, template)
|
||||||
return template, nil
|
return template, nil
|
||||||
|
6
coderd/database/dump.sql
generated
6
coderd/database/dump.sql
generated
@ -248,7 +248,8 @@ CREATE TABLE templates (
|
|||||||
active_version_id uuid NOT NULL,
|
active_version_id uuid NOT NULL,
|
||||||
description character varying(128) DEFAULT ''::character varying NOT NULL,
|
description character varying(128) DEFAULT ''::character varying NOT NULL,
|
||||||
max_ttl bigint DEFAULT '604800000000000'::bigint NOT NULL,
|
max_ttl bigint DEFAULT '604800000000000'::bigint NOT NULL,
|
||||||
min_autostart_interval bigint DEFAULT '3600000000000'::bigint NOT NULL
|
min_autostart_interval bigint DEFAULT '3600000000000'::bigint NOT NULL,
|
||||||
|
created_by uuid
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
@ -476,6 +477,9 @@ ALTER TABLE ONLY template_versions
|
|||||||
ALTER TABLE ONLY template_versions
|
ALTER TABLE ONLY template_versions
|
||||||
ADD CONSTRAINT template_versions_template_id_fkey FOREIGN KEY (template_id) REFERENCES templates(id) ON DELETE CASCADE;
|
ADD CONSTRAINT template_versions_template_id_fkey FOREIGN KEY (template_id) REFERENCES templates(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE ONLY templates
|
||||||
|
ADD CONSTRAINT templates_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE RESTRICT;
|
||||||
|
|
||||||
ALTER TABLE ONLY templates
|
ALTER TABLE ONLY templates
|
||||||
ADD CONSTRAINT templates_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
ADD CONSTRAINT templates_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE ONLY templates DROP COLUMN IF EXISTS created_by;
|
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE ONLY templates ADD COLUMN IF NOT EXISTS created_by uuid REFERENCES users (id) ON DELETE RESTRICT;
|
@ -438,6 +438,7 @@ type Template struct {
|
|||||||
Description string `db:"description" json:"description"`
|
Description string `db:"description" json:"description"`
|
||||||
MaxTtl int64 `db:"max_ttl" json:"max_ttl"`
|
MaxTtl int64 `db:"max_ttl" json:"max_ttl"`
|
||||||
MinAutostartInterval int64 `db:"min_autostart_interval" json:"min_autostart_interval"`
|
MinAutostartInterval int64 `db:"min_autostart_interval" json:"min_autostart_interval"`
|
||||||
|
CreatedBy uuid.NullUUID `db:"created_by" json:"created_by"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TemplateVersion struct {
|
type TemplateVersion struct {
|
||||||
|
@ -1603,7 +1603,7 @@ func (q *sqlQuerier) UpdateProvisionerJobWithCompleteByID(ctx context.Context, a
|
|||||||
|
|
||||||
const getTemplateByID = `-- name: GetTemplateByID :one
|
const getTemplateByID = `-- name: GetTemplateByID :one
|
||||||
SELECT
|
SELECT
|
||||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval
|
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by
|
||||||
FROM
|
FROM
|
||||||
templates
|
templates
|
||||||
WHERE
|
WHERE
|
||||||
@ -1627,13 +1627,14 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
|
|||||||
&i.Description,
|
&i.Description,
|
||||||
&i.MaxTtl,
|
&i.MaxTtl,
|
||||||
&i.MinAutostartInterval,
|
&i.MinAutostartInterval,
|
||||||
|
&i.CreatedBy,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one
|
const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one
|
||||||
SELECT
|
SELECT
|
||||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval
|
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by
|
||||||
FROM
|
FROM
|
||||||
templates
|
templates
|
||||||
WHERE
|
WHERE
|
||||||
@ -1665,13 +1666,14 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
|
|||||||
&i.Description,
|
&i.Description,
|
||||||
&i.MaxTtl,
|
&i.MaxTtl,
|
||||||
&i.MinAutostartInterval,
|
&i.MinAutostartInterval,
|
||||||
|
&i.CreatedBy,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTemplatesByIDs = `-- name: GetTemplatesByIDs :many
|
const getTemplatesByIDs = `-- name: GetTemplatesByIDs :many
|
||||||
SELECT
|
SELECT
|
||||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval
|
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by
|
||||||
FROM
|
FROM
|
||||||
templates
|
templates
|
||||||
WHERE
|
WHERE
|
||||||
@ -1699,6 +1701,7 @@ func (q *sqlQuerier) GetTemplatesByIDs(ctx context.Context, ids []uuid.UUID) ([]
|
|||||||
&i.Description,
|
&i.Description,
|
||||||
&i.MaxTtl,
|
&i.MaxTtl,
|
||||||
&i.MinAutostartInterval,
|
&i.MinAutostartInterval,
|
||||||
|
&i.CreatedBy,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1715,7 +1718,7 @@ func (q *sqlQuerier) GetTemplatesByIDs(ctx context.Context, ids []uuid.UUID) ([]
|
|||||||
|
|
||||||
const getTemplatesByOrganization = `-- name: GetTemplatesByOrganization :many
|
const getTemplatesByOrganization = `-- name: GetTemplatesByOrganization :many
|
||||||
SELECT
|
SELECT
|
||||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval
|
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by
|
||||||
FROM
|
FROM
|
||||||
templates
|
templates
|
||||||
WHERE
|
WHERE
|
||||||
@ -1749,6 +1752,7 @@ func (q *sqlQuerier) GetTemplatesByOrganization(ctx context.Context, arg GetTemp
|
|||||||
&i.Description,
|
&i.Description,
|
||||||
&i.MaxTtl,
|
&i.MaxTtl,
|
||||||
&i.MinAutostartInterval,
|
&i.MinAutostartInterval,
|
||||||
|
&i.CreatedBy,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1775,10 +1779,11 @@ INSERT INTO
|
|||||||
active_version_id,
|
active_version_id,
|
||||||
description,
|
description,
|
||||||
max_ttl,
|
max_ttl,
|
||||||
min_autostart_interval
|
min_autostart_interval,
|
||||||
|
created_by
|
||||||
)
|
)
|
||||||
VALUES
|
VALUES
|
||||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval
|
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by
|
||||||
`
|
`
|
||||||
|
|
||||||
type InsertTemplateParams struct {
|
type InsertTemplateParams struct {
|
||||||
@ -1792,6 +1797,7 @@ type InsertTemplateParams struct {
|
|||||||
Description string `db:"description" json:"description"`
|
Description string `db:"description" json:"description"`
|
||||||
MaxTtl int64 `db:"max_ttl" json:"max_ttl"`
|
MaxTtl int64 `db:"max_ttl" json:"max_ttl"`
|
||||||
MinAutostartInterval int64 `db:"min_autostart_interval" json:"min_autostart_interval"`
|
MinAutostartInterval int64 `db:"min_autostart_interval" json:"min_autostart_interval"`
|
||||||
|
CreatedBy uuid.NullUUID `db:"created_by" json:"created_by"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParams) (Template, error) {
|
func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParams) (Template, error) {
|
||||||
@ -1806,6 +1812,7 @@ func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParam
|
|||||||
arg.Description,
|
arg.Description,
|
||||||
arg.MaxTtl,
|
arg.MaxTtl,
|
||||||
arg.MinAutostartInterval,
|
arg.MinAutostartInterval,
|
||||||
|
arg.CreatedBy,
|
||||||
)
|
)
|
||||||
var i Template
|
var i Template
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
@ -1820,6 +1827,7 @@ func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParam
|
|||||||
&i.Description,
|
&i.Description,
|
||||||
&i.MaxTtl,
|
&i.MaxTtl,
|
||||||
&i.MinAutostartInterval,
|
&i.MinAutostartInterval,
|
||||||
|
&i.CreatedBy,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@ -1873,7 +1881,7 @@ SET
|
|||||||
WHERE
|
WHERE
|
||||||
id = $1
|
id = $1
|
||||||
RETURNING
|
RETURNING
|
||||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval
|
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateTemplateMetaByIDParams struct {
|
type UpdateTemplateMetaByIDParams struct {
|
||||||
|
@ -49,10 +49,11 @@ INSERT INTO
|
|||||||
active_version_id,
|
active_version_id,
|
||||||
description,
|
description,
|
||||||
max_ttl,
|
max_ttl,
|
||||||
min_autostart_interval
|
min_autostart_interval,
|
||||||
|
created_by
|
||||||
)
|
)
|
||||||
VALUES
|
VALUES
|
||||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *;
|
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING *;
|
||||||
|
|
||||||
-- name: UpdateTemplateActiveVersionByID :exec
|
-- name: UpdateTemplateActiveVersionByID :exec
|
||||||
UPDATE
|
UPDATE
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package coderd
|
package coderd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -49,7 +50,16 @@ func (api *API) template(rw http.ResponseWriter, r *http.Request) {
|
|||||||
count = uint32(workspaceCounts[0].Count)
|
count = uint32(workspaceCounts[0].Count)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpapi.Write(rw, http.StatusOK, convertTemplate(template, count))
|
createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), api.Database, []database.Template{template})
|
||||||
|
if err != nil {
|
||||||
|
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||||
|
Message: "Internal error fetching creator name.",
|
||||||
|
Detail: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpapi.Write(rw, http.StatusOK, convertTemplate(template, count, createdByNameMap[template.ID.String()]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
|
func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
|
||||||
@ -97,6 +107,7 @@ func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
|
|||||||
func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Request) {
|
func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Request) {
|
||||||
var createTemplate codersdk.CreateTemplateRequest
|
var createTemplate codersdk.CreateTemplateRequest
|
||||||
organization := httpmw.OrganizationParam(r)
|
organization := httpmw.OrganizationParam(r)
|
||||||
|
apiKey := httpmw.APIKey(r)
|
||||||
if !api.Authorize(rw, r, rbac.ActionCreate, rbac.ResourceTemplate.InOrg(organization.ID)) {
|
if !api.Authorize(rw, r, rbac.ActionCreate, rbac.ResourceTemplate.InOrg(organization.ID)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -175,6 +186,10 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
|||||||
Description: createTemplate.Description,
|
Description: createTemplate.Description,
|
||||||
MaxTtl: int64(maxTTL),
|
MaxTtl: int64(maxTTL),
|
||||||
MinAutostartInterval: int64(minAutostartInterval),
|
MinAutostartInterval: int64(minAutostartInterval),
|
||||||
|
CreatedBy: uuid.NullUUID{
|
||||||
|
UUID: apiKey.UserID,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("insert template: %s", err)
|
return xerrors.Errorf("insert template: %s", err)
|
||||||
@ -208,7 +223,12 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template = convertTemplate(dbTemplate, 0)
|
createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), db, []database.Template{dbTemplate})
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("get creator name: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
template = convertTemplate(dbTemplate, 0, createdByNameMap[dbTemplate.ID.String()])
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -258,7 +278,16 @@ func (api *API) templatesByOrganization(rw http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
httpapi.Write(rw, http.StatusOK, convertTemplates(templates, workspaceCounts))
|
createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), api.Database, templates)
|
||||||
|
if err != nil {
|
||||||
|
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||||
|
Message: "Internal error fetching creator names.",
|
||||||
|
Detail: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpapi.Write(rw, http.StatusOK, convertTemplates(templates, workspaceCounts, createdByNameMap))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Request) {
|
func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Request) {
|
||||||
@ -304,7 +333,16 @@ func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Re
|
|||||||
count = uint32(workspaceCounts[0].Count)
|
count = uint32(workspaceCounts[0].Count)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpapi.Write(rw, http.StatusOK, convertTemplate(template, count))
|
createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), api.Database, []database.Template{template})
|
||||||
|
if err != nil {
|
||||||
|
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||||
|
Message: "Internal error fetching creator name.",
|
||||||
|
Detail: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpapi.Write(rw, http.StatusOK, convertTemplate(template, count, createdByNameMap[template.ID.String()]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||||
@ -400,10 +438,35 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
httpapi.Write(rw, http.StatusOK, convertTemplate(updated, count))
|
createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), api.Database, []database.Template{updated})
|
||||||
|
if err != nil {
|
||||||
|
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||||
|
Message: "Internal error fetching creator name.",
|
||||||
|
Detail: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpapi.Write(rw, http.StatusOK, convertTemplate(updated, count, createdByNameMap[updated.ID.String()]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertTemplates(templates []database.Template, workspaceCounts []database.GetWorkspaceOwnerCountsByTemplateIDsRow) []codersdk.Template {
|
func getCreatedByNamesByTemplateIDs(ctx context.Context, db database.Store, templates []database.Template) (map[string]string, error) {
|
||||||
|
creators := make(map[string]string, len(templates))
|
||||||
|
for _, template := range templates {
|
||||||
|
if template.CreatedBy.Valid {
|
||||||
|
creator, err := db.GetUserByID(ctx, template.CreatedBy.UUID)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]string{}, err
|
||||||
|
}
|
||||||
|
creators[template.ID.String()] = creator.Username
|
||||||
|
} else {
|
||||||
|
creators[template.ID.String()] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return creators, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertTemplates(templates []database.Template, workspaceCounts []database.GetWorkspaceOwnerCountsByTemplateIDsRow, createdByNameMap map[string]string) []codersdk.Template {
|
||||||
apiTemplates := make([]codersdk.Template, 0, len(templates))
|
apiTemplates := make([]codersdk.Template, 0, len(templates))
|
||||||
for _, template := range templates {
|
for _, template := range templates {
|
||||||
found := false
|
found := false
|
||||||
@ -411,18 +474,18 @@ func convertTemplates(templates []database.Template, workspaceCounts []database.
|
|||||||
if workspaceCount.TemplateID.String() != template.ID.String() {
|
if workspaceCount.TemplateID.String() != template.ID.String() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
apiTemplates = append(apiTemplates, convertTemplate(template, uint32(workspaceCount.Count)))
|
apiTemplates = append(apiTemplates, convertTemplate(template, uint32(workspaceCount.Count), createdByNameMap[template.ID.String()]))
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
apiTemplates = append(apiTemplates, convertTemplate(template, uint32(0)))
|
apiTemplates = append(apiTemplates, convertTemplate(template, uint32(0), createdByNameMap[template.ID.String()]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return apiTemplates
|
return apiTemplates
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertTemplate(template database.Template, workspaceOwnerCount uint32) codersdk.Template {
|
func convertTemplate(template database.Template, workspaceOwnerCount uint32, createdByName string) codersdk.Template {
|
||||||
return codersdk.Template{
|
return codersdk.Template{
|
||||||
ID: template.ID,
|
ID: template.ID,
|
||||||
CreatedAt: template.CreatedAt,
|
CreatedAt: template.CreatedAt,
|
||||||
@ -435,5 +498,7 @@ func convertTemplate(template database.Template, workspaceOwnerCount uint32) cod
|
|||||||
Description: template.Description,
|
Description: template.Description,
|
||||||
MaxTTLMillis: time.Duration(template.MaxTtl).Milliseconds(),
|
MaxTTLMillis: time.Duration(template.MaxTtl).Milliseconds(),
|
||||||
MinAutostartIntervalMillis: time.Duration(template.MinAutostartInterval).Milliseconds(),
|
MinAutostartIntervalMillis: time.Duration(template.MinAutostartInterval).Milliseconds(),
|
||||||
|
CreatedByID: template.CreatedBy,
|
||||||
|
CreatedByName: createdByName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ type Template struct {
|
|||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
MaxTTLMillis int64 `json:"max_ttl_ms"`
|
MaxTTLMillis int64 `json:"max_ttl_ms"`
|
||||||
MinAutostartIntervalMillis int64 `json:"min_autostart_interval_ms"`
|
MinAutostartIntervalMillis int64 `json:"min_autostart_interval_ms"`
|
||||||
|
CreatedByID uuid.NullUUID `json:"created_by_id"`
|
||||||
|
CreatedByName string `json:"created_by_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateActiveTemplateVersion struct {
|
type UpdateActiveTemplateVersion struct {
|
||||||
|
@ -247,6 +247,8 @@ export interface Template {
|
|||||||
readonly description: string
|
readonly description: string
|
||||||
readonly max_ttl_ms: number
|
readonly max_ttl_ms: number
|
||||||
readonly min_autostart_interval_ms: number
|
readonly min_autostart_interval_ms: number
|
||||||
|
readonly created_by_id?: string
|
||||||
|
readonly created_by_name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/templateversions.go:14:6
|
// From codersdk/templateversions.go:14:6
|
||||||
@ -276,12 +278,12 @@ export interface TemplateVersionParameter {
|
|||||||
readonly default_source_value: boolean
|
readonly default_source_value: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/templates.go:98:6
|
// From codersdk/templates.go:100:6
|
||||||
export interface TemplateVersionsByTemplateRequest extends Pagination {
|
export interface TemplateVersionsByTemplateRequest extends Pagination {
|
||||||
readonly template_id: string
|
readonly template_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/templates.go:30:6
|
// From codersdk/templates.go:32:6
|
||||||
export interface UpdateActiveTemplateVersion {
|
export interface UpdateActiveTemplateVersion {
|
||||||
readonly id: string
|
readonly id: string
|
||||||
}
|
}
|
||||||
@ -291,7 +293,7 @@ export interface UpdateRoles {
|
|||||||
readonly roles: string[]
|
readonly roles: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/templates.go:34:6
|
// From codersdk/templates.go:36:6
|
||||||
export interface UpdateTemplateMeta {
|
export interface UpdateTemplateMeta {
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly max_ttl_ms?: number
|
readonly max_ttl_ms?: number
|
||||||
|
@ -23,3 +23,12 @@ UsedByMany.args = {
|
|||||||
},
|
},
|
||||||
activeVersion: Mocks.MockTemplateVersion,
|
activeVersion: Mocks.MockTemplateVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const UnknownCreator = Template.bind({})
|
||||||
|
UnknownCreator.args = {
|
||||||
|
template: {
|
||||||
|
...Mocks.MockTemplate,
|
||||||
|
created_by_name: "",
|
||||||
|
},
|
||||||
|
activeVersion: Mocks.MockTemplateVersion,
|
||||||
|
}
|
||||||
|
@ -13,6 +13,8 @@ const Language = {
|
|||||||
lastUpdateLabel: "Last updated",
|
lastUpdateLabel: "Last updated",
|
||||||
userPlural: "users",
|
userPlural: "users",
|
||||||
userSingular: "user",
|
userSingular: "user",
|
||||||
|
createdByLabel: "Created by",
|
||||||
|
defaultTemplateCreator: "<unknown>",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TemplateStatsProps {
|
export interface TemplateStatsProps {
|
||||||
@ -45,6 +47,11 @@ export const TemplateStats: FC<TemplateStatsProps> = ({ template, activeVersion
|
|||||||
{dayjs().to(dayjs(template.updated_at))}
|
{dayjs().to(dayjs(template.updated_at))}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.statsDivider} />
|
||||||
|
<div className={styles.statItem}>
|
||||||
|
<span className={styles.statsLabel}>{Language.createdByLabel}</span>
|
||||||
|
<span className={styles.statsValue}>{template.created_by_name || Language.defaultTemplateCreator}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -63,7 +70,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
statItem: {
|
statItem: {
|
||||||
minWidth: theme.spacing(20),
|
minWidth: "20%",
|
||||||
padding: theme.spacing(2),
|
padding: theme.spacing(2),
|
||||||
paddingTop: theme.spacing(1.75),
|
paddingTop: theme.spacing(1.75),
|
||||||
},
|
},
|
||||||
|
@ -69,7 +69,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
statItem: {
|
statItem: {
|
||||||
minWidth: theme.spacing(20),
|
minWidth: "16%",
|
||||||
padding: theme.spacing(2),
|
padding: theme.spacing(2),
|
||||||
paddingTop: theme.spacing(1.75),
|
paddingTop: theme.spacing(1.75),
|
||||||
},
|
},
|
||||||
|
@ -49,6 +49,8 @@ export const Language = {
|
|||||||
templateTooltipTitle: "What is template?",
|
templateTooltipTitle: "What is template?",
|
||||||
templateTooltipText: "With templates you can create a common configuration for your workspaces using Terraform.",
|
templateTooltipText: "With templates you can create a common configuration for your workspaces using Terraform.",
|
||||||
templateTooltipLink: "Manage templates",
|
templateTooltipLink: "Manage templates",
|
||||||
|
createdByLabel: "Created by",
|
||||||
|
defaultTemplateCreator: "<unknown>",
|
||||||
}
|
}
|
||||||
|
|
||||||
const TemplateHelpTooltip: React.FC = () => {
|
const TemplateHelpTooltip: React.FC = () => {
|
||||||
@ -95,6 +97,7 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = (props) => {
|
|||||||
<TableCell>{Language.nameLabel}</TableCell>
|
<TableCell>{Language.nameLabel}</TableCell>
|
||||||
<TableCell>{Language.usedByLabel}</TableCell>
|
<TableCell>{Language.usedByLabel}</TableCell>
|
||||||
<TableCell>{Language.lastUpdatedLabel}</TableCell>
|
<TableCell>{Language.lastUpdatedLabel}</TableCell>
|
||||||
|
<TableCell>{Language.createdByLabel}</TableCell>
|
||||||
<TableCell width="1%"></TableCell>
|
<TableCell width="1%"></TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
@ -137,6 +140,7 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = (props) => {
|
|||||||
<TableCell>{Language.developerCount(template.workspace_owner_count)}</TableCell>
|
<TableCell>{Language.developerCount(template.workspace_owner_count)}</TableCell>
|
||||||
|
|
||||||
<TableCell data-chromatic="ignore">{dayjs().to(dayjs(template.updated_at))}</TableCell>
|
<TableCell data-chromatic="ignore">{dayjs().to(dayjs(template.updated_at))}</TableCell>
|
||||||
|
<TableCell>{template.created_by_name || Language.defaultTemplateCreator}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className={styles.arrowCell}>
|
<div className={styles.arrowCell}>
|
||||||
<KeyboardArrowRight className={styles.arrowRight} />
|
<KeyboardArrowRight className={styles.arrowRight} />
|
||||||
|
@ -114,6 +114,8 @@ export const MockTemplate: TypesGen.Template = {
|
|||||||
description: "This is a test description.",
|
description: "This is a test description.",
|
||||||
max_ttl_ms: 604800000,
|
max_ttl_ms: 604800000,
|
||||||
min_autostart_interval_ms: 3600000,
|
min_autostart_interval_ms: 3600000,
|
||||||
|
created_by_id: "test-creator-id",
|
||||||
|
created_by_name: "test_creator",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MockWorkspaceAutostartDisabled: TypesGen.UpdateWorkspaceAutostartRequest = {
|
export const MockWorkspaceAutostartDisabled: TypesGen.UpdateWorkspaceAutostartRequest = {
|
||||||
|
Reference in New Issue
Block a user