mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: Update routing for workspace schedule (#2113)
* fix: Update routing for workspace schedule This was broken as part of #2101. It was a silly mistake, but unfortunate our tests didn't catch it. This is a rare change so unlikely to occur again, so I won't make an issue adding tests. * Update site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx Co-authored-by: Presley Pizzo <1290996+presleyp@users.noreply.github.com> Co-authored-by: Presley Pizzo <1290996+presleyp@users.noreply.github.com>
This commit is contained in:
co-authored by
Presley Pizzo
parent
a4e259e14b
commit
7e8692b0fd
+56
-18
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -807,7 +807,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,
|
||||
|
||||
+14
-12
@@ -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.
|
||||
|
||||
@@ -440,6 +440,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
|
||||
|
||||
@@ -22,7 +22,7 @@ export const WorkspaceBuildStats: FC<WorkspaceBuildStatsProps> = ({ build }) =>
|
||||
<span className={styles.statsLabel}>Workspace Name</span>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/workspaces/${build.workspace_id}`}
|
||||
to={`/@${build.workspace_owner_name}/${build.workspace_name}`}
|
||||
className={combineClasses([styles.statsValue, styles.link])}
|
||||
>
|
||||
{build.workspace_name}
|
||||
|
||||
@@ -92,7 +92,11 @@ export const WorkspaceSchedule: FC<WorkspaceScheduleProps> = ({ workspace }) =>
|
||||
<span className={styles.scheduleValue}>{Language.autoStopDisplay(workspace)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<Link className={styles.scheduleAction} component={RouterLink} to={`/workspaces/${workspace.id}/schedule`}>
|
||||
<Link
|
||||
className={styles.scheduleAction}
|
||||
component={RouterLink}
|
||||
to={`/@${workspace.owner_name}/${workspace.name}/schedule`}
|
||||
>
|
||||
{Language.editScheduleLink}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -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}`)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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 <FullScreenLoader />
|
||||
} else if (scheduleState.matches("error")) {
|
||||
return <ErrorSummary error={getWorkspaceError} retry={() => scheduleSend({ type: "GET_WORKSPACE", workspaceId })} />
|
||||
return (
|
||||
<ErrorSummary
|
||||
error={getWorkspaceError}
|
||||
retry={() => scheduleSend({ type: "GET_WORKSPACE", username, workspaceName })}
|
||||
/>
|
||||
)
|
||||
} else if (scheduleState.matches("presentForm") || scheduleState.matches("submittingSchedule")) {
|
||||
return (
|
||||
<WorkspaceScheduleForm
|
||||
@@ -168,7 +174,7 @@ export const WorkspaceSchedulePage: React.FC = () => {
|
||||
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 <FullScreenLoader />
|
||||
} else {
|
||||
// Theoretically impossible - log and bail
|
||||
|
||||
@@ -137,6 +137,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",
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+1
-1
@@ -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,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user