Skip to content

Commit 02d2aea

Browse files
authored
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
1 parent 46da59a commit 02d2aea

File tree

17 files changed

+135
-24
lines changed

17 files changed

+135
-24
lines changed

coderd/audit/diff_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func TestDiff(t *testing.T) {
8888
ActiveVersionID: uuid.UUID{3},
8989
MaxTtl: int64(time.Hour),
9090
MinAutostartInterval: int64(time.Minute),
91+
CreatedBy: uuid.NullUUID{UUID: uuid.UUID{4}, Valid: true},
9192
},
9293
exp: audit.Map{
9394
"id": uuid.UUID{1}.String(),
@@ -97,6 +98,7 @@ func TestDiff(t *testing.T) {
9798
"active_version_id": uuid.UUID{3}.String(),
9899
"max_ttl": int64(3600000000000),
99100
"min_autostart_interval": int64(60000000000),
101+
"created_by": uuid.UUID{4}.String(),
100102
},
101103
},
102104
})

coderd/audit/table.go

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ var AuditableResources = auditMap(map[any]map[string]Action{
7272
"description": ActionTrack,
7373
"max_ttl": ActionTrack,
7474
"min_autostart_interval": ActionTrack,
75+
"created_by": ActionTrack,
7576
},
7677
&database.TemplateVersion{}: {
7778
"id": ActionTrack,

coderd/database/databasefake/databasefake.go

+1
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,7 @@ func (q *fakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl
13411341
Description: arg.Description,
13421342
MaxTtl: arg.MaxTtl,
13431343
MinAutostartInterval: arg.MinAutostartInterval,
1344+
CreatedBy: arg.CreatedBy,
13441345
}
13451346
q.templates = append(q.templates, template)
13461347
return template, nil

coderd/database/dump.sql

+5-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE ONLY templates DROP COLUMN IF EXISTS created_by;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE ONLY templates ADD COLUMN IF NOT EXISTS created_by uuid REFERENCES users (id) ON DELETE RESTRICT;

coderd/database/models.go

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

+15-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/templates.sql

+3-2
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,11 @@ INSERT INTO
4949
active_version_id,
5050
description,
5151
max_ttl,
52-
min_autostart_interval
52+
min_autostart_interval,
53+
created_by
5354
)
5455
VALUES
55-
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *;
56+
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING *;
5657

5758
-- name: UpdateTemplateActiveVersionByID :exec
5859
UPDATE

coderd/templates.go

+74-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package coderd
22

33
import (
4+
"context"
45
"database/sql"
56
"errors"
67
"fmt"
@@ -49,7 +50,16 @@ func (api *API) template(rw http.ResponseWriter, r *http.Request) {
4950
count = uint32(workspaceCounts[0].Count)
5051
}
5152

52-
httpapi.Write(rw, http.StatusOK, convertTemplate(template, count))
53+
createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), api.Database, []database.Template{template})
54+
if err != nil {
55+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
56+
Message: "Internal error fetching creator name.",
57+
Detail: err.Error(),
58+
})
59+
return
60+
}
61+
62+
httpapi.Write(rw, http.StatusOK, convertTemplate(template, count, createdByNameMap[template.ID.String()]))
5363
}
5464

5565
func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
@@ -97,6 +107,7 @@ func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
97107
func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Request) {
98108
var createTemplate codersdk.CreateTemplateRequest
99109
organization := httpmw.OrganizationParam(r)
110+
apiKey := httpmw.APIKey(r)
100111
if !api.Authorize(rw, r, rbac.ActionCreate, rbac.ResourceTemplate.InOrg(organization.ID)) {
101112
return
102113
}
@@ -175,6 +186,10 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
175186
Description: createTemplate.Description,
176187
MaxTtl: int64(maxTTL),
177188
MinAutostartInterval: int64(minAutostartInterval),
189+
CreatedBy: uuid.NullUUID{
190+
UUID: apiKey.UserID,
191+
Valid: true,
192+
},
178193
})
179194
if err != nil {
180195
return xerrors.Errorf("insert template: %s", err)
@@ -208,7 +223,12 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
208223
}
209224
}
210225

211-
template = convertTemplate(dbTemplate, 0)
226+
createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), db, []database.Template{dbTemplate})
227+
if err != nil {
228+
return xerrors.Errorf("get creator name: %w", err)
229+
}
230+
231+
template = convertTemplate(dbTemplate, 0, createdByNameMap[dbTemplate.ID.String()])
212232
return nil
213233
})
214234
if err != nil {
@@ -258,7 +278,16 @@ func (api *API) templatesByOrganization(rw http.ResponseWriter, r *http.Request)
258278
return
259279
}
260280

261-
httpapi.Write(rw, http.StatusOK, convertTemplates(templates, workspaceCounts))
281+
createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), api.Database, templates)
282+
if err != nil {
283+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
284+
Message: "Internal error fetching creator names.",
285+
Detail: err.Error(),
286+
})
287+
return
288+
}
289+
290+
httpapi.Write(rw, http.StatusOK, convertTemplates(templates, workspaceCounts, createdByNameMap))
262291
}
263292

264293
func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Request) {
@@ -304,7 +333,16 @@ func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Re
304333
count = uint32(workspaceCounts[0].Count)
305334
}
306335

307-
httpapi.Write(rw, http.StatusOK, convertTemplate(template, count))
336+
createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), api.Database, []database.Template{template})
337+
if err != nil {
338+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
339+
Message: "Internal error fetching creator name.",
340+
Detail: err.Error(),
341+
})
342+
return
343+
}
344+
345+
httpapi.Write(rw, http.StatusOK, convertTemplate(template, count, createdByNameMap[template.ID.String()]))
308346
}
309347

310348
func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
@@ -400,29 +438,54 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
400438
return
401439
}
402440

403-
httpapi.Write(rw, http.StatusOK, convertTemplate(updated, count))
441+
createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), api.Database, []database.Template{updated})
442+
if err != nil {
443+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
444+
Message: "Internal error fetching creator name.",
445+
Detail: err.Error(),
446+
})
447+
return
448+
}
449+
450+
httpapi.Write(rw, http.StatusOK, convertTemplate(updated, count, createdByNameMap[updated.ID.String()]))
451+
}
452+
453+
func getCreatedByNamesByTemplateIDs(ctx context.Context, db database.Store, templates []database.Template) (map[string]string, error) {
454+
creators := make(map[string]string, len(templates))
455+
for _, template := range templates {
456+
if template.CreatedBy.Valid {
457+
creator, err := db.GetUserByID(ctx, template.CreatedBy.UUID)
458+
if err != nil {
459+
return map[string]string{}, err
460+
}
461+
creators[template.ID.String()] = creator.Username
462+
} else {
463+
creators[template.ID.String()] = ""
464+
}
465+
}
466+
return creators, nil
404467
}
405468

406-
func convertTemplates(templates []database.Template, workspaceCounts []database.GetWorkspaceOwnerCountsByTemplateIDsRow) []codersdk.Template {
469+
func convertTemplates(templates []database.Template, workspaceCounts []database.GetWorkspaceOwnerCountsByTemplateIDsRow, createdByNameMap map[string]string) []codersdk.Template {
407470
apiTemplates := make([]codersdk.Template, 0, len(templates))
408471
for _, template := range templates {
409472
found := false
410473
for _, workspaceCount := range workspaceCounts {
411474
if workspaceCount.TemplateID.String() != template.ID.String() {
412475
continue
413476
}
414-
apiTemplates = append(apiTemplates, convertTemplate(template, uint32(workspaceCount.Count)))
477+
apiTemplates = append(apiTemplates, convertTemplate(template, uint32(workspaceCount.Count), createdByNameMap[template.ID.String()]))
415478
found = true
416479
break
417480
}
418481
if !found {
419-
apiTemplates = append(apiTemplates, convertTemplate(template, uint32(0)))
482+
apiTemplates = append(apiTemplates, convertTemplate(template, uint32(0), createdByNameMap[template.ID.String()]))
420483
}
421484
}
422485
return apiTemplates
423486
}
424487

425-
func convertTemplate(template database.Template, workspaceOwnerCount uint32) codersdk.Template {
488+
func convertTemplate(template database.Template, workspaceOwnerCount uint32, createdByName string) codersdk.Template {
426489
return codersdk.Template{
427490
ID: template.ID,
428491
CreatedAt: template.CreatedAt,
@@ -435,5 +498,7 @@ func convertTemplate(template database.Template, workspaceOwnerCount uint32) cod
435498
Description: template.Description,
436499
MaxTTLMillis: time.Duration(template.MaxTtl).Milliseconds(),
437500
MinAutostartIntervalMillis: time.Duration(template.MinAutostartInterval).Milliseconds(),
501+
CreatedByID: template.CreatedBy,
502+
CreatedByName: createdByName,
438503
}
439504
}

codersdk/templates.go

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ type Template struct {
2525
Description string `json:"description"`
2626
MaxTTLMillis int64 `json:"max_ttl_ms"`
2727
MinAutostartIntervalMillis int64 `json:"min_autostart_interval_ms"`
28+
CreatedByID uuid.NullUUID `json:"created_by_id"`
29+
CreatedByName string `json:"created_by_name"`
2830
}
2931

3032
type UpdateActiveTemplateVersion struct {

site/src/api/typesGenerated.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,8 @@ export interface Template {
247247
readonly description: string
248248
readonly max_ttl_ms: number
249249
readonly min_autostart_interval_ms: number
250+
readonly created_by_id?: string
251+
readonly created_by_name: string
250252
}
251253

252254
// From codersdk/templateversions.go:14:6
@@ -276,12 +278,12 @@ export interface TemplateVersionParameter {
276278
readonly default_source_value: boolean
277279
}
278280

279-
// From codersdk/templates.go:98:6
281+
// From codersdk/templates.go:100:6
280282
export interface TemplateVersionsByTemplateRequest extends Pagination {
281283
readonly template_id: string
282284
}
283285

284-
// From codersdk/templates.go:30:6
286+
// From codersdk/templates.go:32:6
285287
export interface UpdateActiveTemplateVersion {
286288
readonly id: string
287289
}
@@ -291,7 +293,7 @@ export interface UpdateRoles {
291293
readonly roles: string[]
292294
}
293295

294-
// From codersdk/templates.go:34:6
296+
// From codersdk/templates.go:36:6
295297
export interface UpdateTemplateMeta {
296298
readonly description?: string
297299
readonly max_ttl_ms?: number

0 commit comments

Comments
 (0)