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

Commitd931b2c

Browse files
authored
chore: refactor schedule banner (#4274)
* Start refactor* Fix color of auto stop switch* Format* Use helper functions for min/max check* Fix type* Put new component in own file* Fix decrease deadline bug* Simplify functions* Use ChooseOne* Remove commented code
1 parent139bc6f commitd931b2c

File tree

7 files changed

+309
-152
lines changed

7 files changed

+309
-152
lines changed

‎site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ export const WorkspaceScheduleForm: FC<React.PropsWithChildren<WorkspaceSchedule
309309
name="autoStopEnabled"
310310
checked={form.values.autoStopEnabled}
311311
onChange={handleToggleAutoStop}
312+
color="primary"
312313
/>
313314
}
314315
label={Language.stopSwitch}

‎site/src/pages/WorkspacePage/WorkspacePage.tsx

Lines changed: 23 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,21 @@
11
import{makeStyles}from"@material-ui/core/styles"
2-
import{useActor,useMachine,useSelector}from"@xstate/react"
3-
import{FeatureNames}from"api/types"
4-
importdayjsfrom"dayjs"
5-
importminMaxfrom"dayjs/plugin/minMax"
6-
import{FC,useContext,useEffect}from"react"
7-
import{Helmet}from"react-helmet-async"
8-
import{useTranslation}from"react-i18next"
2+
import{useMachine}from"@xstate/react"
3+
import{ChooseOne,Cond}from"components/Conditionals/ChooseOne"
4+
import{FC,useEffect}from"react"
95
import{useParams}from"react-router-dom"
10-
import{selectFeatureVisibility}from"xServices/entitlements/entitlementsSelectors"
11-
import{DeleteDialog}from"../../components/Dialogs/DeleteDialog/DeleteDialog"
126
import{ErrorSummary}from"../../components/ErrorSummary/ErrorSummary"
137
import{FullScreenLoader}from"../../components/Loader/FullScreenLoader"
14-
import{Workspace,WorkspaceErrors}from"../../components/Workspace/Workspace"
158
import{firstOrItem}from"../../util/array"
16-
import{pageTitle}from"../../util/page"
17-
import{canExtendDeadline,canReduceDeadline,maxDeadline,minDeadline}from"../../util/schedule"
18-
import{getFaviconByStatus}from"../../util/workspace"
19-
import{XServiceContext}from"../../xServices/StateContext"
209
import{workspaceMachine}from"../../xServices/workspace/workspaceXService"
21-
import{workspaceScheduleBannerMachine}from"../../xServices/workspaceSchedule/workspaceScheduleBannerXService"
22-
23-
dayjs.extend(minMax)
10+
import{WorkspaceReadyPage}from"./WorkspaceReadyPage"
2411

2512
exportconstWorkspacePage:FC=()=>{
2613
const{username:usernameQueryParam,workspace:workspaceQueryParam}=useParams()
2714
constusername=firstOrItem(usernameQueryParam,null)
2815
constworkspaceName=firstOrItem(workspaceQueryParam,null)
29-
const{ t}=useTranslation("workspacePage")
30-
constxServices=useContext(XServiceContext)
31-
constfeatureVisibility=useSelector(xServices.entitlementsXService,selectFeatureVisibility)
3216
const[workspaceState,workspaceSend]=useMachine(workspaceMachine)
33-
const{
34-
workspace,
35-
getWorkspaceError,
36-
template,
37-
getTemplateWarning,
38-
refreshWorkspaceWarning,
39-
builds,
40-
getBuildsError,
41-
permissions,
42-
checkPermissionsError,
43-
buildError,
44-
cancellationError,
45-
applicationsHost,
46-
}=workspaceState.context
47-
constcanUpdateWorkspace=Boolean(permissions?.updateWorkspace)
48-
const[bannerState,bannerSend]=useMachine(workspaceScheduleBannerMachine)
49-
const[buildInfoState]=useActor(xServices.buildInfoXService)
17+
const{ workspace, getWorkspaceError, getTemplateWarning, checkPermissionsError}=
18+
workspaceState.context
5019
conststyles=useStyles()
5120

5221
/**
@@ -57,95 +26,23 @@ export const WorkspacePage: FC = () => {
5726
username&&workspaceName&&workspaceSend({type:"GET_WORKSPACE", username, workspaceName})
5827
},[username,workspaceName,workspaceSend])
5928

60-
if(workspaceState.matches("error")){
61-
return(
62-
<divclassName={styles.error}>
63-
{Boolean(getWorkspaceError)&&<ErrorSummaryerror={getWorkspaceError}/>}
64-
{Boolean(getTemplateWarning)&&<ErrorSummaryerror={getTemplateWarning}/>}
65-
{Boolean(checkPermissionsError)&&<ErrorSummaryerror={checkPermissionsError}/>}
66-
</div>
67-
)
68-
}elseif(!workspace||!permissions){
69-
return<FullScreenLoader/>
70-
}elseif(!template){
71-
return<FullScreenLoader/>
72-
}else{
73-
constdeadline=dayjs(workspace.latest_build.deadline).utc()
74-
constfavicon=getFaviconByStatus(workspace.latest_build)
75-
return(
76-
<>
77-
<Helmet>
78-
<title>{pageTitle(`${workspace.owner_name}/${workspace.name}`)}</title>
79-
<linkrel="alternate icon"type="image/png"href={`/favicons/${favicon}.png`}/>
80-
<linkrel="icon"type="image/svg+xml"href={`/favicons/${favicon}.svg`}/>
81-
</Helmet>
82-
83-
<Workspace
84-
bannerProps={{
85-
isLoading:bannerState.hasTag("loading"),
86-
onExtend:()=>{
87-
bannerSend({
88-
type:"UPDATE_DEADLINE",
89-
workspaceId:workspace.id,
90-
newDeadline:dayjs.min(deadline.add(4,"hours"),maxDeadline(workspace,template)),
91-
})
92-
},
93-
}}
94-
scheduleProps={{
95-
onDeadlineMinus:()=>{
96-
bannerSend({
97-
type:"UPDATE_DEADLINE",
98-
workspaceId:workspace.id,
99-
newDeadline:dayjs.max(deadline.add(-1,"hours"),minDeadline()),
100-
})
101-
},
102-
onDeadlinePlus:()=>{
103-
bannerSend({
104-
type:"UPDATE_DEADLINE",
105-
workspaceId:workspace.id,
106-
newDeadline:dayjs.min(deadline.add(1,"hours"),maxDeadline(workspace,template)),
107-
})
108-
},
109-
deadlineMinusEnabled:()=>{
110-
returncanReduceDeadline(deadline)
111-
},
112-
deadlinePlusEnabled:()=>{
113-
returncanExtendDeadline(deadline,workspace,template)
114-
},
115-
}}
116-
isUpdating={workspaceState.hasTag("updating")}
117-
workspace={workspace}
118-
handleStart={()=>workspaceSend("START")}
119-
handleStop={()=>workspaceSend("STOP")}
120-
handleDelete={()=>workspaceSend("ASK_DELETE")}
121-
handleUpdate={()=>workspaceSend("UPDATE")}
122-
handleCancel={()=>workspaceSend("CANCEL")}
123-
resources={workspace.latest_build.resources}
124-
builds={builds}
125-
canUpdateWorkspace={canUpdateWorkspace}
126-
hideSSHButton={featureVisibility[FeatureNames.BrowserOnly]}
127-
workspaceErrors={{
128-
[WorkspaceErrors.GET_RESOURCES_ERROR]:refreshWorkspaceWarning,
129-
[WorkspaceErrors.GET_BUILDS_ERROR]:getBuildsError,
130-
[WorkspaceErrors.BUILD_ERROR]:buildError,
131-
[WorkspaceErrors.CANCELLATION_ERROR]:cancellationError,
132-
}}
133-
buildInfo={buildInfoState.context.buildInfo}
134-
applicationsHost={applicationsHost}
135-
/>
136-
<DeleteDialog
137-
entity="workspace"
138-
name={workspace.name}
139-
info={t("deleteDialog.info",{timeAgo:dayjs(workspace.created_at).fromNow()})}
140-
isOpen={workspaceState.matches({ready:{build:"askingDelete"}})}
141-
onCancel={()=>workspaceSend("CANCEL_DELETE")}
142-
onConfirm={()=>{
143-
workspaceSend("DELETE")
144-
}}
145-
/>
146-
</>
147-
)
148-
}
29+
return(
30+
<ChooseOne>
31+
<Condcondition={workspaceState.matches("error")}>
32+
<divclassName={styles.error}>
33+
{Boolean(getWorkspaceError)&&<ErrorSummaryerror={getWorkspaceError}/>}
34+
{Boolean(getTemplateWarning)&&<ErrorSummaryerror={getTemplateWarning}/>}
35+
{Boolean(checkPermissionsError)&&<ErrorSummaryerror={checkPermissionsError}/>}
36+
</div>
37+
</Cond>
38+
<Condcondition={Boolean(workspace)&&workspaceState.matches("ready")}>
39+
<WorkspaceReadyPageworkspaceState={workspaceState}workspaceSend={workspaceSend}/>
40+
</Cond>
41+
<Cond>
42+
<FullScreenLoader/>
43+
</Cond>
44+
</ChooseOne>
45+
)
14946
}
15047

15148
constuseStyles=makeStyles((theme)=>({
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import{useActor,useSelector}from"@xstate/react"
2+
import{FeatureNames}from"api/types"
3+
importdayjsfrom"dayjs"
4+
import{useContext}from"react"
5+
import{Helmet}from"react-helmet-async"
6+
import{useTranslation}from"react-i18next"
7+
import{selectFeatureVisibility}from"xServices/entitlements/entitlementsSelectors"
8+
import{StateFrom}from"xstate"
9+
import{DeleteDialog}from"../../components/Dialogs/DeleteDialog/DeleteDialog"
10+
import{Workspace,WorkspaceErrors}from"../../components/Workspace/Workspace"
11+
import{pageTitle}from"../../util/page"
12+
import{getFaviconByStatus}from"../../util/workspace"
13+
import{XServiceContext}from"../../xServices/StateContext"
14+
import{WorkspaceEvent,workspaceMachine}from"../../xServices/workspace/workspaceXService"
15+
16+
interfaceWorkspaceReadyPageProps{
17+
workspaceState:StateFrom<typeofworkspaceMachine>
18+
workspaceSend:(event:WorkspaceEvent)=>void
19+
}
20+
21+
exportconstWorkspaceReadyPage=({
22+
workspaceState,
23+
workspaceSend,
24+
}:WorkspaceReadyPageProps):JSX.Element=>{
25+
const[bannerState,bannerSend]=useActor(workspaceState.children["scheduleBannerMachine"])
26+
constxServices=useContext(XServiceContext)
27+
constfeatureVisibility=useSelector(xServices.entitlementsXService,selectFeatureVisibility)
28+
const[buildInfoState]=useActor(xServices.buildInfoXService)
29+
const{
30+
workspace,
31+
refreshWorkspaceWarning,
32+
builds,
33+
getBuildsError,
34+
buildError,
35+
cancellationError,
36+
applicationsHost,
37+
permissions,
38+
}=workspaceState.context
39+
if(workspace===undefined){
40+
throwError("Workspace is undefined")
41+
}
42+
constcanUpdateWorkspace=Boolean(permissions?.updateWorkspace)
43+
const{ t}=useTranslation("workspacePage")
44+
constfavicon=getFaviconByStatus(workspace.latest_build)
45+
46+
return(
47+
<>
48+
<Helmet>
49+
<title>{pageTitle(`${workspace.owner_name}/${workspace.name}`)}</title>
50+
<linkrel="alternate icon"type="image/png"href={`/favicons/${favicon}.png`}/>
51+
<linkrel="icon"type="image/svg+xml"href={`/favicons/${favicon}.svg`}/>
52+
</Helmet>
53+
54+
<Workspace
55+
bannerProps={{
56+
isLoading:bannerState.hasTag("loading"),
57+
onExtend:()=>{
58+
bannerSend({
59+
type:"INCREASE_DEADLINE",
60+
hours:4,
61+
})
62+
},
63+
}}
64+
scheduleProps={{
65+
onDeadlineMinus:()=>{
66+
bannerSend({
67+
type:"DECREASE_DEADLINE",
68+
hours:1,
69+
})
70+
},
71+
onDeadlinePlus:()=>{
72+
bannerSend({
73+
type:"INCREASE_DEADLINE",
74+
hours:1,
75+
})
76+
},
77+
deadlineMinusEnabled:()=>!bannerState.matches("atMinDeadline"),
78+
deadlinePlusEnabled:()=>!bannerState.matches("atMaxDeadline"),
79+
}}
80+
isUpdating={workspaceState.hasTag("updating")}
81+
workspace={workspace}
82+
handleStart={()=>workspaceSend({type:"START"})}
83+
handleStop={()=>workspaceSend({type:"STOP"})}
84+
handleDelete={()=>workspaceSend({type:"ASK_DELETE"})}
85+
handleUpdate={()=>workspaceSend({type:"UPDATE"})}
86+
handleCancel={()=>workspaceSend({type:"CANCEL"})}
87+
resources={workspace.latest_build.resources}
88+
builds={builds}
89+
canUpdateWorkspace={canUpdateWorkspace}
90+
hideSSHButton={featureVisibility[FeatureNames.BrowserOnly]}
91+
workspaceErrors={{
92+
[WorkspaceErrors.GET_RESOURCES_ERROR]:refreshWorkspaceWarning,
93+
[WorkspaceErrors.GET_BUILDS_ERROR]:getBuildsError,
94+
[WorkspaceErrors.BUILD_ERROR]:buildError,
95+
[WorkspaceErrors.CANCELLATION_ERROR]:cancellationError,
96+
}}
97+
buildInfo={buildInfoState.context.buildInfo}
98+
applicationsHost={applicationsHost}
99+
/>
100+
<DeleteDialog
101+
entity="workspace"
102+
name={workspace.name}
103+
info={t("deleteDialog.info",{timeAgo:dayjs(workspace.created_at).fromNow()})}
104+
isOpen={workspaceState.matches({ready:{build:"askingDelete"}})}
105+
onCancel={()=>workspaceSend({type:"CANCEL_DELETE"})}
106+
onConfirm={()=>{
107+
workspaceSend({type:"DELETE"})
108+
}}
109+
/>
110+
</>
111+
)
112+
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import {
88
deadlineExtensionMax,
99
deadlineExtensionMin,
1010
extractTimezone,
11-
maxDeadline,
12-
minDeadline,
11+
getMaxDeadline,
12+
getMinDeadline,
1313
stripTimezone,
1414
}from"./schedule"
1515

@@ -55,7 +55,7 @@ describe("maxDeadline", () => {
5555
}
5656

5757
// Then: deadlineMinusDisabled should be falsy
58-
constdelta=maxDeadline(workspace,template).diff(now)
58+
constdelta=getMaxDeadline(workspace,template).diff(now)
5959
expect(delta).toBeLessThanOrEqual(deadlineExtensionMax.asMilliseconds())
6060
})
6161
})
@@ -68,15 +68,15 @@ describe("maxDeadline", () => {
6868
}
6969

7070
// Then: deadlineMinusDisabled should be falsy
71-
constdelta=maxDeadline(workspace,template).diff(now)
71+
constdelta=getMaxDeadline(workspace,template).diff(now)
7272
expect(delta).toBeLessThanOrEqual(deadlineExtensionMax.asMilliseconds())
7373
})
7474
})
7575
})
7676

7777
describe("minDeadline",()=>{
7878
it("should never be less than 30 minutes",()=>{
79-
constdelta=minDeadline().diff(now)
79+
constdelta=getMinDeadline().diff(now)
8080
expect(delta).toBeGreaterThanOrEqual(deadlineExtensionMin.asMilliseconds())
8181
})
8282
})

‎site/src/util/schedule.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,30 @@ export const autoStopDisplay = (workspace: Workspace): string => {
113113
exportconstdeadlineExtensionMin=dayjs.duration(30,"minutes")
114114
exportconstdeadlineExtensionMax=dayjs.duration(24,"hours")
115115

116-
exportfunctionmaxDeadline(ws:Workspace,tpl:Template):dayjs.Dayjs{
116+
/**
117+
* Depends on the time the workspace was last updated, the template config,
118+
* and a global constant.
119+
*@param ws workspace
120+
*@param tpl template
121+
*@returns the latest datetime at which the workspace can be automatically shut down.
122+
*/
123+
exportfunctiongetMaxDeadline(ws:Workspace|undefined,tpl:Template):dayjs.Dayjs{
117124
// note: we count runtime from updated_at as started_at counts from the start of
118125
// the workspace build process, which can take a while.
126+
if(ws===undefined){
127+
throwError("Cannot calculate max deadline because workspace is undefined")
128+
}
119129
conststartedAt=dayjs(ws.latest_build.updated_at)
120130
constmaxTemplateDeadline=startedAt.add(dayjs.duration(tpl.max_ttl_ms,"milliseconds"))
121131
constmaxGlobalDeadline=startedAt.add(deadlineExtensionMax)
122132
returndayjs.min(maxTemplateDeadline,maxGlobalDeadline)
123133
}
124134

125-
exportfunctionminDeadline():dayjs.Dayjs{
135+
/**
136+
* Depends on the current time and a global constant.
137+
*@returns the earliest datetime at which the workspace can be automatically shut down.
138+
*/
139+
exportfunctiongetMinDeadline():dayjs.Dayjs{
126140
returndayjs().add(deadlineExtensionMin)
127141
}
128142

@@ -131,9 +145,12 @@ export function canExtendDeadline(
131145
workspace:Workspace,
132146
template:Template,
133147
):boolean{
134-
returndeadline<maxDeadline(workspace,template)
148+
returndeadline<getMaxDeadline(workspace,template)
135149
}
136150

137151
exportfunctioncanReduceDeadline(deadline:dayjs.Dayjs):boolean{
138-
returndeadline>minDeadline()
152+
returndeadline>getMinDeadline()
139153
}
154+
155+
exportconstgetDeadline=(workspace:Workspace):dayjs.Dayjs=>
156+
dayjs(workspace.latest_build.deadline).utc()

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp