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

Commitc70bfa1

Browse files
greyscaledkylecarbs
authored andcommitted
fix: derive running ws stop time from deadline (#1920)
* refactor: isWorkspaceOn utilitySummary:A utility is function is added that answers the question if a workspaceis on.Impact:This is a shared piece of logic in workspace scheduling presentations.In particular it unblocks work in 1779, or at least allows animplementation that shares details with the WorkspaceScheduleBanner.Notes:We could possibly instead return whether the workspace is "ON","UNKNOWN", or "OFF". Maybe a future improvement for that could be madeas the neds arrises.* fix: derive running ws stop time from deadlineSummary:When a workspace is on, the remaining time until shutdown needs to bederived from the deadline timestamp, not implied from the TTL
1 parentdfbb451 commitc70bfa1

File tree

8 files changed

+164
-60
lines changed

8 files changed

+164
-60
lines changed
Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,6 @@
11
import{action}from"@storybook/addon-actions"
22
import{Story}from"@storybook/react"
3-
import{
4-
MockCanceledWorkspace,
5-
MockCancelingWorkspace,
6-
MockDeletedWorkspace,
7-
MockDeletingWorkspace,
8-
MockFailedWorkspace,
9-
MockOutdatedWorkspace,
10-
MockStartingWorkspace,
11-
MockStoppedWorkspace,
12-
MockStoppingWorkspace,
13-
MockWorkspace,
14-
MockWorkspaceBuild,
15-
MockWorkspaceResource,
16-
MockWorkspaceResource2,
17-
}from"../../testHelpers/renderHelpers"
3+
import*asMocksfrom"../../testHelpers/entities"
184
import{Workspace,WorkspaceProps}from"./Workspace"
195

206
exportdefault{
@@ -27,36 +13,73 @@ const Template: Story<WorkspaceProps> = (args) => <Workspace {...args} />
2713

2814
exportconstStarted=Template.bind({})
2915
Started.args={
30-
workspace:MockWorkspace,
16+
workspace:Mocks.MockWorkspace,
3117
handleStart:action("start"),
3218
handleStop:action("stop"),
33-
resources:[MockWorkspaceResource,MockWorkspaceResource2],
34-
builds:[MockWorkspaceBuild],
19+
resources:[Mocks.MockWorkspaceResource,Mocks.MockWorkspaceResource2],
20+
builds:[Mocks.MockWorkspaceBuild],
3521
}
3622

3723
exportconstStarting=Template.bind({})
38-
Starting.args={ ...Started.args,workspace:MockStartingWorkspace}
24+
Starting.args={
25+
...Started.args,
26+
workspace:Mocks.MockStartingWorkspace,
27+
}
3928

4029
exportconstStopped=Template.bind({})
41-
Stopped.args={ ...Started.args,workspace:MockStoppedWorkspace}
30+
Stopped.args={
31+
...Started.args,
32+
workspace:Mocks.MockStoppedWorkspace,
33+
}
4234

4335
exportconstStopping=Template.bind({})
44-
Stopping.args={ ...Started.args,workspace:MockStoppingWorkspace}
36+
Stopping.args={
37+
...Started.args,
38+
workspace:Mocks.MockStoppingWorkspace,
39+
}
4540

4641
exportconstError=Template.bind({})
47-
Error.args={ ...Started.args,workspace:MockFailedWorkspace}
42+
Error.args={
43+
...Started.args,
44+
workspace:{
45+
...Mocks.MockFailedWorkspace,
46+
latest_build:{
47+
...Mocks.MockWorkspaceBuild,
48+
job:{
49+
...Mocks.MockProvisionerJob,
50+
status:"failed",
51+
},
52+
transition:"start",
53+
},
54+
},
55+
}
4856

4957
exportconstDeleting=Template.bind({})
50-
Deleting.args={ ...Started.args,workspace:MockDeletingWorkspace}
58+
Deleting.args={
59+
...Started.args,
60+
workspace:Mocks.MockDeletingWorkspace,
61+
}
5162

5263
exportconstDeleted=Template.bind({})
53-
Deleted.args={ ...Started.args,workspace:MockDeletedWorkspace}
64+
Deleted.args={
65+
...Started.args,
66+
workspace:Mocks.MockDeletedWorkspace,
67+
}
5468

5569
exportconstCanceling=Template.bind({})
56-
Canceling.args={ ...Started.args,workspace:MockCancelingWorkspace}
70+
Canceling.args={
71+
...Started.args,
72+
workspace:Mocks.MockCancelingWorkspace,
73+
}
5774

5875
exportconstCanceled=Template.bind({})
59-
Canceled.args={ ...Started.args,workspace:MockCanceledWorkspace}
76+
Canceled.args={
77+
...Started.args,
78+
workspace:Mocks.MockCanceledWorkspace,
79+
}
6080

6181
exportconstOutdated=Template.bind({})
62-
Outdated.args={ ...Started.args,workspace:MockOutdatedWorkspace}
82+
Outdated.args={
83+
...Started.args,
84+
workspace:Mocks.MockOutdatedWorkspace,
85+
}

‎site/src/components/WorkspaceSchedule/WorkspaceSchedule.stories.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import{Story}from"@storybook/react"
22
importdayjsfrom"dayjs"
3+
importutcfrom"dayjs/plugin/utc"
34
import*asMocksfrom"../../testHelpers/entities"
45
import{WorkspaceSchedule,WorkspaceScheduleProps}from"./WorkspaceSchedule"
56

7+
dayjs.extend(utc)
8+
9+
// REMARK: There's a known problem with storybook and using date libraries that
10+
// call string.toLowerCase
11+
// SEE: https:github.com/storybookjs/storybook/issues/12208#issuecomment-697044557
12+
constONE=1
13+
constSEVEN=7
14+
615
exportdefault{
716
title:"components/WorkspaceSchedule",
817
component:WorkspaceSchedule,
9-
argTypes:{},
1018
}
1119

1220
constTemplate:Story<WorkspaceScheduleProps>=(args)=><WorkspaceSchedule{...args}/>
@@ -15,6 +23,12 @@ export const NoTTL = Template.bind({})
1523
NoTTL.args={
1624
workspace:{
1725
...Mocks.MockWorkspace,
26+
latest_build:{
27+
...Mocks.MockWorkspaceBuild,
28+
// a mannual shutdown has a deadline of '"0001-01-01T00:00:00Z"'
29+
// SEE: #1834
30+
deadline:"0001-01-01T00:00:00Z",
31+
},
1832
ttl:undefined,
1933
},
2034
}
@@ -23,11 +37,10 @@ export const ShutdownSoon = Template.bind({})
2337
ShutdownSoon.args={
2438
workspace:{
2539
...Mocks.MockWorkspace,
26-
2740
latest_build:{
2841
...Mocks.MockWorkspaceBuild,
42+
deadline:dayjs().add(ONE,"hour").utc().format(),
2943
transition:"start",
30-
updated_at:dayjs().subtract(1,"hour").toString(),// 1 hour ago
3144
},
3245
ttl:2*60*60*1000*1_000_000,// 2 hours
3346
},
@@ -40,8 +53,8 @@ ShutdownLong.args = {
4053

4154
latest_build:{
4255
...Mocks.MockWorkspaceBuild,
56+
deadline:dayjs().add(SEVEN,"days").utc().format(),
4357
transition:"start",
44-
updated_at:dayjs().toString(),
4558
},
4659
ttl:7*24*60*60*1000*1_000_000,// 7 days
4760
},
@@ -55,7 +68,6 @@ WorkspaceOffShort.args = {
5568
latest_build:{
5669
...Mocks.MockWorkspaceBuild,
5770
transition:"stop",
58-
updated_at:dayjs().subtract(2,"days").toString(),
5971
},
6072
ttl:2*60*60*1000*1_000_000,// 2 hours
6173
},
@@ -69,7 +81,6 @@ WorkspaceOffLong.args = {
6981
latest_build:{
7082
...Mocks.MockWorkspaceBuild,
7183
transition:"stop",
72-
updated_at:dayjs().subtract(2,"days").toString(),
7384
},
7485
ttl:2*365*24*60*60*1000*1_000_000,// 2 years
7586
},

‎site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@ import cronstrue from "cronstrue"
66
importdayjsfrom"dayjs"
77
importdurationfrom"dayjs/plugin/duration"
88
importrelativeTimefrom"dayjs/plugin/relativeTime"
9+
importutcfrom"dayjs/plugin/utc"
910
import{FC}from"react"
1011
import{LinkasRouterLink}from"react-router-dom"
1112
import{Workspace}from"../../api/typesGenerated"
1213
import{MONOSPACE_FONT_FAMILY}from"../../theme/constants"
1314
import{extractTimezone,stripTimezone}from"../../util/schedule"
15+
import{isWorkspaceOn}from"../../util/workspace"
1416
import{Stack}from"../Stack/Stack"
1517

18+
dayjs.extend(utc)
1619
dayjs.extend(duration)
1720
dayjs.extend(relativeTime)
1821

19-
constLanguage={
22+
exportconstLanguage={
2023
autoStartDisplay:(schedule:string):string=>{
2124
if(schedule){
2225
returncronstrue.toString(stripTimezone(schedule),{throwExceptionOnParseError:false})
@@ -33,24 +36,34 @@ const Language = {
3336
}
3437
},
3538
autoStopDisplay:(workspace:Workspace):string=>{
36-
constlatest=workspace.latest_build
39+
constdeadline=dayjs(workspace.latest_build.deadline).utc()
40+
// a mannual shutdown has a deadline of '"0001-01-01T00:00:00Z"'
41+
// SEE: #1834
42+
consthasDeadline=deadline.year()>1
43+
constttl=workspace.ttl
3744

38-
if(!workspace.ttl||workspace.ttl<1){
39-
return"Manual"
40-
}
41-
42-
if(latest.transition==="start"){
43-
constnow=dayjs()
44-
constupdatedAt=dayjs(latest.updated_at)
45-
constdeadline=updatedAt.add(workspace.ttl/1_000_000,"ms")
45+
if(isWorkspaceOn(workspace)&&hasDeadline){
46+
// Workspace is on --> derive from latest_build.deadline. Note that the
47+
// user may modify their workspace object (ttl) while the workspace is
48+
// running and depending on system semantics, the deadline may still
49+
// represent the previously defined ttl. Thus, we always derive from the
50+
// deadline as the source of truth.
51+
constnow=dayjs().utc()
4652
if(now.isAfter(deadline)){
47-
return"Workspace is shutting down now"
53+
return"Workspace is shutting down"
54+
}else{
55+
returnnow.to(deadline)
4856
}
49-
returnnow.to(deadline)
57+
}elseif(!ttl||ttl<1){
58+
// If the workspace is not on, and the ttl is 0 or undefined, then the
59+
// workspace is set to manually shutdown.
60+
return"Manual"
61+
}else{
62+
// The workspace has a ttl set, but is either in an unknown state or is
63+
// not running. Therefore, we derive from workspace.ttl.
64+
constduration=dayjs.duration(ttl/1_000_000,"milliseconds")
65+
return`${duration.humanize()} after start`
5066
}
51-
52-
constduration=dayjs.duration(workspace.ttl/1_000_000,"milliseconds")
53-
return`${duration.humanize()} after start`
5467
},
5568
editScheduleLink:"Edit schedule",
5669
schedule:"Schedule",

‎site/src/components/WorkspaceScheduleBanner/WorkspaceScheduleBanner.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ describe("WorkspaceScheduleBanner", () => {
9191
latest_build:{
9292
...Mocks.MockWorkspaceBuild,
9393
deadline:dayjs().add(27,"minutes").utc().format(),
94-
job:Mocks.MockRunningProvisionerJob,
9594
transition:"start",
9695
},
9796
}

‎site/src/components/WorkspaceScheduleBanner/WorkspaceScheduleBanner.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import isSameOrBefore from "dayjs/plugin/isSameOrBefore"
55
importutcfrom"dayjs/plugin/utc"
66
import{FC}from"react"
77
import*asTypesGenfrom"../../api/typesGenerated"
8+
import{isWorkspaceOn}from"../../util/workspace"
89

910
dayjs.extend(utc)
1011
dayjs.extend(isSameOrBefore)
@@ -18,12 +19,7 @@ export interface WorkspaceScheduleBannerProps {
1819
}
1920

2021
exportconstshouldDisplay=(workspace:TypesGen.Workspace):boolean=>{
21-
consttransition=workspace.latest_build.transition
22-
conststatus=workspace.latest_build.job.status
23-
24-
if(transition!=="start"){
25-
returnfalse
26-
}elseif(status==="canceled"||status==="canceling"||status==="failed"){
22+
if(!isWorkspaceOn(workspace)){
2723
returnfalse
2824
}else{
2925
// a mannual shutdown has a deadline of '"0001-01-01T00:00:00Z"'

‎site/src/testHelpers/entities.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,24 @@ export const MockWorkspace: TypesGen.Workspace = {
167167
latest_build:MockWorkspaceBuild,
168168
}
169169

170-
exportconstMockStoppedWorkspace:TypesGen.Workspace={ ...MockWorkspace,latest_build:MockWorkspaceBuildStop}
170+
exportconstMockStoppedWorkspace:TypesGen.Workspace={
171+
...MockWorkspace,
172+
latest_build:MockWorkspaceBuildStop,
173+
}
171174
exportconstMockStoppingWorkspace:TypesGen.Workspace={
172175
...MockWorkspace,
173-
latest_build:{ ...MockWorkspaceBuildStop,job:MockRunningProvisionerJob},
176+
latest_build:{
177+
...MockWorkspaceBuildStop,
178+
job:MockRunningProvisionerJob,
179+
},
174180
}
175181
exportconstMockStartingWorkspace:TypesGen.Workspace={
176182
...MockWorkspace,
177-
latest_build:{ ...MockWorkspaceBuild,job:MockRunningProvisionerJob},
183+
latest_build:{
184+
...MockWorkspaceBuild,
185+
job:MockRunningProvisionerJob,
186+
transition:"start",
187+
},
178188
}
179189
exportconstMockCancelingWorkspace:TypesGen.Workspace={
180190
...MockWorkspace,
@@ -186,7 +196,10 @@ export const MockCanceledWorkspace: TypesGen.Workspace = {
186196
}
187197
exportconstMockFailedWorkspace:TypesGen.Workspace={
188198
...MockWorkspace,
189-
latest_build:{ ...MockWorkspaceBuild,job:MockFailedProvisionerJob},
199+
latest_build:{
200+
...MockWorkspaceBuild,
201+
job:MockFailedProvisionerJob,
202+
},
190203
}
191204
exportconstMockDeletingWorkspace:TypesGen.Workspace={
192205
...MockWorkspace,

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import*asTypesGenfrom"../api/typesGenerated"
2+
import*asMocksfrom"../testHelpers/entities"
3+
import{isWorkspaceOn}from"./workspace"
4+
5+
describe("util > workspace",()=>{
6+
describe("isWorkspaceOn",()=>{
7+
it.each<[TypesGen.WorkspaceTransition,TypesGen.ProvisionerJobStatus,boolean]>([
8+
["delete","canceled",false],
9+
["delete","canceling",false],
10+
["delete","failed",false],
11+
["delete","pending",false],
12+
["delete","running",false],
13+
["delete","succeeded",false],
14+
15+
["stop","canceled",false],
16+
["stop","canceling",false],
17+
["stop","failed",false],
18+
["stop","pending",false],
19+
["stop","running",false],
20+
["stop","succeeded",false],
21+
22+
["start","canceled",false],
23+
["start","canceling",false],
24+
["start","failed",false],
25+
["start","pending",false],
26+
["start","running",false],
27+
["start","succeeded",true],
28+
])(`transition=%p, status=%p, isWorkspaceOn=%p`,(transition,status,isOn)=>{
29+
constworkspace:TypesGen.Workspace={
30+
...Mocks.MockWorkspace,
31+
latest_build:{
32+
...Mocks.MockWorkspaceBuild,
33+
job:{
34+
...Mocks.MockProvisionerJob,
35+
status,
36+
},
37+
transition,
38+
},
39+
}
40+
expect(isWorkspaceOn(workspace)).toBe(isOn)
41+
})
42+
})
43+
})

‎site/src/util/workspace.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import{Theme}from"@material-ui/core/styles"
22
importdayjsfrom"dayjs"
33
import{WorkspaceBuildTransition}from"../api/types"
4-
import{WorkspaceAgent,WorkspaceBuild}from"../api/typesGenerated"
4+
import{Workspace,WorkspaceAgent,WorkspaceBuild}from"../api/typesGenerated"
55

66
exporttypeWorkspaceStatus=
77
|"queued"
@@ -185,3 +185,9 @@ export const getDisplayAgentStatus = (
185185
}
186186
}
187187
}
188+
189+
exportconstisWorkspaceOn=(workspace:Workspace):boolean=>{
190+
consttransition=workspace.latest_build.transition
191+
conststatus=workspace.latest_build.job.status
192+
returntransition==="start"&&status==="succeeded"
193+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp