Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 511bb46

Browse files
feat: Change workspace version using the UI (#5158)
1 parent eff99f7 commit 511bb46

18 files changed

+606
-7
lines changed

site/src/AppRouter.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ const WorkspaceBuildPage = lazy(
4646
() => import("./pages/WorkspaceBuildPage/WorkspaceBuildPage"),
4747
)
4848
const WorkspacePage = lazy(() => import("./pages/WorkspacePage/WorkspacePage"))
49+
const WorkspaceChangeVersionPage = lazy(
50+
() => import("./pages/WorkspaceChangeVersionPage/WorkspaceChangeVersionPage"),
51+
)
4952
const WorkspaceSchedulePage = lazy(
5053
() => import("./pages/WorkspaceSchedulePage/WorkspaceSchedulePage"),
5154
)
@@ -360,6 +363,7 @@ export const AppRouter: FC = () => {
360363
</AuthAndFrame>
361364
}
362365
/>
366+
363367
<Route
364368
path="schedule"
365369
element={
@@ -386,6 +390,15 @@ export const AppRouter: FC = () => {
386390
</AuthAndFrame>
387391
}
388392
/>
393+
394+
<Route
395+
path="change-version"
396+
element={
397+
<RequireAuth>
398+
<WorkspaceChangeVersionPage />
399+
</RequireAuth>
400+
}
401+
/>
389402
</Route>
390403
</Route>
391404

site/src/components/DropdownButton/ActionCtas.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Button from "@material-ui/core/Button"
33
import { makeStyles } from "@material-ui/core/styles"
44
import BlockIcon from "@material-ui/icons/Block"
55
import CloudQueueIcon from "@material-ui/icons/CloudQueue"
6+
import UpdateOutlined from "@material-ui/icons/UpdateOutlined"
67
import CropSquareIcon from "@material-ui/icons/CropSquare"
78
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline"
89
import PlayCircleOutlineIcon from "@material-ui/icons/PlayCircleOutline"
@@ -33,6 +34,23 @@ export const UpdateButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({
3334
)
3435
}
3536

37+
export const ChangeVersionButton: FC<
38+
React.PropsWithChildren<WorkspaceAction>
39+
> = ({ handleAction }) => {
40+
const styles = useStyles()
41+
const { t } = useTranslation("workspacePage")
42+
43+
return (
44+
<Button
45+
className={styles.actionButton}
46+
startIcon={<UpdateOutlined />}
47+
onClick={handleAction}
48+
>
49+
{t("actionButton.changeVersion")}
50+
</Button>
51+
)
52+
}
53+
3654
export const StartButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({
3755
handleAction,
3856
}) => {

site/src/components/Workspace/Workspace.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export interface WorkspaceProps {
4545
handleDelete: () => void
4646
handleUpdate: () => void
4747
handleCancel: () => void
48+
handleChangeVersion: () => void
4849
isUpdating: boolean
4950
workspace: TypesGen.Workspace
5051
resources?: TypesGen.WorkspaceResource[]
@@ -68,6 +69,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
6869
handleDelete,
6970
handleUpdate,
7071
handleCancel,
72+
handleChangeVersion,
7173
workspace,
7274
isUpdating,
7375
resources,
@@ -143,6 +145,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
143145
handleDelete={handleDelete}
144146
handleUpdate={handleUpdate}
145147
handleCancel={handleCancel}
148+
handleChangeVersion={handleChangeVersion}
146149
isUpdating={isUpdating}
147150
/>
148151
</Stack>

site/src/components/WorkspaceActions/WorkspaceActions.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const renderComponent = async (props: Partial<WorkspaceActionsProps> = {}) => {
1818
handleDelete={jest.fn()}
1919
handleUpdate={jest.fn()}
2020
handleCancel={jest.fn()}
21+
handleChangeVersion={jest.fn()}
2122
isUpdating={false}
2223
/>,
2324
)
@@ -35,6 +36,7 @@ const renderAndClick = async (props: Partial<WorkspaceActionsProps> = {}) => {
3536
handleDelete={jest.fn()}
3637
handleUpdate={jest.fn()}
3738
handleCancel={jest.fn()}
39+
handleChangeVersion={jest.fn()}
3840
isUpdating={false}
3941
/>,
4042
)

site/src/components/WorkspaceActions/WorkspaceActions.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next"
44
import { WorkspaceStatus } from "../../api/typesGenerated"
55
import {
66
ActionLoadingButton,
7+
ChangeVersionButton,
78
DeleteButton,
89
DisabledButton,
910
StartButton,
@@ -20,6 +21,7 @@ export interface WorkspaceActionsProps {
2021
handleDelete: () => void
2122
handleUpdate: () => void
2223
handleCancel: () => void
24+
handleChangeVersion: () => void
2325
isUpdating: boolean
2426
children?: ReactNode
2527
}
@@ -32,6 +34,7 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
3234
handleDelete,
3335
handleUpdate,
3436
handleCancel,
37+
handleChangeVersion,
3538
isUpdating,
3639
}) => {
3740
const { t } = useTranslation("workspacePage")
@@ -45,6 +48,9 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
4548
[ButtonTypesEnum.updating]: (
4649
<ActionLoadingButton label={t("actionButton.updating")} />
4750
),
51+
[ButtonTypesEnum.changeVersion]: (
52+
<ChangeVersionButton handleAction={handleChangeVersion} />
53+
),
4854
[ButtonTypesEnum.start]: <StartButton handleAction={handleStart} />,
4955
[ButtonTypesEnum.starting]: (
5056
<ActionLoadingButton label={t("actionButton.starting")} />

site/src/components/WorkspaceActions/constants.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export enum ButtonTypesEnum {
1111
deleting = "deleting",
1212
update = "update",
1313
updating = "updating",
14+
changeVersion = "changeVersion",
1415
// disabled buttons
1516
canceling = "canceling",
1617
deleted = "deleted",
@@ -34,7 +35,11 @@ export const statusToAbilities: Record<WorkspaceStatus, WorkspaceAbilities> = {
3435
canAcceptJobs: false,
3536
},
3637
running: {
37-
actions: [ButtonTypesEnum.stop, ButtonTypesEnum.delete],
38+
actions: [
39+
ButtonTypesEnum.stop,
40+
ButtonTypesEnum.changeVersion,
41+
ButtonTypesEnum.delete,
42+
],
3843
canCancel: false,
3944
canAcceptJobs: true,
4045
},
@@ -44,22 +49,31 @@ export const statusToAbilities: Record<WorkspaceStatus, WorkspaceAbilities> = {
4449
canAcceptJobs: false,
4550
},
4651
stopped: {
47-
actions: [ButtonTypesEnum.start, ButtonTypesEnum.delete],
52+
actions: [
53+
ButtonTypesEnum.start,
54+
ButtonTypesEnum.changeVersion,
55+
ButtonTypesEnum.delete,
56+
],
4857
canCancel: false,
4958
canAcceptJobs: true,
5059
},
5160
canceled: {
5261
actions: [
5362
ButtonTypesEnum.start,
5463
ButtonTypesEnum.stop,
64+
ButtonTypesEnum.changeVersion,
5565
ButtonTypesEnum.delete,
5666
],
5767
canCancel: false,
5868
canAcceptJobs: true,
5969
},
6070
// in the case of an error
6171
failed: {
62-
actions: [ButtonTypesEnum.start, ButtonTypesEnum.delete],
72+
actions: [
73+
ButtonTypesEnum.start,
74+
ButtonTypesEnum.changeVersion,
75+
ButtonTypesEnum.delete,
76+
],
6377
canCancel: false,
6478
canAcceptJobs: true,
6579
},

site/src/i18n/en/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import workspacesPage from "./workspacesPage.json"
1010
import usersPage from "./usersPage.json"
1111
import templateVersionPage from "./templateVersionPage.json"
1212
import loginPage from "./loginPage.json"
13+
import workspaceChangeVersionPage from "./workspaceChangeVersionPage.json"
1314

1415
export const en = {
1516
common,
@@ -24,4 +25,5 @@ export const en = {
2425
usersPage,
2526
templateVersionPage,
2627
loginPage,
28+
workspaceChangeVersionPage,
2729
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"title": "",
3+
"labels": {
4+
"workspaceVersion": "Workspace version",
5+
"submit": "Update version",
6+
"createdBy": "Created by",
7+
"active": "Active"
8+
}
9+
}

site/src/i18n/en/workspacePage.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"updating": "Updating",
2828
"starting": "Starting...",
2929
"stopping": "Stopping...",
30-
"deleting": "Deleting..."
30+
"deleting": "Deleting...",
31+
"changeVersion": "Change version"
3132
},
3233
"disabledButton": {
3334
"canceling": "Canceling",
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { makeStyles } from "@material-ui/core/styles"
2+
import TextField from "@material-ui/core/TextField"
3+
import Autocomplete from "@material-ui/lab/Autocomplete"
4+
import { Template, TemplateVersion, Workspace } from "api/typesGenerated"
5+
import { FormFooter } from "components/FormFooter/FormFooter"
6+
import { Pill } from "components/Pill/Pill"
7+
import { Stack } from "components/Stack/Stack"
8+
import { useFormik } from "formik"
9+
import { FC } from "react"
10+
import { useTranslation } from "react-i18next"
11+
import { createDayString } from "util/createDayString"
12+
import * as Yup from "yup"
13+
14+
const validationSchema = Yup.object({
15+
versionId: Yup.string().required(),
16+
})
17+
18+
export const WorkspaceChangeVersionForm: FC<{
19+
isLoading: boolean
20+
workspace: Workspace
21+
template: Template
22+
versions: TemplateVersion[]
23+
onSubmit: (versionId: string) => void
24+
onCancel: () => void
25+
}> = ({ isLoading, workspace, template, versions, onSubmit, onCancel }) => {
26+
const styles = useStyles()
27+
const { t } = useTranslation("workspaceChangeVersionPage")
28+
const formik = useFormik({
29+
initialValues: {
30+
versionId: workspace.latest_build.template_version_id,
31+
},
32+
validationSchema,
33+
onSubmit: ({ versionId }) => onSubmit(versionId),
34+
})
35+
const autocompleteValue = versions.find(
36+
(version) => version.id === formik.values.versionId,
37+
)
38+
39+
return (
40+
<form onSubmit={formik.handleSubmit}>
41+
<Stack direction="column" spacing={3}>
42+
<Stack
43+
direction="row"
44+
spacing={2}
45+
className={styles.workspace}
46+
alignItems="center"
47+
>
48+
<div className={styles.workspaceIcon}>
49+
<img src={workspace.template_icon} alt="" />
50+
</div>
51+
<Stack direction="column" spacing={0.5}>
52+
<span className={styles.workspaceName}>{workspace.name}</span>
53+
54+
<span className={styles.workspaceDescription}>
55+
{workspace.template_display_name.length > 0
56+
? workspace.template_display_name
57+
: workspace.template_name}
58+
</span>
59+
</Stack>
60+
</Stack>
61+
62+
<Autocomplete
63+
id="workspaceVersion"
64+
disableClearable
65+
options={versions.slice().reverse()}
66+
value={autocompleteValue}
67+
onChange={async (_event, value) => {
68+
if (value) {
69+
await formik.setFieldValue("versionId", value.id)
70+
}
71+
}}
72+
renderInput={(params) => (
73+
<TextField
74+
{...params}
75+
label={t("labels.workspaceVersion")}
76+
variant="outlined"
77+
fullWidth
78+
/>
79+
)}
80+
getOptionLabel={(version: TemplateVersion) => version.name}
81+
renderOption={(version: TemplateVersion) => (
82+
<div className={styles.menuItem}>
83+
<div>
84+
<div>{version.name}</div>
85+
<div className={styles.versionDescription}>
86+
{t("labels.createdBy")} {version.created_by.username}{" "}
87+
{createDayString(version.created_at)}
88+
</div>
89+
</div>
90+
91+
{template.active_version_id === version.id && (
92+
<Pill
93+
type="success"
94+
text={t("labels.active")}
95+
className={styles.activePill}
96+
/>
97+
)}
98+
</div>
99+
)}
100+
/>
101+
</Stack>
102+
103+
<FormFooter
104+
onCancel={onCancel}
105+
isLoading={isLoading}
106+
submitLabel={t("labels.submit")}
107+
/>
108+
</form>
109+
)
110+
}
111+
112+
const useStyles = makeStyles((theme) => ({
113+
workspace: {
114+
padding: theme.spacing(2.5, 3),
115+
borderRadius: theme.shape.borderRadius,
116+
backgroundColor: theme.palette.background.paper,
117+
border: `1px solid ${theme.palette.divider}`,
118+
},
119+
120+
workspaceName: {
121+
fontSize: theme.spacing(2),
122+
},
123+
124+
workspaceDescription: {
125+
fontSize: theme.spacing(1.75),
126+
color: theme.palette.text.secondary,
127+
},
128+
129+
workspaceIcon: {
130+
width: theme.spacing(5),
131+
lineHeight: 1,
132+
133+
"& img": {
134+
width: "100%",
135+
},
136+
},
137+
138+
menuItem: {
139+
paddingTop: theme.spacing(1),
140+
paddingBottom: theme.spacing(1),
141+
position: "relative",
142+
width: "100%",
143+
},
144+
145+
versionDescription: {
146+
fontSize: theme.spacing(1.5),
147+
color: theme.palette.text.secondary,
148+
},
149+
150+
activePill: {
151+
position: "absolute",
152+
top: theme.spacing(2),
153+
right: theme.spacing(2),
154+
},
155+
}))

0 commit comments

Comments
 (0)