Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit42e9956

Browse files
authored
feat: workspace view for schedules (#991)
Summary:This adds the client-side implementation to match the types introducedin#879 and#844 as well as a card in the Workspaces page to presentworkspace 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.tsxNext Steps:The WorkspaceSchedule.tsx card only presents data (on purpose). In orderto 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 whichwill likely be generalized up to WorkspaceSection.tsxImpact:This is user-facingThis does not fully resolve either#274 or#275 (I may further decomposethat work to reflect reality and keep things in small deliverableincrements), but adds significant progress towards both.
1 parent027d89d commit42e9956

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
typeUpdateWorkspaceAutostartRequeststruct {
95-
Schedulestring
95+
Schedulestring`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
typeUpdateWorkspaceAutostopRequeststruct {
115-
Schedulestring
115+
Schedulestring`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
@@ -33,6 +33,7 @@
3333
"@xstate/inspect":"0.6.5",
3434
"@xstate/react":"3.0.0",
3535
"axios":"0.26.1",
36+
"cronstrue":"2.2.0",
3637
"formik":"2.2.9",
3738
"history":"5.3.0",
3839
"react":"17.0.2",

‎site/src/api/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,23 @@ export const getBuildInfo = async (): Promise<Types.BuildInfoResponse> => {
7373
constresponse=awaitaxios.get("/api/v2/buildinfo")
7474
returnresponse.data
7575
}
76+
77+
exportconstputWorkspaceAutostart=async(
78+
workspaceID:string,
79+
autostart:Types.WorkspaceAutostartRequest,
80+
):Promise<void>=>{
81+
constpayload=JSON.stringify(autostart)
82+
awaitaxios.put(`/api/v2/workspaces/${workspaceID}/autostart`,payload,{
83+
headers:{ ...CONTENT_TYPE_JSON},
84+
})
85+
}
86+
87+
exportconstputWorkspaceAutostop=async(
88+
workspaceID:string,
89+
autostop:Types.WorkspaceAutostopRequest,
90+
):Promise<void>=>{
91+
constpayload=JSON.stringify(autostop)
92+
awaitaxios.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
exportinterfaceWorkspace{
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
exportinterfaceAPIKeyResponse{
@@ -74,3 +78,11 @@ export interface UserAgent {
7478
readonlyip_address:string
7579
readonlyos:string
7680
}
81+
82+
exportinterfaceWorkspaceAutostartRequest{
83+
schedule:string
84+
}
85+
86+
exportinterfaceWorkspaceAutostopRequest{
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*asTypesfrom"../../api/types"
99
import*asConstantsfrom"./constants"
10+
import{WorkspaceSchedule}from"./WorkspaceSchedule"
1011
import{WorkspaceSection}from"./WorkspaceSection"
1112

1213
exportinterfaceWorkspaceProps{
@@ -30,6 +31,7 @@ export const Workspace: React.FC<WorkspaceProps> = ({ organization, template, wo
3031
<WorkspaceSectiontitle="Applications">
3132
<Placeholder/>
3233
</WorkspaceSection>
34+
<WorkspaceScheduleautostart={workspace.autostart_schedule}autostop={workspace.autostop_schedule}/>
3335
<WorkspaceSectiontitle="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+
importReactfrom"react"
3+
import{MockWorkspaceAutostartEnabled}from"../../test_helpers"
4+
import{WorkspaceSchedule,WorkspaceScheduleProps}from"./WorkspaceSchedule"
5+
6+
exportdefault{
7+
title:"Workspaces/WorkspaceSchedule",
8+
component:WorkspaceSchedule,
9+
}
10+
11+
constTemplate:Story<WorkspaceScheduleProps>=(args)=><WorkspaceSchedule{...args}/>
12+
13+
exportconstExample=Template.bind({})
14+
Example.args={
15+
autostart:MockWorkspaceAutostartEnabled.schedule,
16+
autostop:"",
17+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
importBoxfrom"@material-ui/core/Box"
2+
importTypographyfrom"@material-ui/core/Typography"
3+
importcronstruefrom"cronstrue"
4+
importReactfrom"react"
5+
import{expandScheduleCronString,extractTimezone}from"../../util/schedule"
6+
import{WorkspaceSection}from"./WorkspaceSection"
7+
8+
constLanguage={
9+
autoStartLabel:(schedule:string):string=>{
10+
constprefix="Workspace start"
11+
12+
if(schedule){
13+
return`${prefix} (${extractTimezone(schedule)})`
14+
}else{
15+
returnprefix
16+
}
17+
},
18+
autoStopLabel:(schedule:string):string=>{
19+
constprefix="Workspace shutdown"
20+
21+
if(schedule){
22+
return`${prefix} (${extractTimezone(schedule)})`
23+
}else{
24+
returnprefix
25+
}
26+
},
27+
cronHumanDisplay:(schedule:string):string=>{
28+
if(schedule){
29+
returncronstrue.toString(expandScheduleCronString(schedule),{throwExceptionOnParseError:false})
30+
}
31+
return"Manual"
32+
},
33+
}
34+
35+
exportinterfaceWorkspaceScheduleProps{
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+
exportconstWorkspaceSchedule:React.FC<WorkspaceScheduleProps>=({ autostart, autostop})=>{
46+
return(
47+
<WorkspaceSectiontitle="Workspace schedule">
48+
<Boxmt={2}>
49+
<Typographyvariant="h6">{Language.autoStartLabel(autostart)}</Typography>
50+
<Typography>{Language.cronHumanDisplay(autostart)}</Typography>
51+
</Box>
52+
53+
<Boxmt={2}>
54+
<Typographyvariant="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
exportconstMockSessionToken={session_token:"my-session-token"}
@@ -46,13 +47,34 @@ export const MockTemplate: Template = {
4647
active_version_id:"",
4748
}
4849

50+
exportconstMockWorkspaceAutostartDisabled:WorkspaceAutostartRequest={
51+
schedule:"",
52+
}
53+
54+
exportconstMockWorkspaceAutostartEnabled: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+
exportconstMockWorkspaceAutostopDisabled:WorkspaceAutostartRequest={
61+
schedule:"",
62+
}
63+
64+
exportconstMockWorkspaceAutostopEnabled: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
exportconstMockWorkspace: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
exportconstMockUserAgent: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
returnres(ctx.status(200),ctx.json(M.MockWorkspace))
4646
}),
47+
rest.put("/api/v2/workspaces/:workspaceId/autostart",async(req,res,ctx)=>{
48+
returnres(ctx.status(200))
49+
}),
50+
rest.put("/api/v2/workspaces/:workspaceId/autostop",async(req,res,ctx)=>{
51+
returnres(ctx.status(200))
52+
}),
4753
]

‎site/src/util/schedule.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import{expandScheduleCronString,extractTimezone,stripTimezone}from"./schedule"
2+
3+
describe("util/schedule",()=>{
4+
describe("stripTimezone",()=>{
5+
it.each<[string,string]>([
6+
["CRON_TZ=Canada/Eastern 30 9 1-5","30 9 1-5"],
7+
["CRON_TZ=America/Central 0 8 1,2,4,5","0 8 1,2,4,5"],
8+
["30 9 1-5","30 9 1-5"],
9+
])(`stripTimezone(%p) returns %p`,(input,expected)=>{
10+
expect(stripTimezone(input)).toBe(expected)
11+
})
12+
})
13+
14+
describe("extractTimezone",()=>{
15+
it.each<[string,string]>([
16+
["CRON_TZ=Canada/Eastern 30 9 1-5","Canada/Eastern"],
17+
["CRON_TZ=America/Central 0 8 1,2,4,5","America/Central"],
18+
["30 9 1-5","UTC"],
19+
])(`extractTimezone(%p) returns %p`,(input,expected)=>{
20+
expect(extractTimezone(input)).toBe(expected)
21+
})
22+
})
23+
24+
describe("expandScheduleCronString",()=>{
25+
it.each<[string,string]>([
26+
["CRON_TZ=Canada/Eastern 30 9 1-5","30 9 * * 1-5"],
27+
["CRON_TZ=America/Central 0 8 1,2,4,5","0 8 * * 1,2,4,5"],
28+
["30 9 1-5","30 9 * * 1-5"],
29+
])(`expandScheduleCronString(%p) returns %p`,(input,expected)=>{
30+
expect(expandScheduleCronString(input)).toBe(expected)
31+
})
32+
})
33+
})

‎site/src/util/schedule.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
*@fileoverview Client-side counterpart of the coderd/autostart/schedule Go
3+
* package. This package is a variation on crontab that uses minute, hour and
4+
* day of week.
5+
*/
6+
7+
/**
8+
* DEFAULT_TIMEZONE is the default timezone that crontab assumes unless one is
9+
* specified.
10+
*/
11+
constDEFAULT_TIMEZONE="UTC"
12+
13+
/**
14+
* stripTimezone strips a leading timezone from a schedule string
15+
*/
16+
exportconststripTimezone=(raw:string):string=>{
17+
returnraw.replace(/CRON_TZ=\S*\s/,"")
18+
}
19+
20+
/**
21+
* extractTimezone returns a leading timezone from a schedule string if one is
22+
* specified; otherwise DEFAULT_TIMEZONE
23+
*/
24+
exportconstextractTimezone=(raw:string):string=>{
25+
constmatches=raw.match(/CRON_TZ=\S*\s/g)
26+
27+
if(matches&&matches.length){
28+
returnmatches[0].replace(/CRON_TZ=/,"").trim()
29+
}else{
30+
returnDEFAULT_TIMEZONE
31+
}
32+
}
33+
34+
/**
35+
* expandScheduleCronString ensures a Schedule is expanded to a valid 5-value
36+
* cron string by inserting '*' in month and day positions. If there is a
37+
* leading timezone, it is removed.
38+
*
39+
*@example
40+
* expandScheduleCronString("30 9 1-5") // -> "30 9 * * 1-5"
41+
*/
42+
exportconstexpandScheduleCronString=(schedule:string):string=>{
43+
constprepared=stripTimezone(schedule).trim()
44+
45+
constparts=prepared.split(" ")
46+
47+
while(parts.length<5){
48+
// insert '*' in the second to last position
49+
// ie [a, b, c] --> [a, b, *, c]
50+
parts.splice(parts.length-1,0,"*")
51+
}
52+
53+
returnparts.join(" ")
54+
}

‎site/yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5450,6 +5450,11 @@ create-require@^1.1.0:
54505450
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
54515451
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
54525452

5453+
cronstrue@2.2.0:
5454+
version "2.2.0"
5455+
resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.2.0.tgz#8e02b8ef0fa70a9eab9999f1f838df4bd378b471"
5456+
integrity sha512-oM/ftAvCNIdygVGGfYp8gxrVc81mDSA2mff0kvu6+ehrZhfYPzGHG8DVcFdrRVizjHnzWoFIlgEq6KTM/9lPBw==
5457+
54535458
cross-fetch@^3.0.4:
54545459
version "3.1.5"
54555460
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp