-
Notifications
You must be signed in to change notification settings - Fork 894
feat: edit workspace schedule page #1701
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
58ecefa
feat: edit workspace schedule page
greyscaled 3f73a6c
fixup! feat: edit workspace schedule page
greyscaled 30dff81
Merge branch 'main' into vapurrmaid/gh-1455/part-3/page
greyscaled ecc6792
remove promise
greyscaled c0f28c3
Merge origin/main
greyscaled d61a332
refactor to map + add loading/disabled
greyscaled f60f59b
time validation
greyscaled 406d465
more tests
greyscaled a6dff9d
Update site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx
greyscaled 7a14859
fix routing
greyscaled 1158645
handle formErrors
greyscaled 7a050db
finalize machine
greyscaled 1166924
add timezone
greyscaled 947b4a0
switch to TTL (hours)
greyscaled 4764e5c
adjust ttl
greyscaled ebc4965
initialization
greyscaled 747b52f
fixup! initialization
greyscaled 854f781
fixup! initialization
greyscaled 8bedc8a
Merge origin/main
greyscaled 6afbb74
improve error message
greyscaled 5bacfb1
Apply suggestions from code review
greyscaled 5b3adc3
Update site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tes…
greyscaled 81e7e05
fix ttl initialization
greyscaled 531df3e
Update site/src/util/schedule.test.ts
greyscaled 7ae590a
Fix typo
greyscaled 262d9e3
import ReactNode directly
greyscaled 5d22197
guess timezone
greyscaled eda8ad8
fix test
greyscaled f59f056
lint
greyscaled File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next
Next commit
feat: edit workspace schedule page
- Loading branch information
commit 58ecefa121312f085bb60a11dc77c9b306c058b6
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { useMachine } from "@xstate/react" | ||
import React, { useEffect } from "react" | ||
import { useNavigate, useParams } from "react-router-dom" | ||
import * as TypesGen from "../../api/typesGenerated" | ||
import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" | ||
import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" | ||
import { | ||
WorkspaceScheduleForm, | ||
WorkspaceScheduleFormValues, | ||
} from "../../components/WorkspaceStats/WorkspaceScheduleForm" | ||
import { firstOrItem } from "../../util/array" | ||
import { workspaceSchedule } from "../../xServices/workspaceSchedule/workspaceScheduleXService" | ||
|
||
// TODO(Grey): Test before opening PR from draft | ||
export const formValuesToAutoStartRequest = ( | ||
values: WorkspaceScheduleFormValues, | ||
): TypesGen.UpdateWorkspaceAutostartRequest => { | ||
if (!values.startTime) { | ||
return { | ||
schedule: "", | ||
} | ||
} | ||
|
||
// TODO(Grey): Fill in | ||
return { | ||
schedule: "9 30 * * 1-5", | ||
} | ||
} | ||
|
||
export const formValuesToTTLRequest = (values: WorkspaceScheduleFormValues): TypesGen.UpdateWorkspaceTTLRequest => { | ||
if (!values.ttl) { | ||
return { | ||
ttl: 0, // TODO(Grey): Verify with Cian whether 0 or null is better to send | ||
} | ||
} | ||
|
||
// TODO(Grey): Fill in | ||
return { | ||
ttl: 0, | ||
} | ||
} | ||
|
||
// TODO(Grey): React testing library for this | ||
export const WorkspaceSchedulePage: React.FC = () => { | ||
const navigate = useNavigate() | ||
const { workspace: workspaceQueryParam } = useParams() | ||
const workspaceId = firstOrItem(workspaceQueryParam, null) | ||
|
||
// TODO(Grey): Consume the formSubmissionErrors in WorkspaceScheduleForm | ||
const [scheduleState, scheduleSend] = useMachine(workspaceSchedule) | ||
const { getWorkspaceError, workspace } = scheduleState.context | ||
|
||
/** | ||
* Get workspace on mount and whenever workspaceId changes (scheduleSend | ||
* should not change). | ||
*/ | ||
useEffect(() => { | ||
workspaceId && scheduleSend({ type: "GET_WORKSPACE", workspaceId }) | ||
}, [workspaceId, scheduleSend]) | ||
|
||
if (!workspaceId) { | ||
navigate("/workspaces") | ||
return null | ||
} else if (scheduleState.matches("error")) { | ||
return <ErrorSummary error={getWorkspaceError} retry={() => scheduleSend({ type: "GET_WORKSPACE", workspaceId })} /> | ||
} else if (!workspace) { | ||
return <FullScreenLoader /> | ||
} else { | ||
return ( | ||
<WorkspaceScheduleForm | ||
onCancel={() => { | ||
navigate(`/workspaces/${workspaceId}`) | ||
}} | ||
onSubmit={(values) => { | ||
scheduleSend({ | ||
type: "SUBMIT_SCHEDULE", | ||
autoStart: formValuesToAutoStartRequest(values), | ||
ttl: formValuesToTTLRequest(values), | ||
}) | ||
|
||
// TODO(Grey): Remove this after onSubmit is un-promisified | ||
// TODO(Grey): navigation logic | ||
return Promise.resolve() | ||
}} | ||
/> | ||
) | ||
} | ||
} |
146 changes: 146 additions & 0 deletions
146
site/src/xServices/workspaceSchedule/workspaceScheduleXService.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
/** | ||
* @fileoverview workspaceSchedule is an xstate machine backing a form to CRUD | ||
* an individual workspace's schedule. | ||
*/ | ||
Comment on lines
+1
to
+4
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was so helpful; thank you! |
||
import { assign, createMachine } from "xstate" | ||
import * as API from "../../api/api" | ||
import { ApiError, FieldErrors, mapApiErrorToFieldErrors } from "../../api/errors" | ||
import * as TypesGen from "../../api/typesGenerated" | ||
import { displayError, displaySuccess } from "../../components/GlobalSnackbar/utils" | ||
|
||
export const Language = { | ||
errorSubmissionFailed: "Failed to update schedule", | ||
errorWorkspaceFetch: "Failed to fetch workspace", | ||
successMessage: "Successfully updated workspace schedule.", | ||
} | ||
|
||
export interface WorkspaceScheduleContext { | ||
formErrors?: FieldErrors | ||
getWorkspaceError?: Error | unknown | ||
/** | ||
* Each workspace has their own schedule (start and ttl). For this reason, we | ||
* re-fetch the workspace to ensure we're up-to-date. As a result, this | ||
* machine is partially influenced by workspaceXService. | ||
*/ | ||
workspace?: TypesGen.Workspace | ||
} | ||
|
||
export type WorkspaceScheduleEvent = | ||
| { type: "GET_WORKSPACE"; workspaceId: string } | ||
| { | ||
type: "SUBMIT_SCHEDULE" | ||
autoStart: TypesGen.UpdateWorkspaceAutostartRequest | ||
ttl: TypesGen.UpdateWorkspaceTTLRequest | ||
} | ||
|
||
export const workspaceSchedule = createMachine( | ||
{ | ||
tsTypes: {} as import("./workspaceScheduleXService.typegen").Typegen0, | ||
schema: { | ||
context: {} as WorkspaceScheduleContext, | ||
events: {} as WorkspaceScheduleEvent, | ||
services: {} as { | ||
getWorkspace: { | ||
data: TypesGen.Workspace | ||
} | ||
}, | ||
}, | ||
id: "workspaceScheduleState", | ||
initial: "idle", | ||
on: { | ||
GET_WORKSPACE: "gettingWorkspace", | ||
}, | ||
states: { | ||
idle: { | ||
tags: "loading", | ||
}, | ||
gettingWorkspace: { | ||
entry: ["clearGetWorkspaceError", "clearContext"], | ||
invoke: { | ||
src: "getWorkspace", | ||
id: "getWorkspace", | ||
onDone: { | ||
target: "presentForm", | ||
actions: ["assignWorkspace"], | ||
}, | ||
onError: { | ||
target: "error", | ||
actions: ["assignGetWorkspaceError", "displayWorkspaceError"], | ||
}, | ||
}, | ||
tags: "loading", | ||
}, | ||
presentForm: { | ||
on: { | ||
SUBMIT_SCHEDULE: "submittingSchedule", | ||
}, | ||
}, | ||
submittingSchedule: { | ||
invoke: { | ||
src: "submitSchedule", | ||
id: "submitSchedule", | ||
onDone: { | ||
target: "idle", | ||
actions: "displaySuccess", | ||
}, | ||
onError: { | ||
target: "presentForm", | ||
actions: ["assignSubmissionError", "displaySubmissionError"], | ||
}, | ||
}, | ||
tags: "loading", | ||
}, | ||
error: { | ||
on: { | ||
GET_WORKSPACE: "gettingWorkspace", | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
actions: { | ||
assignSubmissionError: assign({ | ||
formErrors: (_, event) => mapApiErrorToFieldErrors((event.data as ApiError).response.data), | ||
}), | ||
assignWorkspace: assign({ | ||
workspace: (_, event) => event.data, | ||
}), | ||
assignGetWorkspaceError: assign({ | ||
getWorkspaceError: (_, event) => event.data, | ||
}), | ||
clearContext: () => { | ||
assign({ workspace: undefined }) | ||
}, | ||
clearGetWorkspaceError: (context) => { | ||
assign({ ...context, getWorkspaceError: undefined }) | ||
}, | ||
displayWorkspaceError: () => { | ||
displayError(Language.errorWorkspaceFetch) | ||
}, | ||
displaySubmissionError: () => { | ||
displayError(Language.errorSubmissionFailed) | ||
}, | ||
displaySuccess: () => { | ||
displaySuccess(Language.successMessage) | ||
}, | ||
}, | ||
|
||
services: { | ||
getWorkspace: async (_, event) => { | ||
return await API.getWorkspace(event.workspaceId) | ||
}, | ||
submitSchedule: async (context, event) => { | ||
if (!context.workspace?.id) { | ||
// This state is theoretically impossible, but helps TS | ||
throw new Error("failed to load workspace") | ||
} | ||
|
||
// REMARK: These calls are purposefully synchronous because if one | ||
// value contradicts the other, we don't want a race condition | ||
// on re-submission. | ||
await API.putWorkspaceAutostart(context.workspace.id, event.autoStart) | ||
await API.putWorkspaceAutostop(context.workspace.id, event.ttl) | ||
greyscaled marked this conversation as resolved.
Show resolved
Hide resolved
greyscaled marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
}, | ||
}, | ||
) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.