Skip to content

Commit 74fe38e

Browse files
authored
feat: Add initiator_username to workspace builds in apis (#2174)
* feat: Add initiator_username to workspace builds in apis
1 parent 1470149 commit 74fe38e

File tree

7 files changed

+96
-30
lines changed

7 files changed

+96
-30
lines changed

coderd/users.go

+9
Original file line numberDiff line numberDiff line change
@@ -912,3 +912,12 @@ func userOrganizationIDs(ctx context.Context, api *API, user database.User) ([]u
912912
member := organizationIDsByMemberIDsRows[0]
913913
return member.OrganizationIDs, nil
914914
}
915+
916+
func findUser(id uuid.UUID, users []database.User) *database.User {
917+
for _, u := range users {
918+
if u.ID == id {
919+
return &u
920+
}
921+
}
922+
return nil
923+
}

coderd/workspacebuilds.go

+39-10
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) {
3737
return
3838
}
3939

40-
owner, err := api.Database.GetUserByID(r.Context(), workspace.OwnerID)
40+
users, err := api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{workspace.OwnerID, workspaceBuild.InitiatorID})
4141
if err != nil {
4242
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
4343
Message: "Internal error fetching user.",
@@ -46,7 +46,9 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) {
4646
return
4747
}
4848

49-
httpapi.Write(rw, http.StatusOK, convertWorkspaceBuild(owner, workspace, workspaceBuild, job))
49+
httpapi.Write(rw, http.StatusOK,
50+
convertWorkspaceBuild(findUser(workspace.OwnerID, users), findUser(workspaceBuild.InitiatorID, users),
51+
workspace, workspaceBuild, job))
5052
}
5153

5254
func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) {
@@ -128,7 +130,11 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) {
128130
jobByID[job.ID.String()] = job
129131
}
130132

131-
owner, err := api.Database.GetUserByID(r.Context(), workspace.OwnerID)
133+
userIDs := []uuid.UUID{workspace.OwnerID}
134+
for _, build := range builds {
135+
userIDs = append(userIDs, build.InitiatorID)
136+
}
137+
users, err := api.Database.GetUsersByIDs(r.Context(), userIDs)
132138
if err != nil {
133139
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
134140
Message: "Internal error fetching user.",
@@ -146,7 +152,9 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) {
146152
})
147153
return
148154
}
149-
apiBuilds = append(apiBuilds, convertWorkspaceBuild(owner, workspace, build, job))
155+
apiBuilds = append(apiBuilds,
156+
convertWorkspaceBuild(findUser(workspace.OwnerID, users), findUser(build.InitiatorID, users),
157+
workspace, build, job))
150158
}
151159

152160
httpapi.Write(rw, http.StatusOK, apiBuilds)
@@ -185,7 +193,7 @@ func (api *API) workspaceBuildByName(rw http.ResponseWriter, r *http.Request) {
185193
})
186194
return
187195
}
188-
owner, err := api.Database.GetUserByID(r.Context(), workspace.OwnerID)
196+
users, err := api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{workspace.OwnerID, workspaceBuild.InitiatorID})
189197
if err != nil {
190198
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
191199
Message: "Internal error getting user.",
@@ -194,7 +202,9 @@ func (api *API) workspaceBuildByName(rw http.ResponseWriter, r *http.Request) {
194202
return
195203
}
196204

197-
httpapi.Write(rw, http.StatusOK, convertWorkspaceBuild(owner, workspace, workspaceBuild, job))
205+
httpapi.Write(rw, http.StatusOK,
206+
convertWorkspaceBuild(findUser(workspace.OwnerID, users), findUser(workspaceBuild.InitiatorID, users),
207+
workspace, workspaceBuild, job))
198208
}
199209

200210
func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
@@ -368,7 +378,10 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
368378
return
369379
}
370380

371-
owner, err := api.Database.GetUserByID(r.Context(), workspace.OwnerID)
381+
users, err := api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{
382+
workspace.OwnerID,
383+
workspaceBuild.InitiatorID,
384+
})
372385
if err != nil {
373386
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
374387
Message: "Internal error getting user.",
@@ -378,7 +391,8 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
378391
}
379392

380393
httpapi.Write(rw, http.StatusCreated,
381-
convertWorkspaceBuild(owner, workspace, workspaceBuild, provisionerJob))
394+
convertWorkspaceBuild(findUser(workspace.OwnerID, users), findUser(workspaceBuild.InitiatorID, users),
395+
workspace, workspaceBuild, provisionerJob))
382396
}
383397

384398
func (api *API) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Request) {
@@ -508,27 +522,42 @@ func (api *API) workspaceBuildState(rw http.ResponseWriter, r *http.Request) {
508522
}
509523

510524
func convertWorkspaceBuild(
511-
workspaceOwner database.User,
525+
workspaceOwner *database.User,
526+
buildInitiator *database.User,
512527
workspace database.Workspace,
513528
workspaceBuild database.WorkspaceBuild,
514529
job database.ProvisionerJob) codersdk.WorkspaceBuild {
515530
//nolint:unconvert
516531
if workspace.ID != workspaceBuild.WorkspaceID {
517532
panic("workspace and build do not match")
518533
}
534+
535+
// Both owner and initiator should always be present. But from a static
536+
// code analysis POV, these could be nil.
537+
ownerName := "unknown"
538+
if workspaceOwner != nil {
539+
ownerName = workspaceOwner.Username
540+
}
541+
542+
initiatorName := "unknown"
543+
if workspaceOwner != nil {
544+
initiatorName = buildInitiator.Username
545+
}
546+
519547
return codersdk.WorkspaceBuild{
520548
ID: workspaceBuild.ID,
521549
CreatedAt: workspaceBuild.CreatedAt,
522550
UpdatedAt: workspaceBuild.UpdatedAt,
523551
WorkspaceOwnerID: workspace.OwnerID,
524-
WorkspaceOwnerName: workspaceOwner.Username,
552+
WorkspaceOwnerName: ownerName,
525553
WorkspaceID: workspaceBuild.WorkspaceID,
526554
WorkspaceName: workspace.Name,
527555
TemplateVersionID: workspaceBuild.TemplateVersionID,
528556
BuildNumber: workspaceBuild.BuildNumber,
529557
Name: workspaceBuild.Name,
530558
Transition: codersdk.WorkspaceTransition(workspaceBuild.Transition),
531559
InitiatorID: workspaceBuild.InitiatorID,
560+
InitiatorUsername: initiatorName,
532561
Job: convertProvisionerJob(job),
533562
Deadline: workspaceBuild.Deadline,
534563
}

coderd/workspacebuilds_test.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,18 @@ func TestWorkspaceBuilds(t *testing.T) {
3333
t.Run("Single", func(t *testing.T) {
3434
t.Parallel()
3535
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
36-
user := coderdtest.CreateFirstUser(t, client)
37-
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
38-
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
36+
first := coderdtest.CreateFirstUser(t, client)
37+
user, err := client.User(context.Background(), codersdk.Me)
38+
require.NoError(t, err, "fetch me")
39+
version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, nil)
40+
template := coderdtest.CreateTemplate(t, client, first.OrganizationID, version.ID)
3941
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
40-
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
42+
workspace := coderdtest.CreateWorkspace(t, client, first.OrganizationID, template.ID)
4143
builds, err := client.WorkspaceBuilds(context.Background(),
4244
codersdk.WorkspaceBuildsRequest{WorkspaceID: workspace.ID})
4345
require.Len(t, builds, 1)
4446
require.Equal(t, int32(1), builds[0].BuildNumber)
47+
require.Equal(t, user.Username, builds[0].InitiatorUsername)
4548
require.NoError(t, err)
4649
})
4750

coderd/workspaces.go

+38-16
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
7373
group errgroup.Group
7474
job database.ProvisionerJob
7575
template database.Template
76-
owner database.User
76+
users []database.User
7777
)
7878
group.Go(func() (err error) {
7979
job, err = api.Database.GetProvisionerJobByID(r.Context(), build.JobID)
@@ -84,7 +84,7 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
8484
return err
8585
})
8686
group.Go(func() (err error) {
87-
owner, err = api.Database.GetUserByID(r.Context(), workspace.OwnerID)
87+
users, err = api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{workspace.OwnerID, build.InitiatorID})
8888
return err
8989
})
9090
err = group.Wait()
@@ -96,7 +96,8 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
9696
return
9797
}
9898

99-
httpapi.Write(rw, http.StatusOK, convertWorkspace(workspace, build, job, template, owner))
99+
httpapi.Write(rw, http.StatusOK, convertWorkspace(workspace, build, job, template,
100+
findUser(workspace.OwnerID, users), findUser(build.InitiatorID, users)))
100101
}
101102

102103
// workspaces returns all workspaces a user can read.
@@ -232,7 +233,16 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
232233
return
233234
}
234235

235-
httpapi.Write(rw, http.StatusOK, convertWorkspace(workspace, build, job, template, owner))
236+
initiator, err := api.Database.GetUserByID(r.Context(), build.InitiatorID)
237+
if err != nil {
238+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
239+
Message: "Internal error fetching template.",
240+
Detail: err.Error(),
241+
})
242+
return
243+
}
244+
245+
httpapi.Write(rw, http.StatusOK, convertWorkspace(workspace, build, job, template, &owner, &initiator))
236246
}
237247

238248
// Create a new workspace for the currently authenticated user.
@@ -465,7 +475,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
465475
})
466476
return
467477
}
468-
user, err := api.Database.GetUserByID(r.Context(), apiKey.UserID)
478+
users, err := api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{apiKey.UserID, workspaceBuild.InitiatorID})
469479
if err != nil {
470480
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
471481
Message: "Internal error fetching user.",
@@ -474,7 +484,8 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
474484
return
475485
}
476486

477-
httpapi.Write(rw, http.StatusCreated, convertWorkspace(workspace, workspaceBuild, templateVersionJob, template, user))
487+
httpapi.Write(rw, http.StatusCreated, convertWorkspace(workspace, workspaceBuild, templateVersionJob, template,
488+
findUser(apiKey.UserID, users), findUser(workspaceBuild.InitiatorID, users)))
478489
}
479490

480491
func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) {
@@ -691,7 +702,7 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) {
691702
group errgroup.Group
692703
job database.ProvisionerJob
693704
template database.Template
694-
owner database.User
705+
users []database.User
695706
)
696707
group.Go(func() (err error) {
697708
job, err = api.Database.GetProvisionerJobByID(r.Context(), build.JobID)
@@ -702,7 +713,7 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) {
702713
return err
703714
})
704715
group.Go(func() (err error) {
705-
owner, err = api.Database.GetUserByID(r.Context(), workspace.OwnerID)
716+
users, err = api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{workspace.OwnerID, build.InitiatorID})
706717
return err
707718
})
708719
err = group.Wait()
@@ -714,7 +725,8 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) {
714725
return
715726
}
716727

717-
_ = wsjson.Write(ctx, c, convertWorkspace(workspace, build, job, template, owner))
728+
_ = wsjson.Write(ctx, c, convertWorkspace(workspace, build, job, template,
729+
findUser(workspace.OwnerID, users), findUser(build.InitiatorID, users)))
718730
case <-ctx.Done():
719731
return
720732
}
@@ -724,16 +736,20 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) {
724736
func convertWorkspaces(ctx context.Context, db database.Store, workspaces []database.Workspace) ([]codersdk.Workspace, error) {
725737
workspaceIDs := make([]uuid.UUID, 0, len(workspaces))
726738
templateIDs := make([]uuid.UUID, 0, len(workspaces))
727-
ownerIDs := make([]uuid.UUID, 0, len(workspaces))
739+
userIDs := make([]uuid.UUID, 0, len(workspaces))
728740
for _, workspace := range workspaces {
729741
workspaceIDs = append(workspaceIDs, workspace.ID)
730742
templateIDs = append(templateIDs, workspace.TemplateID)
731-
ownerIDs = append(ownerIDs, workspace.OwnerID)
743+
userIDs = append(userIDs, workspace.OwnerID)
732744
}
733745
workspaceBuilds, err := db.GetLatestWorkspaceBuildsByWorkspaceIDs(ctx, workspaceIDs)
734746
if errors.Is(err, sql.ErrNoRows) {
735747
err = nil
736748
}
749+
for _, build := range workspaceBuilds {
750+
userIDs = append(userIDs, build.InitiatorID)
751+
}
752+
737753
if err != nil {
738754
return nil, xerrors.Errorf("get workspace builds: %w", err)
739755
}
@@ -744,7 +760,7 @@ func convertWorkspaces(ctx context.Context, db database.Store, workspaces []data
744760
if err != nil {
745761
return nil, xerrors.Errorf("get templates: %w", err)
746762
}
747-
users, err := db.GetUsersByIDs(ctx, ownerIDs)
763+
users, err := db.GetUsersByIDs(ctx, userIDs)
748764
if err != nil {
749765
return nil, xerrors.Errorf("get users: %w", err)
750766
}
@@ -803,11 +819,15 @@ func convertWorkspaces(ctx context.Context, db database.Store, workspaces []data
803819
if !exists {
804820
return nil, xerrors.Errorf("build job not found for workspace: %w", err)
805821
}
806-
user, exists := userByID[workspace.OwnerID]
822+
owner, exists := userByID[workspace.OwnerID]
807823
if !exists {
808824
return nil, xerrors.Errorf("owner not found for workspace: %q", workspace.Name)
809825
}
810-
apiWorkspaces = append(apiWorkspaces, convertWorkspace(workspace, build, job, template, user))
826+
initiator, exists := userByID[build.InitiatorID]
827+
if !exists {
828+
return nil, xerrors.Errorf("build initiator not found for workspace: %q", workspace.Name)
829+
}
830+
apiWorkspaces = append(apiWorkspaces, convertWorkspace(workspace, build, job, template, &owner, &initiator))
811831
}
812832
return apiWorkspaces, nil
813833
}
@@ -816,7 +836,9 @@ func convertWorkspace(
816836
workspaceBuild database.WorkspaceBuild,
817837
job database.ProvisionerJob,
818838
template database.Template,
819-
owner database.User) codersdk.Workspace {
839+
owner *database.User,
840+
initiator *database.User,
841+
) codersdk.Workspace {
820842
var autostartSchedule *string
821843
if workspace.AutostartSchedule.Valid {
822844
autostartSchedule = &workspace.AutostartSchedule.String
@@ -830,7 +852,7 @@ func convertWorkspace(
830852
OwnerID: workspace.OwnerID,
831853
OwnerName: owner.Username,
832854
TemplateID: workspace.TemplateID,
833-
LatestBuild: convertWorkspaceBuild(owner, workspace, workspaceBuild, job),
855+
LatestBuild: convertWorkspaceBuild(owner, initiator, workspace, workspaceBuild, job),
834856
TemplateName: template.Name,
835857
Outdated: workspaceBuild.TemplateVersionID.String() != template.ActiveVersionID.String(),
836858
Name: workspace.Name,

codersdk/workspacebuilds.go

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type WorkspaceBuild struct {
3434
Name string `json:"name"`
3535
Transition WorkspaceTransition `json:"transition"`
3636
InitiatorID uuid.UUID `json:"initiator_id"`
37+
InitiatorUsername string `json:"initiator_name"`
3738
Job ProvisionerJob `json:"job"`
3839
Deadline time.Time `json:"deadline"`
3940
}

site/src/api/typesGenerated.ts

+1
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ export interface WorkspaceBuild {
454454
readonly name: string
455455
readonly transition: WorkspaceTransition
456456
readonly initiator_id: string
457+
readonly initiator_name: string
457458
readonly job: ProvisionerJob
458459
readonly deadline: string
459460
}

site/src/testHelpers/entities.ts

+1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ export const MockWorkspaceBuild: TypesGen.WorkspaceBuild = {
131131
created_at: "2022-05-17T17:39:01.382927298Z",
132132
id: "1",
133133
initiator_id: "",
134+
initiator_name: "",
134135
job: MockProvisionerJob,
135136
name: "a-workspace-build",
136137
template_version_id: "",

0 commit comments

Comments
 (0)