feat: Add workspace owner name to response (#1448)

This will be rendered in the workspace page!
This commit is contained in:
Kyle Carberry
2022-05-13 20:41:21 -05:00
committed by GitHub
parent 4cfc9af442
commit dbd5b4a47b
11 changed files with 116 additions and 20 deletions

View File

@ -4,11 +4,12 @@ import (
"context" "context"
"testing" "testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/clitest" "github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk" "github.com/coder/coder/codersdk"
"github.com/coder/coder/pty/ptytest" "github.com/coder/coder/pty/ptytest"
"github.com/stretchr/testify/require"
) )
func TestTemplateDelete(t *testing.T) { func TestTemplateDelete(t *testing.T) {

View File

@ -242,6 +242,22 @@ func (q *fakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams
return users, nil return users, nil
} }
func (q *fakeQuerier) GetUsersByIDs(_ context.Context, ids []uuid.UUID) ([]database.User, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
users := make([]database.User, 0)
for _, user := range q.users {
for _, id := range ids {
if user.ID.String() != id.String() {
continue
}
users = append(users, user)
}
}
return users, nil
}
func (q *fakeQuerier) GetAllUserRoles(_ context.Context, userID uuid.UUID) (database.GetAllUserRolesRow, error) { func (q *fakeQuerier) GetAllUserRoles(_ context.Context, userID uuid.UUID) (database.GetAllUserRolesRow, error) {
q.mutex.RLock() q.mutex.RLock()
defer q.mutex.RUnlock() defer q.mutex.RUnlock()

View File

@ -54,6 +54,7 @@ type querier interface {
GetUserByID(ctx context.Context, id uuid.UUID) (User, error) GetUserByID(ctx context.Context, id uuid.UUID) (User, error)
GetUserCount(ctx context.Context) (int64, error) GetUserCount(ctx context.Context) (int64, error)
GetUsers(ctx context.Context, arg GetUsersParams) ([]User, error) GetUsers(ctx context.Context, arg GetUsersParams) ([]User, error)
GetUsersByIDs(ctx context.Context, ids []uuid.UUID) ([]User, error)
GetWorkspaceAgentByAuthToken(ctx context.Context, authToken uuid.UUID) (WorkspaceAgent, error) GetWorkspaceAgentByAuthToken(ctx context.Context, authToken uuid.UUID) (WorkspaceAgent, error)
GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (WorkspaceAgent, error) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (WorkspaceAgent, error)
GetWorkspaceAgentByInstanceID(ctx context.Context, authInstanceID string) (WorkspaceAgent, error) GetWorkspaceAgentByInstanceID(ctx context.Context, authInstanceID string) (WorkspaceAgent, error)

View File

@ -2251,6 +2251,42 @@ func (q *sqlQuerier) GetUsers(ctx context.Context, arg GetUsersParams) ([]User,
return items, nil return items, nil
} }
const getUsersByIDs = `-- name: GetUsersByIDs :many
SELECT id, email, username, hashed_password, created_at, updated_at, status, rbac_roles FROM users WHERE id = ANY($1 :: uuid [ ])
`
func (q *sqlQuerier) GetUsersByIDs(ctx context.Context, ids []uuid.UUID) ([]User, error) {
rows, err := q.db.QueryContext(ctx, getUsersByIDs, pq.Array(ids))
if err != nil {
return nil, err
}
defer rows.Close()
var items []User
for rows.Next() {
var i User
if err := rows.Scan(
&i.ID,
&i.Email,
&i.Username,
&i.HashedPassword,
&i.CreatedAt,
&i.UpdatedAt,
&i.Status,
pq.Array(&i.RBACRoles),
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const insertUser = `-- name: InsertUser :one const insertUser = `-- name: InsertUser :one
INSERT INTO INSERT INTO
users ( users (

View File

@ -8,6 +8,9 @@ WHERE
LIMIT LIMIT
1; 1;
-- name: GetUsersByIDs :many
SELECT * FROM users WHERE id = ANY(@ids :: uuid [ ]);
-- name: GetUserByEmailOrUsername :one -- name: GetUserByEmailOrUsername :one
SELECT SELECT
* *

View File

@ -426,7 +426,7 @@ func (api *api) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
} }
httpapi.Write(rw, http.StatusOK, convertWorkspace(workspace, httpapi.Write(rw, http.StatusOK, convertWorkspace(workspace,
convertWorkspaceBuild(build, convertProvisionerJob(job)), template)) convertWorkspaceBuild(build, convertProvisionerJob(job)), template, owner))
} }
// Create a new workspace for the currently authenticated user. // Create a new workspace for the currently authenticated user.
@ -617,9 +617,16 @@ func (api *api) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
}) })
return return
} }
user, err := api.Database.GetUserByID(r.Context(), apiKey.UserID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get user: %s", err),
})
return
}
httpapi.Write(rw, http.StatusCreated, convertWorkspace(workspace, httpapi.Write(rw, http.StatusCreated, convertWorkspace(workspace,
convertWorkspaceBuild(workspaceBuild, convertProvisionerJob(templateVersionJob)), template)) convertWorkspaceBuild(workspaceBuild, convertProvisionerJob(templateVersionJob)), template, user))
} }
// convertOrganization consumes the database representation and outputs an API friendly representation. // convertOrganization consumes the database representation and outputs an API friendly representation.

View File

@ -11,6 +11,7 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/moby/moby/pkg/namesgenerator" "github.com/moby/moby/pkg/namesgenerator"
"golang.org/x/sync/errgroup"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/coder/coder/coderd/autobuild/schedule" "github.com/coder/coder/coderd/autobuild/schedule"
@ -30,21 +31,34 @@ func (api *api) workspace(rw http.ResponseWriter, r *http.Request) {
}) })
return return
} }
job, err := api.Database.GetProvisionerJobByID(r.Context(), build.JobID) var (
group errgroup.Group
job database.ProvisionerJob
template database.Template
owner database.User
)
group.Go(func() (err error) {
job, err = api.Database.GetProvisionerJobByID(r.Context(), build.JobID)
return err
})
group.Go(func() (err error) {
template, err = api.Database.GetTemplateByID(r.Context(), workspace.TemplateID)
return err
})
group.Go(func() (err error) {
owner, err = api.Database.GetUserByID(r.Context(), workspace.OwnerID)
return err
})
err = group.Wait()
if err != nil { if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get workspace build job: %s", err), Message: fmt.Sprintf("fetch resource: %s", err),
}) })
return return
} }
template, err := api.Database.GetTemplateByID(r.Context(), workspace.TemplateID)
if err != nil { httpapi.Write(rw, http.StatusOK,
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ convertWorkspace(workspace, convertWorkspaceBuild(build, convertProvisionerJob(job)), template, owner))
Message: fmt.Sprintf("get template: %s", err),
})
return
}
httpapi.Write(rw, http.StatusOK, convertWorkspace(workspace, convertWorkspaceBuild(build, convertProvisionerJob(job)), template))
} }
func (api *api) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { func (api *api) workspaceBuilds(rw http.ResponseWriter, r *http.Request) {
@ -359,9 +373,11 @@ func (api *api) putWorkspaceAutostop(rw http.ResponseWriter, r *http.Request) {
func convertWorkspaces(ctx context.Context, db database.Store, workspaces []database.Workspace) ([]codersdk.Workspace, error) { func convertWorkspaces(ctx context.Context, db database.Store, workspaces []database.Workspace) ([]codersdk.Workspace, error) {
workspaceIDs := make([]uuid.UUID, 0, len(workspaces)) workspaceIDs := make([]uuid.UUID, 0, len(workspaces))
templateIDs := make([]uuid.UUID, 0, len(workspaces)) templateIDs := make([]uuid.UUID, 0, len(workspaces))
ownerIDs := make([]uuid.UUID, 0, len(workspaces))
for _, workspace := range workspaces { for _, workspace := range workspaces {
workspaceIDs = append(workspaceIDs, workspace.ID) workspaceIDs = append(workspaceIDs, workspace.ID)
templateIDs = append(templateIDs, workspace.TemplateID) templateIDs = append(templateIDs, workspace.TemplateID)
ownerIDs = append(ownerIDs, workspace.OwnerID)
} }
workspaceBuilds, err := db.GetWorkspaceBuildsByWorkspaceIDsWithoutAfter(ctx, workspaceIDs) workspaceBuilds, err := db.GetWorkspaceBuildsByWorkspaceIDsWithoutAfter(ctx, workspaceIDs)
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
@ -377,6 +393,10 @@ func convertWorkspaces(ctx context.Context, db database.Store, workspaces []data
if err != nil { if err != nil {
return nil, xerrors.Errorf("get templates: %w", err) return nil, xerrors.Errorf("get templates: %w", err)
} }
users, err := db.GetUsersByIDs(ctx, ownerIDs)
if err != nil {
return nil, xerrors.Errorf("get users: %w", err)
}
jobIDs := make([]uuid.UUID, 0, len(workspaceBuilds)) jobIDs := make([]uuid.UUID, 0, len(workspaceBuilds))
for _, build := range workspaceBuilds { for _, build := range workspaceBuilds {
jobIDs = append(jobIDs, build.JobID) jobIDs = append(jobIDs, build.JobID)
@ -397,6 +417,10 @@ func convertWorkspaces(ctx context.Context, db database.Store, workspaces []data
for _, template := range templates { for _, template := range templates {
templateByID[template.ID] = template templateByID[template.ID] = template
} }
userByID := map[uuid.UUID]database.User{}
for _, user := range users {
userByID[user.ID] = user
}
jobByID := map[uuid.UUID]database.ProvisionerJob{} jobByID := map[uuid.UUID]database.ProvisionerJob{}
for _, job := range jobs { for _, job := range jobs {
jobByID[job.ID] = job jobByID[job.ID] = job
@ -413,20 +437,25 @@ func convertWorkspaces(ctx context.Context, db database.Store, workspaces []data
} }
job, exists := jobByID[build.JobID] job, exists := jobByID[build.JobID]
if !exists { if !exists {
return nil, xerrors.Errorf("build job not found for workspace: %q", err) return nil, xerrors.Errorf("build job not found for workspace: %w", err)
}
user, exists := userByID[workspace.OwnerID]
if !exists {
return nil, xerrors.Errorf("owner not found for workspace: %q", workspace.Name)
} }
apiWorkspaces = append(apiWorkspaces, apiWorkspaces = append(apiWorkspaces,
convertWorkspace(workspace, convertWorkspaceBuild(build, convertProvisionerJob(job)), template)) convertWorkspace(workspace, convertWorkspaceBuild(build, convertProvisionerJob(job)), template, user))
} }
return apiWorkspaces, nil return apiWorkspaces, nil
} }
func convertWorkspace(workspace database.Workspace, workspaceBuild codersdk.WorkspaceBuild, template database.Template) codersdk.Workspace { func convertWorkspace(workspace database.Workspace, workspaceBuild codersdk.WorkspaceBuild, template database.Template, owner database.User) codersdk.Workspace {
return codersdk.Workspace{ return codersdk.Workspace{
ID: workspace.ID, ID: workspace.ID,
CreatedAt: workspace.CreatedAt, CreatedAt: workspace.CreatedAt,
UpdatedAt: workspace.UpdatedAt, UpdatedAt: workspace.UpdatedAt,
OwnerID: workspace.OwnerID, OwnerID: workspace.OwnerID,
OwnerName: owner.Username,
TemplateID: workspace.TemplateID, TemplateID: workspace.TemplateID,
LatestBuild: workspaceBuild, LatestBuild: workspaceBuild,
TemplateName: template.Name, TemplateName: template.Name,

View File

@ -20,6 +20,7 @@ type Workspace struct {
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
OwnerID uuid.UUID `json:"owner_id"` OwnerID uuid.UUID `json:"owner_id"`
OwnerName string `json:"owner_name"`
TemplateID uuid.UUID `json:"template_id"` TemplateID uuid.UUID `json:"template_id"`
TemplateName string `json:"template_name"` TemplateName string `json:"template_name"`
LatestBuild WorkspaceBuild `json:"latest_build"` LatestBuild WorkspaceBuild `json:"latest_build"`

View File

@ -85,7 +85,7 @@ export interface CreateUserRequest {
readonly organization_id: string readonly organization_id: string
} }
// From codersdk/workspaces.go:33:6 // From codersdk/workspaces.go:34:6
export interface CreateWorkspaceBuildRequest { export interface CreateWorkspaceBuildRequest {
readonly template_version_id?: string readonly template_version_id?: string
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.WorkspaceTransition") // This is likely an enum in an external package ("github.com/coder/coder/coderd/database.WorkspaceTransition")
@ -289,12 +289,12 @@ export interface UpdateUserProfileRequest {
readonly username: string readonly username: string
} }
// From codersdk/workspaces.go:95:6 // From codersdk/workspaces.go:96:6
export interface UpdateWorkspaceAutostartRequest { export interface UpdateWorkspaceAutostartRequest {
readonly schedule: string readonly schedule: string
} }
// From codersdk/workspaces.go:115:6 // From codersdk/workspaces.go:116:6
export interface UpdateWorkspaceAutostopRequest { export interface UpdateWorkspaceAutostopRequest {
readonly schedule: string readonly schedule: string
} }
@ -355,6 +355,7 @@ export interface Workspace {
readonly created_at: string readonly created_at: string
readonly updated_at: string readonly updated_at: string
readonly owner_id: string readonly owner_id: string
readonly owner_name: string
readonly template_id: string readonly template_id: string
readonly template_name: string readonly template_name: string
readonly latest_build: WorkspaceBuild readonly latest_build: WorkspaceBuild

View File

@ -124,6 +124,7 @@ export const MockWorkspace: TypesGen.Workspace = {
template_name: MockTemplate.name, template_name: MockTemplate.name,
outdated: false, outdated: false,
owner_id: MockUser.id, owner_id: MockUser.id,
owner_name: MockUser.username,
autostart_schedule: MockWorkspaceAutostartEnabled.schedule, autostart_schedule: MockWorkspaceAutostartEnabled.schedule,
autostop_schedule: MockWorkspaceAutostopEnabled.schedule, autostop_schedule: MockWorkspaceAutostopEnabled.schedule,
latest_build: MockWorkspaceBuild, latest_build: MockWorkspaceBuild,