Skip to content

feat: Add initiator_username to workspace builds in apis #2174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions coderd/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
49 changes: 39 additions & 10 deletions coderd/workspacebuilds.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand All @@ -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) {
Expand Down Expand Up @@ -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.",
Expand All @@ -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)
Expand Down Expand Up @@ -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.",
Expand All @@ -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) {
Expand Down Expand Up @@ -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.",
Expand All @@ -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) {
Expand Down Expand Up @@ -508,27 +522,42 @@ 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 {
//nolint:unconvert
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,
BuildNumber: workspaceBuild.BuildNumber,
Name: workspaceBuild.Name,
Transition: codersdk.WorkspaceTransition(workspaceBuild.Transition),
InitiatorID: workspaceBuild.InitiatorID,
InitiatorUsername: initiatorName,
Job: convertProvisionerJob(job),
Deadline: workspaceBuild.Deadline,
}
Expand Down
11 changes: 7 additions & 4 deletions coderd/workspacebuilds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})

Expand Down
54 changes: 38 additions & 16 deletions coderd/workspaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.",
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand All @@ -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
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions codersdk/workspacebuilds.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down
1 change: 1 addition & 0 deletions site/src/api/typesGenerated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions site/src/testHelpers/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: "",
Expand Down