Skip to content

feat(site): make workspace batch deletion GA #9313

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Aug 30, 2023
6 changes: 2 additions & 4 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions codersdk/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const (
FeatureAdvancedTemplateScheduling FeatureName = "advanced_template_scheduling"
FeatureTemplateAutostopRequirement FeatureName = "template_autostop_requirement"
FeatureWorkspaceProxy FeatureName = "workspace_proxy"
FeatureWorkspaceBatchActions FeatureName = "workspace_batch_actions"
)

// FeatureNames must be kept in-sync with the Feature enum above.
Expand All @@ -64,6 +65,7 @@ var FeatureNames = []FeatureName{
FeatureAdvancedTemplateScheduling,
FeatureWorkspaceProxy,
FeatureUserRoleManagement,
FeatureWorkspaceBatchActions,
}

// Humanize returns the feature name in a human-readable format.
Expand Down Expand Up @@ -1941,9 +1943,6 @@ const (
// Deployment health page
ExperimentDeploymentHealthPage Experiment = "deployment_health_page"

// Workspaces batch actions
ExperimentWorkspacesBatchActions Experiment = "workspaces_batch_actions"

// Add new experiments here!
// ExperimentExample Experiment = "example"
)
Expand All @@ -1954,7 +1953,6 @@ const (
// not be included here and will be essentially hidden.
var ExperimentsAll = Experiments{
ExperimentDeploymentHealthPage,
ExperimentWorkspacesBatchActions,
}

// Experiments is a list of experiments that are enabled for the deployment.
Expand Down
1 change: 0 additions & 1 deletion docs/api/schemas.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions site/src/api/typesGenerated.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 20 additions & 30 deletions site/src/components/TableLoader/TableLoader.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { makeStyles } from "@mui/styles"
import TableCell from "@mui/material/TableCell"
import TableRow from "@mui/material/TableRow"
import Skeleton from "@mui/material/Skeleton"
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"
import { FC } from "react"
import TableRow, { TableRowProps } from "@mui/material/TableRow"
import { FC, ReactNode, cloneElement, isValidElement } from "react"
import { Loader } from "../Loader/Loader"

export const TableLoader: FC = () => {
Expand All @@ -25,35 +23,27 @@ const useStyles = makeStyles((theme) => ({
},
}))

export const TableLoaderSkeleton: FC<{
columns: number
export const TableLoaderSkeleton = ({
rows = 4,
children,
}: {
rows?: number
useAvatarData?: boolean
}> = ({ columns, rows = 4, useAvatarData = false }) => {
const placeholderColumns = Array(columns).fill(undefined)
const placeholderRows = Array(rows).fill(undefined)

children: ReactNode
}) => {
if (!isValidElement(children)) {
throw new Error(
"TableLoaderSkeleton children must be a valid React element",
)
}
return (
<>
{placeholderRows.map((_, rowIndex) => (
<TableRow key={rowIndex} role="progressbar" data-testid="loader">
{placeholderColumns.map((_, columnIndex) => {
if (useAvatarData && columnIndex === 0) {
return (
<TableCell key={columnIndex}>
<AvatarDataSkeleton />
</TableCell>
)
}

return (
<TableCell key={columnIndex}>
<Skeleton variant="text" width="25%" />
</TableCell>
)
})}
</TableRow>
))}
{Array.from({ length: rows }, (_, i) =>
cloneElement(children, { key: i }),
)}
</>
)
}

export const TableRowSkeleton = (props: TableRowProps) => {
return <TableRow role="progressbar" data-testid="loader" {...props} />
}
31 changes: 29 additions & 2 deletions site/src/components/UsersTable/UsersTableBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import * as TypesGen from "../../api/typesGenerated"
import { combineClasses } from "../../utils/combineClasses"
import { AvatarData } from "../AvatarData/AvatarData"
import { EmptyState } from "../EmptyState/EmptyState"
import { TableLoaderSkeleton } from "../TableLoader/TableLoader"
import {
TableLoaderSkeleton,
TableRowSkeleton,
} from "../TableLoader/TableLoader"
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
import { EditRolesButton } from "components/EditRolesButton/EditRolesButton"
import { Stack } from "components/Stack/Stack"
Expand All @@ -23,6 +26,8 @@ import GitHub from "@mui/icons-material/GitHub"
import PasswordOutlined from "@mui/icons-material/PasswordOutlined"
import relativeTime from "dayjs/plugin/relativeTime"
import ShieldOutlined from "@mui/icons-material/ShieldOutlined"
import Skeleton from "@mui/material/Skeleton"
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"

dayjs.extend(relativeTime)

Expand Down Expand Up @@ -91,7 +96,29 @@ export const UsersTableBody: FC<
return (
<ChooseOne>
<Cond condition={Boolean(isLoading)}>
<TableLoaderSkeleton columns={canEditUsers ? 5 : 4} useAvatarData />
<TableLoaderSkeleton>
<TableRowSkeleton>
<TableCell>
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
<AvatarDataSkeleton />
</Box>
</TableCell>
<TableCell>
<Skeleton variant="text" width="25%" />
</TableCell>
<TableCell>
<Skeleton variant="text" width="25%" />
</TableCell>
<TableCell>
<Skeleton variant="text" width="25%" />
</TableCell>
{canEditUsers && (
<TableCell>
<Skeleton variant="text" width="25%" />
</TableCell>
)}
</TableRowSkeleton>
</TableLoaderSkeleton>
</Cond>
<Cond condition={!users || users.length === 0}>
<ChooseOne>
Expand Down
30 changes: 28 additions & 2 deletions site/src/pages/GroupsPage/GroupsPageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@ import { AvatarData } from "components/AvatarData/AvatarData"
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
import { EmptyState } from "components/EmptyState/EmptyState"
import { Stack } from "components/Stack/Stack"
import { TableLoaderSkeleton } from "components/TableLoader/TableLoader"
import {
TableLoaderSkeleton,
TableRowSkeleton,
} from "components/TableLoader/TableLoader"
import { UserAvatar } from "components/UserAvatar/UserAvatar"
import { FC } from "react"
import { Link as RouterLink, useNavigate } from "react-router-dom"
import { Paywall } from "components/Paywall/Paywall"
import { Group } from "api/typesGenerated"
import { GroupAvatar } from "components/GroupAvatar/GroupAvatar"
import { docs } from "utils/docs"
import Skeleton from "@mui/material/Skeleton"
import { Box } from "@mui/system"
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"

export type GroupsPageViewProps = {
groups: Group[] | undefined
Expand Down Expand Up @@ -83,7 +89,7 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
<TableBody>
<ChooseOne>
<Cond condition={isLoading}>
<TableLoaderSkeleton columns={3} useAvatarData />
<TableLoader />
</Cond>

<Cond condition={isEmpty}>
Expand Down Expand Up @@ -184,6 +190,26 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
)
}

const TableLoader = () => {
return (
<TableLoaderSkeleton>
<TableRowSkeleton>
<TableCell>
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
<AvatarDataSkeleton />
</Box>
</TableCell>
<TableCell>
<Skeleton variant="text" width="25%" />
</TableCell>
<TableCell>
<Skeleton variant="text" width="25%" />
</TableCell>
</TableRowSkeleton>
</TableLoaderSkeleton>
)
}

const useStyles = makeStyles((theme) => ({
clickableTableRow: {
cursor: "pointer",
Expand Down
36 changes: 34 additions & 2 deletions site/src/pages/TemplatesPage/TemplatesPageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import {
PageHeaderTitle,
} from "../../components/PageHeader/PageHeader"
import { Stack } from "../../components/Stack/Stack"
import { TableLoaderSkeleton } from "../../components/TableLoader/TableLoader"
import {
TableLoaderSkeleton,
TableRowSkeleton,
} from "../../components/TableLoader/TableLoader"
import {
HelpTooltip,
HelpTooltipLink,
Expand All @@ -42,6 +45,9 @@ import ArrowForwardOutlined from "@mui/icons-material/ArrowForwardOutlined"
import { Avatar } from "components/Avatar/Avatar"
import { ErrorAlert } from "components/Alert/ErrorAlert"
import { docs } from "utils/docs"
import Skeleton from "@mui/material/Skeleton"
import { Box } from "@mui/system"
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"

export const Language = {
developerCount: (activeCount: number): string => {
Expand Down Expand Up @@ -196,7 +202,7 @@ export const TemplatesPageView: FC<
</TableHead>
<TableBody>
<Maybe condition={isLoading}>
<TableLoaderSkeleton columns={5} useAvatarData />
<TableLoader />
</Maybe>

<ChooseOne>
Expand All @@ -222,6 +228,32 @@ export const TemplatesPageView: FC<
)
}

const TableLoader = () => {
return (
<TableLoaderSkeleton>
<TableRowSkeleton>
<TableCell>
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
<AvatarDataSkeleton />
</Box>
</TableCell>
<TableCell>
<Skeleton variant="text" width="25%" />
</TableCell>
<TableCell>
<Skeleton variant="text" width="25%" />
</TableCell>
<TableCell>
<Skeleton variant="text" width="25%" />
</TableCell>
<TableCell>
<Skeleton variant="text" width="25%" />
</TableCell>
</TableRowSkeleton>
</TableLoaderSkeleton>
)
}

const useStyles = makeStyles((theme) => ({
templateIconWrapper: {
// Same size then the avatar component
Expand Down
2 changes: 1 addition & 1 deletion site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe("WorkspacesPage", () => {

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

Expand Down
11 changes: 5 additions & 6 deletions site/src/pages/WorkspacesPage/WorkspacesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,9 @@ const WorkspacesPage: FC = () => {
const [checkedWorkspaces, setCheckedWorkspaces] = useState<Workspace[]>([])
const [isDeletingAll, setIsDeletingAll] = useState(false)
const [urlSearchParams] = searchParamsResult
const dashboard = useDashboard()
const isWorkspaceBatchActionsEnabled =
dashboard.experiments.includes("workspaces_batch_actions") ||
process.env.NODE_ENV === "development"
const { entitlements } = useDashboard()
const canCheckWorkspaces =
entitlements.features["workspace_batch_actions"].enabled

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

<WorkspacesPageView
isWorkspaceBatchActionsEnabled={isWorkspaceBatchActionsEnabled}
checkedWorkspaces={checkedWorkspaces}
onCheckChange={setCheckedWorkspaces}
canCheckWorkspaces={canCheckWorkspaces}
workspaces={data?.workspaces}
dormantWorkspaces={dormantWorkspaces}
error={error}
Expand Down Expand Up @@ -198,7 +197,7 @@ const BatchDeleteConfirmation = ({
const confirmDeletion = async () => {
setConfirmError(false)

if (confirmValue.toLowerCase() !== "delete") {
if (confirmValue !== "DELETE") {
setConfirmError(true)
return
}
Expand Down
Loading