diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index 8877af73ac631..1d829409dabc2 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -37,7 +37,16 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(rw, http.StatusOK, convertWorkspaceBuild(workspace, workspaceBuild, job)) + owner, err := api.Database.GetUserByID(r.Context(), workspace.OwnerID) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: "Internal error fetching user", + Detail: err.Error(), + }) + return + } + + httpapi.Write(rw, http.StatusOK, convertWorkspaceBuild(owner, workspace, workspaceBuild, job)) } func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { @@ -100,8 +109,8 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { } jobIDs := make([]uuid.UUID, 0, len(builds)) - for _, version := range builds { - jobIDs = append(jobIDs, version.JobID) + for _, build := range builds { + jobIDs = append(jobIDs, build.JobID) } jobs, err := api.Database.GetProvisionerJobsByIDs(r.Context(), jobIDs) if errors.Is(err, sql.ErrNoRows) { @@ -119,6 +128,15 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { jobByID[job.ID.String()] = job } + owner, err := api.Database.GetUserByID(r.Context(), workspace.OwnerID) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: "Internal error fetching user", + Detail: err.Error(), + }) + return + } + apiBuilds := make([]codersdk.WorkspaceBuild, 0) for _, build := range builds { job, exists := jobByID[build.JobID.String()] @@ -128,7 +146,7 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { }) return } - apiBuilds = append(apiBuilds, convertWorkspaceBuild(workspace, build, job)) + apiBuilds = append(apiBuilds, convertWorkspaceBuild(owner, workspace, build, job)) } httpapi.Write(rw, http.StatusOK, apiBuilds) @@ -167,8 +185,16 @@ func (api *API) workspaceBuildByName(rw http.ResponseWriter, r *http.Request) { }) return } + owner, err := api.Database.GetUserByID(r.Context(), workspace.OwnerID) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: "Internal error getting user", + Detail: err.Error(), + }) + return + } - httpapi.Write(rw, http.StatusOK, convertWorkspaceBuild(workspace, workspaceBuild, job)) + httpapi.Write(rw, http.StatusOK, convertWorkspaceBuild(owner, workspace, workspaceBuild, job)) } func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { @@ -342,8 +368,17 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { return } + owner, err := api.Database.GetUserByID(r.Context(), workspace.OwnerID) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: "Internal error getting user", + Detail: err.Error(), + }) + return + } + httpapi.Write(rw, http.StatusCreated, - convertWorkspaceBuild(workspace, workspaceBuild, provisionerJob)) + convertWorkspaceBuild(owner, workspace, workspaceBuild, provisionerJob)) } func (api *API) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Request) { @@ -473,6 +508,7 @@ func (api *API) workspaceBuildState(rw http.ResponseWriter, r *http.Request) { } func convertWorkspaceBuild( + workspaceOwner database.User, workspace database.Workspace, workspaceBuild database.WorkspaceBuild, job database.ProvisionerJob) codersdk.WorkspaceBuild { @@ -481,18 +517,20 @@ func convertWorkspaceBuild( panic("workspace and build do not match") } return codersdk.WorkspaceBuild{ - ID: workspaceBuild.ID, - CreatedAt: workspaceBuild.CreatedAt, - UpdatedAt: workspaceBuild.UpdatedAt, - WorkspaceID: workspaceBuild.WorkspaceID, - WorkspaceName: workspace.Name, - TemplateVersionID: workspaceBuild.TemplateVersionID, - BuildNumber: workspaceBuild.BuildNumber, - Name: workspaceBuild.Name, - Transition: codersdk.WorkspaceTransition(workspaceBuild.Transition), - InitiatorID: workspaceBuild.InitiatorID, - Job: convertProvisionerJob(job), - Deadline: workspaceBuild.Deadline, + ID: workspaceBuild.ID, + CreatedAt: workspaceBuild.CreatedAt, + UpdatedAt: workspaceBuild.UpdatedAt, + WorkspaceOwnerID: workspace.OwnerID, + WorkspaceOwnerName: workspaceOwner.Username, + WorkspaceID: workspaceBuild.WorkspaceID, + WorkspaceName: workspace.Name, + TemplateVersionID: workspaceBuild.TemplateVersionID, + BuildNumber: workspaceBuild.BuildNumber, + Name: workspaceBuild.Name, + Transition: codersdk.WorkspaceTransition(workspaceBuild.Transition), + InitiatorID: workspaceBuild.InitiatorID, + Job: convertProvisionerJob(job), + Deadline: workspaceBuild.Deadline, } } diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 23c142bcdf873..115146598afb5 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -796,7 +796,7 @@ func convertWorkspace( OwnerID: workspace.OwnerID, OwnerName: owner.Username, TemplateID: workspace.TemplateID, - LatestBuild: convertWorkspaceBuild(workspace, workspaceBuild, job), + LatestBuild: convertWorkspaceBuild(owner, 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 3216c4a62a439..ccf3e917f5d63 100644 --- a/codersdk/workspacebuilds.go +++ b/codersdk/workspacebuilds.go @@ -22,18 +22,20 @@ const ( // WorkspaceBuild is an at-point representation of a workspace state. // BuildNumbers start at 1 and increase by 1 for each subsequent build type WorkspaceBuild struct { - ID uuid.UUID `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - WorkspaceID uuid.UUID `json:"workspace_id"` - WorkspaceName string `json:"workspace_name"` - TemplateVersionID uuid.UUID `json:"template_version_id"` - BuildNumber int32 `json:"build_number"` - Name string `json:"name"` - Transition WorkspaceTransition `json:"transition"` - InitiatorID uuid.UUID `json:"initiator_id"` - Job ProvisionerJob `json:"job"` - Deadline time.Time `json:"deadline"` + ID uuid.UUID `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + WorkspaceID uuid.UUID `json:"workspace_id"` + WorkspaceName string `json:"workspace_name"` + WorkspaceOwnerID uuid.UUID `json:"workspace_owner_id"` + WorkspaceOwnerName string `json:"workspace_owner_name"` + TemplateVersionID uuid.UUID `json:"template_version_id"` + BuildNumber int32 `json:"build_number"` + Name string `json:"name"` + Transition WorkspaceTransition `json:"transition"` + InitiatorID uuid.UUID `json:"initiator_id"` + Job ProvisionerJob `json:"job"` + Deadline time.Time `json:"deadline"` } // WorkspaceBuild returns a single workspace build for a workspace. diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 8e71d52a3906c..e71337dc786d6 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -436,6 +436,8 @@ export interface WorkspaceBuild { readonly updated_at: string readonly workspace_id: string readonly workspace_name: string + readonly workspace_owner_id: string + readonly workspace_owner_name: string readonly template_version_id: string readonly build_number: number readonly name: string diff --git a/site/src/components/WorkspaceBuildStats/WorkspaceBuildStats.tsx b/site/src/components/WorkspaceBuildStats/WorkspaceBuildStats.tsx index 5087e4a6cb476..92152cd283209 100644 --- a/site/src/components/WorkspaceBuildStats/WorkspaceBuildStats.tsx +++ b/site/src/components/WorkspaceBuildStats/WorkspaceBuildStats.tsx @@ -22,7 +22,7 @@ export const WorkspaceBuildStats: FC = ({ build }) => Workspace Name {build.workspace_name} diff --git a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx index ccb103f15f85e..8ea1cc01c67c9 100644 --- a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx +++ b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx @@ -92,7 +92,11 @@ export const WorkspaceSchedule: FC = ({ workspace }) => {Language.autoStopDisplay(workspace)}
- + {Language.editScheduleLink}
diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx index 9749a75413adf..fec4684d4706e 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx @@ -17,7 +17,7 @@ const CreateWorkspacePage: FC = () => { context: { organizationId, preSelectedTemplateName }, actions: { onCreateWorkspace: (_, event) => { - navigate("/workspaces/" + event.data.id) + navigate(`/@${event.data.owner_name}/${event.data.name}`) }, }, }) diff --git a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx index 0aef9da61c823..00af835a415a8 100644 --- a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx +++ b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx @@ -142,25 +142,31 @@ export const workspaceToInitialValues = ( } export const WorkspaceSchedulePage: React.FC = () => { + const { username: usernameQueryParam, workspace: workspaceQueryParam } = useParams() const navigate = useNavigate() - const { workspace: workspaceQueryParam } = useParams() - const workspaceId = firstOrItem(workspaceQueryParam, null) + const username = firstOrItem(usernameQueryParam, null) + const workspaceName = firstOrItem(workspaceQueryParam, null) const [scheduleState, scheduleSend] = useMachine(workspaceSchedule) const { formErrors, getWorkspaceError, workspace } = scheduleState.context - // Get workspace on mount and whenever workspaceId changes. + // Get workspace on mount and whenever the args for getting a workspace change. // scheduleSend should not change. useEffect(() => { - workspaceId && scheduleSend({ type: "GET_WORKSPACE", workspaceId }) - }, [workspaceId, scheduleSend]) + username && workspaceName && scheduleSend({ type: "GET_WORKSPACE", username, workspaceName }) + }, [username, workspaceName, scheduleSend]) - if (!workspaceId) { + if (!username || !workspaceName) { navigate("/workspaces") return null } else if (scheduleState.matches("idle") || scheduleState.matches("gettingWorkspace") || !workspace) { return } else if (scheduleState.matches("error")) { - return scheduleSend({ type: "GET_WORKSPACE", workspaceId })} /> + return ( + scheduleSend({ type: "GET_WORKSPACE", username, workspaceName })} + /> + ) } else if (scheduleState.matches("presentForm") || scheduleState.matches("submittingSchedule")) { return ( { initialValues={workspaceToInitialValues(workspace, dayjs.tz.guess())} isLoading={scheduleState.tags.has("loading")} onCancel={() => { - navigate(`/workspaces/${workspaceId}`) + navigate(`/@${username}/${workspaceName}`) }} onSubmit={(values) => { scheduleSend({ @@ -180,7 +186,7 @@ export const WorkspaceSchedulePage: React.FC = () => { /> ) } else if (scheduleState.matches("submitSuccess")) { - navigate(`/workspaces/${workspaceId}`) + navigate(`/@${username}/${workspaceName}`) return } else { // Theoretically impossible - log and bail diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 2c06cb214020d..b277278929e35 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -135,6 +135,8 @@ export const MockWorkspaceBuild: TypesGen.WorkspaceBuild = { transition: "start", updated_at: "2022-05-17T17:39:01.382927298Z", workspace_name: "test-workspace", + workspace_owner_id: MockUser.id, + workspace_owner_name: MockUser.username, workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3", deadline: "2022-05-17T23:39:00.00Z", } diff --git a/site/src/xServices/workspaceSchedule/workspaceScheduleXService.ts b/site/src/xServices/workspaceSchedule/workspaceScheduleXService.ts index a1ddb58254366..64f6ca3af37b0 100644 --- a/site/src/xServices/workspaceSchedule/workspaceScheduleXService.ts +++ b/site/src/xServices/workspaceSchedule/workspaceScheduleXService.ts @@ -26,7 +26,7 @@ export interface WorkspaceScheduleContext { } export type WorkspaceScheduleEvent = - | { type: "GET_WORKSPACE"; workspaceId: string } + | { type: "GET_WORKSPACE"; username: string; workspaceName: string } | { type: "SUBMIT_SCHEDULE" autoStart: TypesGen.UpdateWorkspaceAutostartRequest @@ -132,7 +132,7 @@ export const workspaceSchedule = createMachine( services: { getWorkspace: async (_, event) => { - return await API.getWorkspace(event.workspaceId) + return await API.getWorkspaceByOwnerAndName(event.username, event.workspaceName) }, submitSchedule: async (context, event) => { if (!context.workspace?.id) { diff --git a/site/webpack.dev.ts b/site/webpack.dev.ts index aa66120ed516c..c6225f9afe795 100644 --- a/site/webpack.dev.ts +++ b/site/webpack.dev.ts @@ -63,7 +63,7 @@ const config: Configuration = { port: process.env.PORT || 8080, proxy: { "/api": { - target: "https://dev.coder.com", + target: process.env.CODER_HOST || "http://localhost:3000", ws: true, secure: false, },