feat: Add initiator_username to workspace builds in apis (#2174)

* feat: Add initiator_username to workspace builds in apis
This commit is contained in:
Steven Masley 2022-06-08 20:23:35 -05:00 committed by GitHub
parent 14701498c9
commit 74fe38eb3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 96 additions and 30 deletions

View File

@ -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
}

View File

@ -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,
}

View File

@ -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)
})

View File

@ -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,

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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: "",