diff --git a/coderd/users.go b/coderd/users.go index 5ef87e7ead824..f892262e21ee6 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -912,3 +912,12 @@ func userOrganizationIDs(ctx context.Context, api *API, user database.User) ([]u member := organizationIDsByMemberIDsRows[0] return member.OrganizationIDs, nil } + +func findUser(id uuid.UUID, users []database.User) *database.User { + for _, u := range users { + if u.ID == id { + return &u + } + } + return nil +} diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index 5a2f567572f84..e6505a5395c83 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -37,7 +37,7 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) { return } - owner, err := api.Database.GetUserByID(r.Context(), workspace.OwnerID) + users, err := api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{workspace.OwnerID, workspaceBuild.InitiatorID}) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: "Internal error fetching user.", @@ -46,7 +46,9 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(rw, http.StatusOK, convertWorkspaceBuild(owner, workspace, workspaceBuild, job)) + httpapi.Write(rw, http.StatusOK, + convertWorkspaceBuild(findUser(workspace.OwnerID, users), findUser(workspaceBuild.InitiatorID, users), + workspace, workspaceBuild, job)) } func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { @@ -128,7 +130,11 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { jobByID[job.ID.String()] = job } - owner, err := api.Database.GetUserByID(r.Context(), workspace.OwnerID) + userIDs := []uuid.UUID{workspace.OwnerID} + for _, build := range builds { + userIDs = append(userIDs, build.InitiatorID) + } + users, err := api.Database.GetUsersByIDs(r.Context(), userIDs) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: "Internal error fetching user.", @@ -146,7 +152,9 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { }) return } - apiBuilds = append(apiBuilds, convertWorkspaceBuild(owner, workspace, build, job)) + apiBuilds = append(apiBuilds, + convertWorkspaceBuild(findUser(workspace.OwnerID, users), findUser(build.InitiatorID, users), + workspace, build, job)) } httpapi.Write(rw, http.StatusOK, apiBuilds) @@ -185,7 +193,7 @@ func (api *API) workspaceBuildByName(rw http.ResponseWriter, r *http.Request) { }) return } - owner, err := api.Database.GetUserByID(r.Context(), workspace.OwnerID) + users, err := api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{workspace.OwnerID, workspaceBuild.InitiatorID}) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: "Internal error getting user.", @@ -194,7 +202,9 @@ func (api *API) workspaceBuildByName(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(rw, http.StatusOK, convertWorkspaceBuild(owner, workspace, workspaceBuild, job)) + httpapi.Write(rw, http.StatusOK, + convertWorkspaceBuild(findUser(workspace.OwnerID, users), findUser(workspaceBuild.InitiatorID, users), + workspace, workspaceBuild, job)) } func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { @@ -368,7 +378,10 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { return } - owner, err := api.Database.GetUserByID(r.Context(), workspace.OwnerID) + users, err := api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{ + workspace.OwnerID, + workspaceBuild.InitiatorID, + }) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: "Internal error getting user.", @@ -378,7 +391,8 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { } httpapi.Write(rw, http.StatusCreated, - convertWorkspaceBuild(owner, workspace, workspaceBuild, provisionerJob)) + convertWorkspaceBuild(findUser(workspace.OwnerID, users), findUser(workspaceBuild.InitiatorID, users), + workspace, workspaceBuild, provisionerJob)) } func (api *API) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Request) { @@ -508,7 +522,8 @@ func (api *API) workspaceBuildState(rw http.ResponseWriter, r *http.Request) { } func convertWorkspaceBuild( - workspaceOwner database.User, + workspaceOwner *database.User, + buildInitiator *database.User, workspace database.Workspace, workspaceBuild database.WorkspaceBuild, job database.ProvisionerJob) codersdk.WorkspaceBuild { @@ -516,12 +531,25 @@ func convertWorkspaceBuild( if workspace.ID != workspaceBuild.WorkspaceID { panic("workspace and build do not match") } + + // Both owner and initiator should always be present. But from a static + // code analysis POV, these could be nil. + ownerName := "unknown" + if workspaceOwner != nil { + ownerName = workspaceOwner.Username + } + + initiatorName := "unknown" + if workspaceOwner != nil { + initiatorName = buildInitiator.Username + } + return codersdk.WorkspaceBuild{ ID: workspaceBuild.ID, CreatedAt: workspaceBuild.CreatedAt, UpdatedAt: workspaceBuild.UpdatedAt, WorkspaceOwnerID: workspace.OwnerID, - WorkspaceOwnerName: workspaceOwner.Username, + WorkspaceOwnerName: ownerName, WorkspaceID: workspaceBuild.WorkspaceID, WorkspaceName: workspace.Name, TemplateVersionID: workspaceBuild.TemplateVersionID, @@ -529,6 +557,7 @@ func convertWorkspaceBuild( Name: workspaceBuild.Name, Transition: codersdk.WorkspaceTransition(workspaceBuild.Transition), InitiatorID: workspaceBuild.InitiatorID, + InitiatorUsername: initiatorName, Job: convertProvisionerJob(job), Deadline: workspaceBuild.Deadline, } diff --git a/coderd/workspacebuilds_test.go b/coderd/workspacebuilds_test.go index 5b15f54c29d52..1734f52b836f9 100644 --- a/coderd/workspacebuilds_test.go +++ b/coderd/workspacebuilds_test.go @@ -33,15 +33,18 @@ func TestWorkspaceBuilds(t *testing.T) { t.Run("Single", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) - user := coderdtest.CreateFirstUser(t, client) - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + first := coderdtest.CreateFirstUser(t, client) + user, err := client.User(context.Background(), codersdk.Me) + require.NoError(t, err, "fetch me") + version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, nil) + template := coderdtest.CreateTemplate(t, client, first.OrganizationID, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + workspace := coderdtest.CreateWorkspace(t, client, first.OrganizationID, template.ID) builds, err := client.WorkspaceBuilds(context.Background(), codersdk.WorkspaceBuildsRequest{WorkspaceID: workspace.ID}) require.Len(t, builds, 1) require.Equal(t, int32(1), builds[0].BuildNumber) + require.Equal(t, user.Username, builds[0].InitiatorUsername) require.NoError(t, err) }) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 079f880ed44f3..255685a05901e 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -73,7 +73,7 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) { group errgroup.Group job database.ProvisionerJob template database.Template - owner database.User + users []database.User ) group.Go(func() (err error) { job, err = api.Database.GetProvisionerJobByID(r.Context(), build.JobID) @@ -84,7 +84,7 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) { return err }) group.Go(func() (err error) { - owner, err = api.Database.GetUserByID(r.Context(), workspace.OwnerID) + users, err = api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{workspace.OwnerID, build.InitiatorID}) return err }) err = group.Wait() @@ -96,7 +96,8 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(rw, http.StatusOK, convertWorkspace(workspace, build, job, template, owner)) + httpapi.Write(rw, http.StatusOK, convertWorkspace(workspace, build, job, template, + findUser(workspace.OwnerID, users), findUser(build.InitiatorID, users))) } // workspaces returns all workspaces a user can read. @@ -232,7 +233,16 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) return } - httpapi.Write(rw, http.StatusOK, convertWorkspace(workspace, build, job, template, owner)) + initiator, err := api.Database.GetUserByID(r.Context(), build.InitiatorID) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: "Internal error fetching template.", + Detail: err.Error(), + }) + return + } + + httpapi.Write(rw, http.StatusOK, convertWorkspace(workspace, build, job, template, &owner, &initiator)) } // Create a new workspace for the currently authenticated user. @@ -465,7 +475,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req }) return } - user, err := api.Database.GetUserByID(r.Context(), apiKey.UserID) + users, err := api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{apiKey.UserID, workspaceBuild.InitiatorID}) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: "Internal error fetching user.", @@ -474,7 +484,8 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req return } - httpapi.Write(rw, http.StatusCreated, convertWorkspace(workspace, workspaceBuild, templateVersionJob, template, user)) + httpapi.Write(rw, http.StatusCreated, convertWorkspace(workspace, workspaceBuild, templateVersionJob, template, + findUser(apiKey.UserID, users), findUser(workspaceBuild.InitiatorID, users))) } func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) { @@ -691,7 +702,7 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) { group errgroup.Group job database.ProvisionerJob template database.Template - owner database.User + users []database.User ) group.Go(func() (err error) { job, err = api.Database.GetProvisionerJobByID(r.Context(), build.JobID) @@ -702,7 +713,7 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) { return err }) group.Go(func() (err error) { - owner, err = api.Database.GetUserByID(r.Context(), workspace.OwnerID) + users, err = api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{workspace.OwnerID, build.InitiatorID}) return err }) err = group.Wait() @@ -714,7 +725,8 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) { return } - _ = wsjson.Write(ctx, c, convertWorkspace(workspace, build, job, template, owner)) + _ = wsjson.Write(ctx, c, convertWorkspace(workspace, build, job, template, + findUser(workspace.OwnerID, users), findUser(build.InitiatorID, users))) case <-ctx.Done(): return } @@ -724,16 +736,20 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) { func convertWorkspaces(ctx context.Context, db database.Store, workspaces []database.Workspace) ([]codersdk.Workspace, error) { workspaceIDs := make([]uuid.UUID, 0, len(workspaces)) templateIDs := make([]uuid.UUID, 0, len(workspaces)) - ownerIDs := make([]uuid.UUID, 0, len(workspaces)) + userIDs := make([]uuid.UUID, 0, len(workspaces)) for _, workspace := range workspaces { workspaceIDs = append(workspaceIDs, workspace.ID) templateIDs = append(templateIDs, workspace.TemplateID) - ownerIDs = append(ownerIDs, workspace.OwnerID) + userIDs = append(userIDs, workspace.OwnerID) } workspaceBuilds, err := db.GetLatestWorkspaceBuildsByWorkspaceIDs(ctx, workspaceIDs) if errors.Is(err, sql.ErrNoRows) { err = nil } + for _, build := range workspaceBuilds { + userIDs = append(userIDs, build.InitiatorID) + } + if err != nil { return nil, xerrors.Errorf("get workspace builds: %w", err) } @@ -744,7 +760,7 @@ func convertWorkspaces(ctx context.Context, db database.Store, workspaces []data if err != nil { return nil, xerrors.Errorf("get templates: %w", err) } - users, err := db.GetUsersByIDs(ctx, ownerIDs) + users, err := db.GetUsersByIDs(ctx, userIDs) if err != nil { return nil, xerrors.Errorf("get users: %w", err) } @@ -803,11 +819,15 @@ func convertWorkspaces(ctx context.Context, db database.Store, workspaces []data if !exists { return nil, xerrors.Errorf("build job not found for workspace: %w", err) } - user, exists := userByID[workspace.OwnerID] + owner, exists := userByID[workspace.OwnerID] if !exists { return nil, xerrors.Errorf("owner not found for workspace: %q", workspace.Name) } - apiWorkspaces = append(apiWorkspaces, convertWorkspace(workspace, build, job, template, user)) + initiator, exists := userByID[build.InitiatorID] + if !exists { + return nil, xerrors.Errorf("build initiator not found for workspace: %q", workspace.Name) + } + apiWorkspaces = append(apiWorkspaces, convertWorkspace(workspace, build, job, template, &owner, &initiator)) } return apiWorkspaces, nil } @@ -816,7 +836,9 @@ func convertWorkspace( workspaceBuild database.WorkspaceBuild, job database.ProvisionerJob, template database.Template, - owner database.User) codersdk.Workspace { + owner *database.User, + initiator *database.User, +) codersdk.Workspace { var autostartSchedule *string if workspace.AutostartSchedule.Valid { autostartSchedule = &workspace.AutostartSchedule.String @@ -830,7 +852,7 @@ func convertWorkspace( OwnerID: workspace.OwnerID, OwnerName: owner.Username, TemplateID: workspace.TemplateID, - LatestBuild: convertWorkspaceBuild(owner, workspace, workspaceBuild, job), + LatestBuild: convertWorkspaceBuild(owner, initiator, workspace, workspaceBuild, job), TemplateName: template.Name, Outdated: workspaceBuild.TemplateVersionID.String() != template.ActiveVersionID.String(), Name: workspace.Name, diff --git a/codersdk/workspacebuilds.go b/codersdk/workspacebuilds.go index ccf3e917f5d63..ec5dfcbc63ccd 100644 --- a/codersdk/workspacebuilds.go +++ b/codersdk/workspacebuilds.go @@ -34,6 +34,7 @@ type WorkspaceBuild struct { Name string `json:"name"` Transition WorkspaceTransition `json:"transition"` InitiatorID uuid.UUID `json:"initiator_id"` + InitiatorUsername string `json:"initiator_name"` Job ProvisionerJob `json:"job"` Deadline time.Time `json:"deadline"` } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index f565f6f4e50ff..90c651c61cd9a 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -454,6 +454,7 @@ export interface WorkspaceBuild { readonly name: string readonly transition: WorkspaceTransition readonly initiator_id: string + readonly initiator_name: string readonly job: ProvisionerJob readonly deadline: string } diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index c58097d01d9be..53414283e4cc9 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -131,6 +131,7 @@ export const MockWorkspaceBuild: TypesGen.WorkspaceBuild = { created_at: "2022-05-17T17:39:01.382927298Z", id: "1", initiator_id: "", + initiator_name: "", job: MockProvisionerJob, name: "a-workspace-build", template_version_id: "", 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