Skip to content

Commit 5334762

Browse files
committed
Add base for ephemeral form
1 parent b833861 commit 5334762

File tree

12 files changed

+221
-80
lines changed

12 files changed

+221
-80
lines changed

site/src/api/api.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -481,8 +481,8 @@ export function waitForBuild(build: TypesGen.WorkspaceBuild) {
481481
let latestJobInfo: TypesGen.ProvisionerJob | undefined = undefined
482482

483483
while (
484-
!["succeeded", "canceled"].some(
485-
(status) => latestJobInfo?.status.includes(status),
484+
!["succeeded", "canceled"].some((status) =>
485+
latestJobInfo?.status.includes(status),
486486
)
487487
) {
488488
const { job } = await getWorkspaceBuildByNumber(
@@ -1346,3 +1346,15 @@ export const issueReconnectingPTYSignedToken = async (
13461346
)
13471347
return response.data
13481348
}
1349+
1350+
export const getWorkspaceParameters = async (workspace: TypesGen.Workspace) => {
1351+
const latestBuild = workspace.latest_build
1352+
const [templateVersionRichParameters, buildParameters] = await Promise.all([
1353+
getTemplateVersionRichParameters(latestBuild.template_version_id),
1354+
getWorkspaceBuildParameters(latestBuild.id),
1355+
])
1356+
return {
1357+
templateVersionRichParameters,
1358+
buildParameters,
1359+
}
1360+
}
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
1-
import { FC } from "react"
1+
import { forwardRef } from "react"
22
import MuiLoadingButton, {
33
LoadingButtonProps as MuiLoadingButtonProps,
44
} from "@mui/lab/LoadingButton"
55

66
export type LoadingButtonProps = MuiLoadingButtonProps
77

8-
export const LoadingButton: FC<LoadingButtonProps> = ({
9-
children,
10-
loadingIndicator,
11-
...buttonProps
12-
}) => {
8+
export const LoadingButton = forwardRef<
9+
HTMLButtonElement,
10+
MuiLoadingButtonProps
11+
>(({ children, loadingIndicator, ...buttonProps }, ref) => {
1312
return (
14-
<MuiLoadingButton variant="outlined" color="neutral" {...buttonProps}>
13+
<MuiLoadingButton
14+
variant="outlined"
15+
color="neutral"
16+
ref={ref}
17+
{...buttonProps}
18+
>
1519
{/* known issue: https://github.com/mui/material-ui/issues/27853 */}
1620
<span>
1721
{buttonProps.loading && loadingIndicator ? loadingIndicator : children}
1822
</span>
1923
</MuiLoadingButton>
2024
)
21-
}
25+
})

site/src/components/Workspace/Workspace.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
194194

195195
<PageHeaderActions>
196196
<WorkspaceActions
197-
workspaceStatus={workspace.latest_build.status}
198-
isOutdated={workspace.outdated}
197+
workspace={workspace}
199198
handleStart={handleStart}
200199
handleStop={handleStop}
201200
handleRestart={handleRestart}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {
2+
TemplateVersionParameter,
3+
WorkspaceBuildParameter,
4+
} from "api/typesGenerated"
5+
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput"
6+
import { useFormik } from "formik"
7+
import { getFormHelpers } from "utils/formUtils"
8+
import { getInitialParameterValues } from "utils/richParameters"
9+
10+
export const BuildParametersForm = ({
11+
ephemeralParameters,
12+
buildParameters,
13+
}: {
14+
ephemeralParameters: TemplateVersionParameter[]
15+
buildParameters: WorkspaceBuildParameter[]
16+
}) => {
17+
const form = useFormik({
18+
initialValues: {
19+
rich_parameter_values: getInitialParameterValues(
20+
ephemeralParameters,
21+
buildParameters,
22+
),
23+
},
24+
onSubmit: () => {},
25+
})
26+
const getFieldHelpers = getFormHelpers(form)
27+
28+
return (
29+
<form action="">
30+
{ephemeralParameters.map((parameter, index) => {
31+
return (
32+
<RichParameterInput
33+
{...getFieldHelpers("rich_parameter_values[" + index + "].value")}
34+
key={parameter.name}
35+
parameter={parameter}
36+
initialValue=""
37+
index={index}
38+
onChange={async (value) => {
39+
await form.setFieldValue(`rich_parameter_values[${index}]`, {
40+
name: parameter.name,
41+
value: value,
42+
})
43+
}}
44+
/>
45+
)
46+
})}
47+
</form>
48+
)
49+
}

site/src/components/WorkspaceActions/Buttons.tsx

+98-10
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,21 @@ import CropSquareIcon from "@mui/icons-material/CropSquare"
55
import PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline"
66
import ReplayIcon from "@mui/icons-material/Replay"
77
import { LoadingButton } from "components/LoadingButton/LoadingButton"
8-
import { FC } from "react"
8+
import { FC, useRef, useState } from "react"
99
import BlockOutlined from "@mui/icons-material/BlockOutlined"
10+
import ButtonGroup from "@mui/material/ButtonGroup"
11+
import ExpandMoreOutlined from "@mui/icons-material/ExpandMoreOutlined"
12+
import Popover from "@mui/material/Popover"
13+
import {
14+
HelpTooltipText,
15+
HelpTooltipTitle,
16+
} from "components/Tooltips/HelpTooltip/HelpTooltip"
17+
import Box from "@mui/material/Box"
18+
import { useQuery } from "@tanstack/react-query"
19+
import { Workspace } from "api/typesGenerated"
20+
import { getWorkspaceParameters } from "api/api"
21+
import { BuildParametersForm } from "./BuildParametersPopover"
22+
import { Loader } from "components/Loader/Loader"
1023

1124
interface WorkspaceAction {
1225
loading?: boolean
@@ -31,17 +44,92 @@ export const UpdateButton: FC<WorkspaceAction> = ({
3144
)
3245
}
3346

34-
export const StartButton: FC<WorkspaceAction> = ({ handleAction, loading }) => {
47+
export const StartButton: FC<WorkspaceAction & { workspace: Workspace }> = ({
48+
handleAction,
49+
workspace,
50+
loading,
51+
}) => {
52+
const anchorRef = useRef<HTMLButtonElement>(null)
53+
const [isOpen, setIsOpen] = useState(false)
54+
const { data: parameters } = useQuery({
55+
queryKey: ["workspace", workspace.id, "parameters"],
56+
queryFn: () => getWorkspaceParameters(workspace),
57+
enabled: isOpen,
58+
})
59+
const ephemeralParameters = parameters
60+
? parameters.templateVersionRichParameters.filter((p) => p.ephemeral)
61+
: undefined
62+
3563
return (
36-
<LoadingButton
37-
loading={loading}
38-
loadingIndicator="Starting..."
39-
loadingPosition="start"
40-
startIcon={<PlayCircleOutlineIcon />}
41-
onClick={handleAction}
64+
<ButtonGroup
65+
variant="outlined"
66+
sx={{
67+
// Workaround to make the border transitions smmothly on button groups
68+
"& > button:hover + button": {
69+
borderLeft: "1px solid #FFF",
70+
},
71+
}}
4272
>
43-
Start
44-
</LoadingButton>
73+
<LoadingButton
74+
loading={loading}
75+
loadingIndicator="Starting..."
76+
loadingPosition="start"
77+
startIcon={<PlayCircleOutlineIcon />}
78+
onClick={handleAction}
79+
>
80+
Start
81+
</LoadingButton>
82+
<Button
83+
disabled={loading}
84+
color="neutral"
85+
sx={{ px: 0 }}
86+
ref={anchorRef}
87+
onClick={() => {
88+
setIsOpen(true)
89+
}}
90+
>
91+
<ExpandMoreOutlined sx={{ fontSize: 16 }} />
92+
</Button>
93+
<Popover
94+
open={isOpen}
95+
anchorEl={anchorRef.current}
96+
onClose={() => {
97+
setIsOpen(false)
98+
}}
99+
anchorOrigin={{
100+
vertical: "bottom",
101+
horizontal: "right",
102+
}}
103+
transformOrigin={{
104+
vertical: "top",
105+
horizontal: "right",
106+
}}
107+
sx={{
108+
".MuiPaper-root": {
109+
p: 2.5,
110+
width: (theme) => theme.spacing(38),
111+
marginTop: 1,
112+
},
113+
}}
114+
>
115+
<Box sx={{ color: (theme) => theme.palette.text.secondary }}>
116+
<HelpTooltipTitle>Build Options</HelpTooltipTitle>
117+
<HelpTooltipText>
118+
These parameters only apply for a single workspace start.
119+
</HelpTooltipText>
120+
</Box>
121+
<Box>
122+
{parameters && parameters.buildParameters && ephemeralParameters ? (
123+
<BuildParametersForm
124+
buildParameters={parameters.buildParameters}
125+
ephemeralParameters={ephemeralParameters}
126+
/>
127+
) : (
128+
<Loader />
129+
)}
130+
</Box>
131+
</Popover>
132+
</ButtonGroup>
45133
)
46134
}
47135

site/src/components/WorkspaceActions/WorkspaceActions.stories.tsx

+11-12
Original file line numberDiff line numberDiff line change
@@ -26,67 +26,66 @@ const defaultArgs = {
2626
export const Starting = Template.bind({})
2727
Starting.args = {
2828
...defaultArgs,
29-
workspaceStatus: Mocks.MockStartingWorkspace.latest_build.status,
29+
workspace: Mocks.MockStartingWorkspace,
3030
}
3131

3232
export const Running = Template.bind({})
3333
Running.args = {
3434
...defaultArgs,
35-
workspaceStatus: Mocks.MockWorkspace.latest_build.status,
35+
workspace: Mocks.MockWorkspace,
3636
}
3737

3838
export const Stopping = Template.bind({})
3939
Stopping.args = {
4040
...defaultArgs,
41-
workspaceStatus: Mocks.MockStoppingWorkspace.latest_build.status,
41+
workspace: Mocks.MockStoppingWorkspace,
4242
}
4343

4444
export const Stopped = Template.bind({})
4545
Stopped.args = {
4646
...defaultArgs,
47-
workspaceStatus: Mocks.MockStoppedWorkspace.latest_build.status,
47+
workspace: Mocks.MockStoppedWorkspace,
4848
}
4949

5050
export const Canceling = Template.bind({})
5151
Canceling.args = {
5252
...defaultArgs,
53-
workspaceStatus: Mocks.MockCancelingWorkspace.latest_build.status,
53+
workspace: Mocks.MockCancelingWorkspace,
5454
}
5555

5656
export const Canceled = Template.bind({})
5757
Canceled.args = {
5858
...defaultArgs,
59-
workspaceStatus: Mocks.MockCanceledWorkspace.latest_build.status,
59+
workspace: Mocks.MockCanceledWorkspace,
6060
}
6161

6262
export const Deleting = Template.bind({})
6363
Deleting.args = {
6464
...defaultArgs,
65-
workspaceStatus: Mocks.MockDeletingWorkspace.latest_build.status,
65+
workspace: Mocks.MockDeletingWorkspace,
6666
}
6767

6868
export const Deleted = Template.bind({})
6969
Deleted.args = {
7070
...defaultArgs,
71-
workspaceStatus: Mocks.MockDeletedWorkspace.latest_build.status,
71+
workspace: Mocks.MockDeletedWorkspace,
7272
}
7373

7474
export const Outdated = Template.bind({})
7575
Outdated.args = {
7676
...defaultArgs,
77-
isOutdated: true,
78-
workspaceStatus: Mocks.MockOutdatedWorkspace.latest_build.status,
77+
workspace: Mocks.MockOutdatedWorkspace,
7978
}
8079

8180
export const Failed = Template.bind({})
8281
Failed.args = {
8382
...defaultArgs,
84-
workspaceStatus: Mocks.MockFailedWorkspace.latest_build.status,
83+
workspace: Mocks.MockFailedWorkspace,
8584
}
8685

8786
export const Updating = Template.bind({})
8887
Updating.args = {
8988
...defaultArgs,
9089
isUpdating: true,
91-
workspaceStatus: Mocks.MockOutdatedWorkspace.latest_build.status,
90+
workspace: Mocks.MockOutdatedWorkspace,
9291
}

site/src/components/WorkspaceActions/WorkspaceActions.tsx

+10-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Menu from "@mui/material/Menu"
33
import { makeStyles } from "@mui/styles"
44
import MoreVertOutlined from "@mui/icons-material/MoreVertOutlined"
55
import { FC, Fragment, ReactNode, useRef, useState } from "react"
6-
import { WorkspaceStatus } from "api/typesGenerated"
6+
import { Workspace } from "api/typesGenerated"
77
import {
88
ActionLoadingButton,
99
CancelButton,
@@ -24,8 +24,7 @@ import DeleteOutlined from "@mui/icons-material/DeleteOutlined"
2424
import IconButton from "@mui/material/IconButton"
2525

2626
export interface WorkspaceActionsProps {
27-
workspaceStatus: WorkspaceStatus
28-
isOutdated: boolean
27+
workspace: Workspace
2928
handleStart: () => void
3029
handleStop: () => void
3130
handleRestart: () => void
@@ -41,8 +40,8 @@ export interface WorkspaceActionsProps {
4140
}
4241

4342
export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
44-
workspaceStatus,
45-
isOutdated,
43+
workspace,
44+
4645
handleStart,
4746
handleStop,
4847
handleRestart,
@@ -60,8 +59,8 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
6059
canCancel,
6160
canAcceptJobs,
6261
actions: actionsByStatus,
63-
} = actionsByWorkspaceStatus(workspaceStatus)
64-
const canBeUpdated = isOutdated && canAcceptJobs
62+
} = actionsByWorkspaceStatus(workspace.latest_build.status)
63+
const canBeUpdated = workspace.outdated && canAcceptJobs
6564
const menuTriggerRef = useRef<HTMLButtonElement>(null)
6665
const [isMenuOpen, setIsMenuOpen] = useState(false)
6766

@@ -71,9 +70,11 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
7170
[ButtonTypesEnum.updating]: (
7271
<UpdateButton loading handleAction={handleUpdate} />
7372
),
74-
[ButtonTypesEnum.start]: <StartButton handleAction={handleStart} />,
73+
[ButtonTypesEnum.start]: (
74+
<StartButton handleAction={handleStart} workspace={workspace} />
75+
),
7576
[ButtonTypesEnum.starting]: (
76-
<StartButton loading handleAction={handleStart} />
77+
<StartButton workspace={workspace} loading handleAction={handleStart} />
7778
),
7879
[ButtonTypesEnum.stop]: <StopButton handleAction={handleStop} />,
7980
[ButtonTypesEnum.stopping]: (

site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,11 @@ export const WorkspaceReadyPage = ({
100100
["canceling", "deleting", "pending", "starting", "stopping"].includes(
101101
workspace.latest_build.status,
102102
))
103-
104103
const {
105104
mutate: restartWorkspace,
106105
error: restartBuildError,
107106
isLoading: isRestarting,
108107
} = useRestartWorkspace()
109-
110108
// keep banner machine in sync with workspace
111109
useEffect(() => {
112110
bannerSend({ type: "REFRESH_WORKSPACE", workspace })

0 commit comments

Comments
 (0)