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

Lines changed: 9 additions & 0 deletions
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

Lines changed: 39 additions & 10 deletions
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

Lines changed: 7 additions & 4 deletions
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

Lines changed: 38 additions & 16 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy