mirror of
https://github.com/coder/coder.git
synced 2025-07-18 14:17:22 +00:00
feat: ws schedule timezone select (#2032)
Resolves: #1959 Summary: The package tzdata is used to create a meaningful select-list for timezone in the workspace schedule form. Impact: Improved UX. Furthermore, we guess your timezone if the form is being initialized from scratch.
This commit is contained in:
@ -48,6 +48,7 @@
|
|||||||
"react-router-dom": "6.3.0",
|
"react-router-dom": "6.3.0",
|
||||||
"sourcemapped-stacktrace": "1.1.11",
|
"sourcemapped-stacktrace": "1.1.11",
|
||||||
"swr": "1.2.2",
|
"swr": "1.2.2",
|
||||||
|
"tzdata": "1.0.30",
|
||||||
"uuid": "8.3.2",
|
"uuid": "8.3.2",
|
||||||
"xstate": "4.32.1",
|
"xstate": "4.32.1",
|
||||||
"xterm": "4.18.0",
|
"xterm": "4.18.0",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Language, validationSchema, WorkspaceScheduleFormValues } from "./WorkspaceScheduleForm"
|
import { Language, validationSchema, WorkspaceScheduleFormValues } from "./WorkspaceScheduleForm"
|
||||||
|
import { zones } from "./zones"
|
||||||
|
|
||||||
const valid: WorkspaceScheduleFormValues = {
|
const valid: WorkspaceScheduleFormValues = {
|
||||||
sunday: false,
|
sunday: false,
|
||||||
@ -127,6 +128,15 @@ describe("validationSchema", () => {
|
|||||||
expect(validate).toThrowError(Language.errorTimezone)
|
expect(validate).toThrowError(Language.errorTimezone)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it.each<[string]>(zones.map((zone) => [zone]))(`validation passes for tz=%p`, (zone) => {
|
||||||
|
const values: WorkspaceScheduleFormValues = {
|
||||||
|
...valid,
|
||||||
|
timezone: zone,
|
||||||
|
}
|
||||||
|
const validate = () => validationSchema.validateSync(values)
|
||||||
|
expect(validate).not.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
it("allows a ttl of 7 days", () => {
|
it("allows a ttl of 7 days", () => {
|
||||||
const values: WorkspaceScheduleFormValues = {
|
const values: WorkspaceScheduleFormValues = {
|
||||||
...valid,
|
...valid,
|
||||||
|
@ -4,7 +4,7 @@ import FormControlLabel from "@material-ui/core/FormControlLabel"
|
|||||||
import FormGroup from "@material-ui/core/FormGroup"
|
import FormGroup from "@material-ui/core/FormGroup"
|
||||||
import FormHelperText from "@material-ui/core/FormHelperText"
|
import FormHelperText from "@material-ui/core/FormHelperText"
|
||||||
import FormLabel from "@material-ui/core/FormLabel"
|
import FormLabel from "@material-ui/core/FormLabel"
|
||||||
import Link from "@material-ui/core/Link"
|
import MenuItem from "@material-ui/core/MenuItem"
|
||||||
import makeStyles from "@material-ui/core/styles/makeStyles"
|
import makeStyles from "@material-ui/core/styles/makeStyles"
|
||||||
import TextField from "@material-ui/core/TextField"
|
import TextField from "@material-ui/core/TextField"
|
||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
@ -18,6 +18,7 @@ import { getFormHelpers } from "../../util/formUtils"
|
|||||||
import { FormFooter } from "../FormFooter/FormFooter"
|
import { FormFooter } from "../FormFooter/FormFooter"
|
||||||
import { FullPageForm } from "../FullPageForm/FullPageForm"
|
import { FullPageForm } from "../FullPageForm/FullPageForm"
|
||||||
import { Stack } from "../Stack/Stack"
|
import { Stack } from "../Stack/Stack"
|
||||||
|
import { zones } from "./zones"
|
||||||
|
|
||||||
// REMARK: timezone plugin depends on UTC
|
// REMARK: timezone plugin depends on UTC
|
||||||
//
|
//
|
||||||
@ -203,21 +204,20 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
{...formHelpers(
|
{...formHelpers("timezone")}
|
||||||
"timezone",
|
|
||||||
<>
|
|
||||||
Timezone must be a valid name from the{" "}
|
|
||||||
<Link href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List" target="_blank">
|
|
||||||
timezone database
|
|
||||||
</Link>
|
|
||||||
</>,
|
|
||||||
)}
|
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
}}
|
}}
|
||||||
label={Language.timezoneLabel}
|
label={Language.timezoneLabel}
|
||||||
/>
|
select
|
||||||
|
>
|
||||||
|
{zones.map((zone) => (
|
||||||
|
<MenuItem key={zone} value={zone}>
|
||||||
|
{zone}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
<FormControl component="fieldset" error={Boolean(form.errors.monday)}>
|
<FormControl component="fieldset" error={Boolean(form.errors.monday)}>
|
||||||
<FormLabel className={styles.daysOfWeekLabel} component="legend">
|
<FormLabel className={styles.daysOfWeekLabel} component="legend">
|
||||||
|
3
site/src/components/WorkspaceScheduleForm/zones.ts
Normal file
3
site/src/components/WorkspaceScheduleForm/zones.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import tzData from "tzdata"
|
||||||
|
|
||||||
|
export const zones: string[] = Object.keys(tzData.zones)
|
@ -92,7 +92,10 @@ export const formValuesToTTLRequest = (values: WorkspaceScheduleFormValues): Typ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const workspaceToInitialValues = (workspace: TypesGen.Workspace): WorkspaceScheduleFormValues => {
|
export const workspaceToInitialValues = (
|
||||||
|
workspace: TypesGen.Workspace,
|
||||||
|
defaultTimeZone = "",
|
||||||
|
): WorkspaceScheduleFormValues => {
|
||||||
const schedule = workspace.autostart_schedule
|
const schedule = workspace.autostart_schedule
|
||||||
const ttlHours = workspace.ttl_ms ? Math.round(workspace.ttl_ms / (1000 * 60 * 60)) : 0
|
const ttlHours = workspace.ttl_ms ? Math.round(workspace.ttl_ms / (1000 * 60 * 60)) : 0
|
||||||
|
|
||||||
@ -106,12 +109,12 @@ export const workspaceToInitialValues = (workspace: TypesGen.Workspace): Workspa
|
|||||||
friday: false,
|
friday: false,
|
||||||
saturday: false,
|
saturday: false,
|
||||||
startTime: "",
|
startTime: "",
|
||||||
timezone: "",
|
timezone: defaultTimeZone,
|
||||||
ttl: ttlHours,
|
ttl: ttlHours,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const timezone = extractTimezone(schedule, dayjs.tz.guess())
|
const timezone = extractTimezone(schedule, defaultTimeZone)
|
||||||
|
|
||||||
const expression = cronParser.parseExpression(stripTimezone(schedule))
|
const expression = cronParser.parseExpression(stripTimezone(schedule))
|
||||||
|
|
||||||
@ -162,7 +165,7 @@ export const WorkspaceSchedulePage: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<WorkspaceScheduleForm
|
<WorkspaceScheduleForm
|
||||||
fieldErrors={formErrors}
|
fieldErrors={formErrors}
|
||||||
initialValues={workspaceToInitialValues(workspace)}
|
initialValues={workspaceToInitialValues(workspace, dayjs.tz.guess())}
|
||||||
isLoading={scheduleState.tags.has("loading")}
|
isLoading={scheduleState.tags.has("loading")}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
navigate(`/workspaces/${workspaceId}`)
|
navigate(`/workspaces/${workspaceId}`)
|
||||||
|
@ -13270,6 +13270,11 @@ typescript@4.6.4:
|
|||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
|
||||||
integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
|
integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
|
||||||
|
|
||||||
|
tzdata@1.0.30:
|
||||||
|
version "1.0.30"
|
||||||
|
resolved "https://registry.yarnpkg.com/tzdata/-/tzdata-1.0.30.tgz#d9d5a4b4b5e1ed95f6255f98c0564c4256316f52"
|
||||||
|
integrity sha512-/0yogZsIRUVhGIEGZahL+Nnl9gpMD6jtQ9MlVtPVofFwhaqa+cFTgRy1desTAKqdmIJjS6CL+i6F/mnetrLaxw==
|
||||||
|
|
||||||
uglify-js@^3.1.4:
|
uglify-js@^3.1.4:
|
||||||
version "3.15.1"
|
version "3.15.1"
|
||||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.1.tgz#9403dc6fa5695a6172a91bc983ea39f0f7c9086d"
|
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.1.tgz#9403dc6fa5695a6172a91bc983ea39f0f7c9086d"
|
||||||
|
Reference in New Issue
Block a user