mirror of
https://github.com/coder/coder.git
synced 2025-07-21 01:28:49 +00:00
feat: Add workspace owner name to response (#1448)
This will be rendered in the workspace page!
This commit is contained in:
@ -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) {
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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 (
|
||||||
|
@ -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
|
||||||
*
|
*
|
||||||
|
@ -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.
|
||||||
|
@ -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,
|
||||||
|
@ -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"`
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
Reference in New Issue
Block a user