8000 feat: workspace view for schedules · coder/coder@22c66a6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 22c66a6

Browse files
committed
feat: workspace view for schedules
Summary: This adds the client-side implementation to match the types introduced in #879 and #844 as well as a card in the Workspaces page to present workspace the data. Details: * Added a convenient line break in the example schedule.Weekly * Added missing `json:""` annotations in codersdk/workspaces.go * Installed cronstrue for displaying human-friendly cron strings * Adjusted/Added client-side types to match codersdk/workspaces.go * Added new component WorkspaceSchedule.tsx Next Steps: The WorkspaceSchedule.tsx card only presents data (on purpose). In order to make it PUT/modify data, a few changes will be made: - a form for updating workspace schedule will be created - the form will wrapped in a dialog or modal - the WorkspaceSchedule card will have a way of opening the modal which will likely be generalized up to WorkspaceSection.tsx Impact: This is user-facing This does not fully resolve either #274 or #275 (I may further decompose that work to reflect reality and keep things in small deliverable increments), but adds significant progress towards both.
1 parent ce49966 commit 22c66a6

File tree

13 files changed

+235
-3
lines changed

13 files changed

+235
-3
lines changed

coderd/autostart/schedule/schedule.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ var defaultParser = cron.NewParser(parserFormatWeekly)
2626
// local_sched, _ := schedule.Weekly("59 23 *")
2727
// fmt.Println(sched.Next(time.Now().Format(time.RFC3339)))
2828
// // Output: 2022-04-04T23:59:00Z
29+
//
2930
// us_sched, _ := schedule.Weekly("CRON_TZ=US/Central 30 9 1-5")
3031
// fmt.Println(sched.Next(time.Now()).Format(time.RFC3339))
3132
// // Output: 2022-04-04T14:30:00Z

codersdk/workspaces.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func (c *Client) WorkspaceBuildByName(ctx context.Context, workspace uuid.UUID,
9292

9393
// UpdateWorkspaceAutostartRequest is a request to update a workspace's autostart schedule.
9494
type UpdateWorkspaceAutostartRequest struct {
95-
Schedule string
95+
Schedule string `json:"schedule"`
9696
}
9797

9898
// UpdateWorkspaceAutostart sets the autostart schedule for workspace by id.
@@ -112,7 +112,7 @@ func (c *Client) UpdateWorkspaceAutostart(ctx context.Context, id uuid.UUID, req
112112

113113
// UpdateWorkspaceAutostopRequest is a request to update a workspace's autostop schedule.
114114
type UpdateWorkspaceAutostopRequest struct {
115-
Schedule string
115+
Schedule string `json:"schedule"`
116116
}
117117

118118
// UpdateWorkspaceAutostop sets the autostop schedule for workspace by id.

site/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@xstate/inspect": "0.6.5",
3333
"@xstate/react": "3.0.0",
3434
"axios": "0.26.1",
35+
"cronstrue": "2.2.0",
3536
"formik": "2.2.9",
3637
"history": "5.3.0",
3738
"react": "17.0.2",

site/src/api/index.ts

Lines changed: 20 additions & 0 deletions
< 7D4E tr class="diff-line-row">
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,23 @@ export const getBuildInfo = async (): Promise<Types.BuildInfoResponse> => {
7373
const response = await axios.get("/api/v2/buildinfo")
7474
return response.data
7575
}
76+
77+
export const putWorkspaceAutostart = async (
78+
workspaceID: string,
79+
autostart: Types.WorkspaceAutostartRequest,
80+
): Promise<void> => {
81+
const payload = JSON.stringify(autostart)
82+
await axios.put(`/api/v2/workspaces/${workspaceID}/autostart`, payload, {
83+
headers: { ...CONTENT_TYPE_JSON },
84+
})
85+
}
86+
87+
export const putWorkspaceAutostop = async (
88+
workspaceID: string,
89+
autostop: Types.WorkspaceAutostopRequest,
90+
): Promise<void> => {
91+
const payload = JSON.stringify(autostop)
92+
await axios.put(`/api/v2/workspaces/${workspaceID}/autostop`, payload, {
93+
headers: { ...CONTENT_TYPE_JSON },
94+
})
95+
}

site/src/api/types.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,18 @@ export interface CreateWorkspaceRequest {
5454
template_id: string
5555
}
5656

57-
// Must be kept in sync with backend Workspace struct
57+
/**
58+
* @remarks Keep in sync with codersdk/workspaces.go
59+
*/
5860
export interface Workspace {
5961
id: string
6062
created_at: string
6163
updated_at: string
6264
owner_id: string
6365
template_id: string
6466
name: string
67+
autostart_schedule: string
68+
autostop_schedule: string
6569
}
6670

6771
export interface APIKeyResponse {
@@ -74,3 +78,11 @@ export interface UserAgent {
7478
readonly ip_address: string
7579
readonly os: string
7680
}
81+
82+
export interface WorkspaceAutostartRequest {
83+
schedule: string
84+
}
85+
86+
export interface WorkspaceAutostopRequest {
87+
schedule: string
88+
}

site/src/components/Workspace/Workspace.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import React from "react"
77
import { Link } from "react-router-dom"
88
import * as Types from "../../api/types"
99
import * as Constants from "./constants"
10+
import { WorkspaceSchedule } from "./WorkspaceSchedule"
1011
import { WorkspaceSection } from "./WorkspaceSection"
1112

1213
export interface WorkspaceProps {
@@ -30,6 +31,7 @@ export const Workspace: React.FC<WorkspaceProps> = ({ organization, template, wo
3031
<WorkspaceSection title="Applications">
3132
<Placeholder />
3233
</WorkspaceSection>
34+
<WorkspaceSchedule autostart={workspace.autostart_schedule} autostop={workspace.autostop_schedule} />
3335
<WorkspaceSection title="Dev URLs">
3436
<Placeholder />
3537
</WorkspaceSection>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Story } from "@storybook/react"
2+
import React from "react"
3+
import { MockWorkspaceAutostartEnabled } from "../../test_helpers"
4+
import { WorkspaceSchedule, WorkspaceScheduleProps } from "./WorkspaceSchedule"
5+
6+
export default {
7+
title: "Workspaces/WorkspaceSchedule",
8+
component: WorkspaceSchedule,
9+
}
10+
11+
const Template: Story<WorkspaceScheduleProps> = (args) => <WorkspaceSchedule {...args} />
12+
13+
export const Example = Template.bind({})
14+
Example.args = {
15+
autostart: MockW 10000 orkspaceAutostartEnabled.schedule,
16+
autostop: "",
17+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import Box from "@material-ui/core/Box"
2+
import Typography from "@material-ui/core/Typography"
3+
import cronstrue from "cronstrue"
4+
import React from "react"
5+
import { expandScheduleCronString, extractTimezone } from "../../util/schedule"
6+
import { WorkspaceSection } from "./WorkspaceSection"
7+
8+
const Language = {
9+
autoStartLabel: (schedule: string): string => {
10+
const prefix = "Workspace start"
11+
12+
if (schedule) {
13+
return `${prefix} (${extractTimezone(schedule)})`
14+
} else {
15+
return prefix
16+
}
17+
},
18+
autoStopLabel: (schedule: string): string => {
19+
const prefix = "Workspace shutdown"
20+
21+
if (schedule) {
22+
return `${prefix} (${extractTimezone(schedule)})`
23+
} else {
24+
return prefix
25+
}
26+
},
27+
cronHumanDisplay: (schedule: string): string => {
28+
if (schedule) {
29+
return cronstrue.toString(expandScheduleCronString(schedule), { throwExceptionOnParseError: false })
30+
}
31+
return "Manual"
32+
},
33+
}
34+
35+
export interface WorkspaceScheduleProps {
36+
autostart: string
37+
autostop: string
38+
}
39+
40+
/**
41+
* WorkspaceSchedule displays a workspace schedule in a human-readable format
42+
*
43+
* @remarks Visual Component
44+
*/
45+
export const WorkspaceSchedule: React.FC<WorkspaceScheduleProps> = ({ autostart, autostop }) => {
46+
return (
47+
<WorkspaceSection title="Workspace schedule">
48+
<Box mt={2}>
49+
<Typography variant="h6">{Language.autoStartLabel(autostart)}</Typography>
50+
<Typography>{Language.cronHumanDisplay(autostart)}</Typography>
51+
</Box>
52+
53+
<Box mt={2}>
54+
<Typography variant="h6">{Language.autoStopLabel(autostop)}</Typography>
55+
<Typography>{Language.cronHumanDisplay(autostop)}</Typography>
56+
</Box>
57+
</WorkspaceSection>
58+
)
59+
}

site/src/test_helpers/entities.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
UserAgent,
77
UserResponse,
88
Workspace,
9+
WorkspaceAutostartRequest,
910
} from "../api/types"
1011

1112
export const MockSessionToken = { session_token: "my-session-token" }
@@ -46,13 +47,34 @@ export const MockTemplate: Template = {
4647
active_version_id: "",
4748
}
4849

50+
export const MockWorkspaceAutostartDisabled: WorkspaceAutostartRequest = {
51+
schedule: "",
52+
}
53+
54+
export const MockWorkspaceAutostartEnabled: WorkspaceAutostartRequest = {
55+
// Runs at 9:30am Monday through Friday using Canada/Eastern
56+
// (America/Toronto) time
57+
schedule: "CRON_TZ=Canada/Eastern 30 9 1-5",
58+
}
59+
60+
export const MockWorkspaceAutostopDisabled: WorkspaceAutostartRequest = {
61+
schedule: "",
62+
}
63+
64+
export const MockWorkspaceAutostopEnabled: WorkspaceAutostartRequest = {
65+
// Runs at 9:30pm Monday through Friday using America/Toronto
66+
schedule: "CRON_TZ=America/Toronto 30 21 1-5",
67+
}
68+
4969
export const MockWorkspace: Workspace = {
5070
id: "test-workspace",
5171
name: "Test-Workspace",
5272
created_at: "",
5373
updated_at: "",
5474
template_id: MockTemplate.id,
5575
owner_id: MockUser.id,
76+
autostart_schedule: MockWorkspaceAutostartEnabled.schedule,
77+
autostop_schedule: MockWorkspaceAutostopEnabled.schedule,
5678
}
5779

5880
export const MockUserAgent: UserAgent = {

site/src/test_helpers/handlers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,10 @@ export const handlers = [
4444
rest.get("/api/v2/workspaces/:workspaceId", async (req, res, ctx) => {
4545
return res(ctx.status(200), ctx.json(M.MockWorkspace))
4646
}),
47+
rest.get("/api/v2/workspaces/:workspaceId/autostart", async (req, res, ctx) => {
48+
return res(ctx.status(200))
49+
}),
50+
rest.get("/api/v2/workspaces/:workspaceId/autostop", async (req, res, ctx) => {
51+
return res(ctx.status(200))
52+
}),
4753
]

0 commit comments

Comments
 (0)
0