Skip to content

Commit 949ba1a

Browse files
BrunoQuaresmapull[bot]
authored andcommitted
feat(site): make workspace batch deletion GA (#9313)
1 parent 693b415 commit 949ba1a

File tree

15 files changed

+192
-92
lines changed

15 files changed

+192
-92
lines changed

coderd/apidoc/docs.go

+2-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+2-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codersdk/deployment.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const (
4848
FeatureAdvancedTemplateScheduling FeatureName = "advanced_template_scheduling"
4949
FeatureTemplateAutostopRequirement FeatureName = "template_autostop_requirement"
5050
FeatureWorkspaceProxy FeatureName = "workspace_proxy"
51+
FeatureWorkspaceBatchActions FeatureName = "workspace_batch_actions"
5152
)
5253

5354
// FeatureNames must be kept in-sync with the Feature enum above.
@@ -64,6 +65,7 @@ var FeatureNames = []FeatureName{
6465
FeatureAdvancedTemplateScheduling,
6566
FeatureWorkspaceProxy,
6667
FeatureUserRoleManagement,
68+
FeatureWorkspaceBatchActions,
6769
}
6870

6971
// Humanize returns the feature name in a human-readable format.
@@ -1958,9 +1960,6 @@ const (
19581960
// Deployment health page
19591961
ExperimentDeploymentHealthPage Experiment = "deployment_health_page"
19601962

1961-
// Workspaces batch actions
1962-
ExperimentWorkspacesBatchActions Experiment = "workspaces_batch_actions"
1963-
19641963
// Add new experiments here!
19651964
// ExperimentExample Experiment = "example"
19661965
)
@@ -1971,7 +1970,6 @@ const (
19711970
// not be included here and will be essentially hidden.
19721971
var ExperimentsAll = Experiments{
19731972
ExperimentDeploymentHealthPage,
1974-
ExperimentWorkspacesBatchActions,
19751973
}
19761974

19771975
// Experiments is a list of experiments that are enabled for the deployment.

docs/api/schemas.md

-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/api/typesGenerated.ts

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { makeStyles } from "@mui/styles"
22
import TableCell from "@mui/material/TableCell"
3-
import TableRow from "@mui/material/TableRow"
4-
import Skeleton from "@mui/material/Skeleton"
5-
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"
6-
import { FC } from "react"
3+
import TableRow, { TableRowProps } from "@mui/material/TableRow"
4+
import { FC, ReactNode, cloneElement, isValidElement } from "react"
75
import { Loader } from "../Loader/Loader"
86

97
export const TableLoader: FC = () => {
@@ -25,35 +23,27 @@ const useStyles = makeStyles((theme) => ({
2523
},
2624
}))
2725

28-
export const TableLoaderSkeleton: FC<{
29-
columns: number
26+
export const TableLoaderSkeleton = ({
27+
rows = 4,
28+
children,
29+
}: {
3030
rows?: number
31-
useAvatarData?: boolean
32-
}> = ({ columns, rows = 4, useAvatarData = false }) => {
33-
const placeholderColumns = Array(columns).fill(undefined)
34-
const placeholderRows = Array(rows).fill(undefined)
35-
31+
children: ReactNode
32+
}) => {
33+
if (!isValidElement(children)) {
34+
throw new Error(
35+
"TableLoaderSkeleton children must be a valid React element",
36+
)
37+
}
3638
return (
3739
<>
38-
{placeholderRows.map((_, rowIndex) => (
39-
<TableRow key={rowIndex} role="progressbar" data-testid="loader">
40-
{placeholderColumns.map((_, columnIndex) => {
41-
if (useAvatarData && columnIndex === 0) {
42-
return (
43-
<TableCell key={columnIndex}>
44-
<AvatarDataSkeleton />
45-
</TableCell>
46-
)
47-
}
48-
49-
return (
50-
<TableCell key={columnIndex}>
51-
<Skeleton variant="text" width="25%" />
52-
</TableCell>
53-
)
54-
})}
55-
</TableRow>
56-
))}
40+
{Array.from({ length: rows }, (_, i) =>
41+
cloneElement(children, { key: i }),
42+
)}
5743
</>
5844
)
5945
}
46+
47+
export const TableRowSkeleton = (props: TableRowProps) => {
48+
return <TableRow role="progressbar" data-testid="loader" {...props} />
49+
}

site/src/components/UsersTable/UsersTableBody.tsx

+29-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import * as TypesGen from "../../api/typesGenerated"
1010
import { combineClasses } from "../../utils/combineClasses"
1111
import { AvatarData } from "../AvatarData/AvatarData"
1212
import { EmptyState } from "../EmptyState/EmptyState"
13-
import { TableLoaderSkeleton } from "../TableLoader/TableLoader"
13+
import {
14+
TableLoaderSkeleton,
15+
TableRowSkeleton,
16+
} from "../TableLoader/TableLoader"
1417
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
1518
import { EditRolesButton } from "components/EditRolesButton/EditRolesButton"
1619
import { Stack } from "components/Stack/Stack"
@@ -23,6 +26,8 @@ import GitHub from "@mui/icons-material/GitHub"
2326
import PasswordOutlined from "@mui/icons-material/PasswordOutlined"
2427
import relativeTime from "dayjs/plugin/relativeTime"
2528
import ShieldOutlined from "@mui/icons-material/ShieldOutlined"
29+
import Skeleton from "@mui/material/Skeleton"
30+
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"
2631

2732
dayjs.extend(relativeTime)
2833

@@ -91,7 +96,29 @@ export const UsersTableBody: FC<
9196
return (
9297
<ChooseOne>
9398
<Cond condition={Boolean(isLoading)}>
94-
<TableLoaderSkeleton columns={canEditUsers ? 5 : 4} useAvatarData />
99+
<TableLoaderSkeleton>
100+
<TableRowSkeleton>
101+
<TableCell>
102+
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
103+
<AvatarDataSkeleton />
104+
</Box>
105+
</TableCell>
106+
<TableCell>
107+
<Skeleton variant="text" width="25%" />
108+
</TableCell>
109+
<TableCell>
110+
<Skeleton variant="text" width="25%" />
111+
</TableCell>
112+
<TableCell>
113+
<Skeleton variant="text" width="25%" />
114+
</TableCell>
115+
{canEditUsers && (
116+
<TableCell>
117+
<Skeleton variant="text" width="25%" />
118+
</TableCell>
119+
)}
120+
</TableRowSkeleton>
121+
</TableLoaderSkeleton>
95122
</Cond>
96123
<Cond condition={!users || users.length === 0}>
97124
<ChooseOne>

site/src/pages/GroupsPage/GroupsPageView.tsx

+28-2
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,20 @@ import { AvatarData } from "components/AvatarData/AvatarData"
1515
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
1616
import { EmptyState } from "components/EmptyState/EmptyState"
1717
import { Stack } from "components/Stack/Stack"
18-
import { TableLoaderSkeleton } from "components/TableLoader/TableLoader"
18+
import {
19+
TableLoaderSkeleton,
20+
TableRowSkeleton,
21+
} from "components/TableLoader/TableLoader"
1922
import { UserAvatar } from "components/UserAvatar/UserAvatar"
2023
import { FC } from "react"
2124
import { Link as RouterLink, useNavigate } from "react-router-dom"
2225
import { Paywall } from "components/Paywall/Paywall"
2326
import { Group } from "api/typesGenerated"
2427
import { GroupAvatar } from "components/GroupAvatar/GroupAvatar"
2528
import { docs } from "utils/docs"
29+
import Skeleton from "@mui/material/Skeleton"
30+
import { Box } from "@mui/system"
31+
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"
2632

2733
export type GroupsPageViewProps = {
2834
groups: Group[] | undefined
@@ -83,7 +89,7 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
8389
<TableBody>
8490
<ChooseOne>
8591
<Cond condition={isLoading}>
86-
<TableLoaderSkeleton columns={3} useAvatarData />
92+
<TableLoader />
8793
</Cond>
8894

8995
<Cond condition={isEmpty}>
@@ -184,6 +190,26 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
184190
)
185191
}
186192

193+
const TableLoader = () => {
194+
return (
195+
<TableLoaderSkeleton>
196+
<TableRowSkeleton>
197+
<TableCell>
198+
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
199+
<AvatarDataSkeleton />
200+
</Box>
201+
</TableCell>
202+
<TableCell>
203+
<Skeleton variant="text" width="25%" />
204+
</TableCell>
205+
<TableCell>
206+
<Skeleton variant="text" width="25%" />
207+
</TableCell>
208+
</TableRowSkeleton>
209+
</TableLoaderSkeleton>
210+
)
211+
}
212+
187213
const useStyles = makeStyles((theme) => ({
188214
clickableTableRow: {
189215
cursor: "pointer",

site/src/pages/TemplatesPage/TemplatesPageView.tsx

+34-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import {
2424
PageHeaderTitle,
2525
} from "../../components/PageHeader/PageHeader"
2626
import { Stack } from "../../components/Stack/Stack"
27-
import { TableLoaderSkeleton } from "../../components/TableLoader/TableLoader"
27+
import {
28+
TableLoaderSkeleton,
29+
TableRowSkeleton,
30+
} from "../../components/TableLoader/TableLoader"
2831
import {
2932
HelpTooltip,
3033
HelpTooltipLink,
@@ -42,6 +45,9 @@ import ArrowForwardOutlined from "@mui/icons-material/ArrowForwardOutlined"
4245
import { Avatar } from "components/Avatar/Avatar"
4346
import { ErrorAlert } from "components/Alert/ErrorAlert"
4447
import { docs } from "utils/docs"
48+
import Skeleton from "@mui/material/Skeleton"
49+
import { Box } from "@mui/system"
50+
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"
4551

4652
export const Language = {
4753
developerCount: (activeCount: number): string => {
@@ -196,7 +202,7 @@ export const TemplatesPageView: FC<
196202
</TableHead>
197203
<TableBody>
198204
<Maybe condition={isLoading}>
199-
<TableLoaderSkeleton columns={5} useAvatarData />
205+
<TableLoader />
200206
</Maybe>
201207

202208
<ChooseOne>
@@ -222,6 +228,32 @@ export const TemplatesPageView: FC<
222228
)
223229
}
224230

231+
const TableLoader = () => {
232+
return (
233+
<TableLoaderSkeleton>
234+
<TableRowSkeleton>
235+
<TableCell>
236+
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
237+
<AvatarDataSkeleton />
238+
</Box>
239+
</TableCell>
240+
<TableCell>
241+
<Skeleton variant="text" width="25%" />
242+
</TableCell>
243+
<TableCell>
244+
<Skeleton variant="text" width="25%" />
245+
</TableCell>
246+
<TableCell>
247+
<Skeleton variant="text" width="25%" />
248+
</TableCell>
249+
<TableCell>
250+
<Skeleton variant="text" width="25%" />
251+
</TableCell>
252+
</TableRowSkeleton>
253+
</TableLoaderSkeleton>
254+
)
255+
}
256+
225257
const useStyles = makeStyles((theme) => ({
226258
templateIconWrapper: {
227259
// Same size then the avatar component

site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe("WorkspacesPage", () => {
6363

6464
await user.click(getWorkspaceCheckbox(workspaces[0]))
6565
await user.click(getWorkspaceCheckbox(workspaces[1]))
66-
await user.click(screen.getByRole("button", { name: /delete all/i }))
66+
await user.click(screen.getByRole("button", { name: /delete selected/i }))
6767
await user.type(screen.getByLabelText(/type delete to confirm/i), "DELETE")
6868
await user.click(screen.getByTestId("confirm-button"))
6969

site/src/pages/WorkspacesPage/WorkspacesPage.tsx

+5-6
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,9 @@ const WorkspacesPage: FC = () => {
6868
const [checkedWorkspaces, setCheckedWorkspaces] = useState<Workspace[]>([])
6969
const [isDeletingAll, setIsDeletingAll] = useState(false)
7070
const [urlSearchParams] = searchParamsResult
71-
const dashboard = useDashboard()
72-
const isWorkspaceBatchActionsEnabled =
73-
dashboard.experiments.includes("workspaces_batch_actions") ||
74-
process.env.NODE_ENV === "development"
71+
const { entitlements } = useDashboard()
72+
const canCheckWorkspaces =
73+
entitlements.features["workspace_batch_actions"].enabled
7574

7675
// We want to uncheck the selected workspaces always when the url changes
7776
// because of filtering or pagination
@@ -86,9 +85,9 @@ const WorkspacesPage: FC = () => {
8685
</Helmet>
8786

8887
<WorkspacesPageView
89-
isWorkspaceBatchActionsEnabled={isWorkspaceBatchActionsEnabled}
9088
checkedWorkspaces={checkedWorkspaces}
9189
onCheckChange={setCheckedWorkspaces}
90+
canCheckWorkspaces={canCheckWorkspaces}
9291
workspaces={data?.workspaces}
9392
dormantWorkspaces={dormantWorkspaces}
9493
error={error}
@@ -198,7 +197,7 @@ const BatchDeleteConfirmation = ({
198197
const confirmDeletion = async () => {
199198
setConfirmError(false)
200199

201-
if (confirmValue.toLowerCase() !== "delete") {
200+
if (confirmValue !== "DELETE") {
202201
setConfirmError(true)
203202
return
204203
}

0 commit comments

Comments
 (0)