Skip to content

Commit 564b387

Browse files
feat: add provisioner jobs into the UI (#16867)
- Add provisioner jobs back, but as a sub page of the organization settings - Add missing storybook tests to the components Related to #15192.
1 parent cf7d143 commit 564b387

21 files changed

+455
-515
lines changed

site/src/components/Badge/Badge.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const badgeVariants = cva(
1212
variants: {
1313
variant: {
1414
default:
15-
"border-transparent bg-surface-secondary text-content-secondary shadow hover:bg-surface-tertiary",
15+
"border-transparent bg-surface-secondary text-content-secondary shadow",
1616
},
1717
size: {
1818
sm: "text-2xs font-regular",

site/src/modules/management/OrganizationSettingsLayout.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const OrganizationSettingsContext = createContext<
2424
OrganizationSettingsValue | undefined
2525
>(undefined);
2626

27-
type OrganizationSettingsValue = Readonly<{
27+
export type OrganizationSettingsValue = Readonly<{
2828
organizations: readonly Organization[];
2929
organizationPermissionsByOrganizationId: Record<
3030
string,
@@ -36,9 +36,10 @@ type OrganizationSettingsValue = Readonly<{
3636

3737
export const useOrganizationSettings = (): OrganizationSettingsValue => {
3838
const context = useContext(OrganizationSettingsContext);
39+
3940
if (!context) {
4041
throw new Error(
41-
"useOrganizationSettings should be used inside of OrganizationSettingsLayout",
42+
"useOrganizationSettings should be used inside of OrganizationSettingsLayout or with the default values in case of testing.",
4243
);
4344
}
4445

site/src/modules/management/OrganizationSidebarView.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,18 @@ const OrganizationSettingsNavigation: FC<
186186
)}
187187
{orgPermissions.viewProvisioners &&
188188
orgPermissions.viewProvisionerJobs && (
189-
<SettingsSidebarNavItem
190-
href={urlForSubpage(organization.name, "provisioners")}
191-
>
192-
Provisioners
193-
</SettingsSidebarNavItem>
189+
<>
190+
<SettingsSidebarNavItem
191+
href={urlForSubpage(organization.name, "provisioners")}
192+
>
193+
Provisioners
194+
</SettingsSidebarNavItem>
195+
<SettingsSidebarNavItem
196+
href={urlForSubpage(organization.name, "provisioner-jobs")}
197+
>
198+
Provisioner Jobs
199+
</SettingsSidebarNavItem>
200+
</>
194201
)}
195202
{orgPermissions.viewIdpSyncSettings && (
196203
<SettingsSidebarNavItem

site/src/pages/OrganizationSettingsPage/ProvisionersPage/CancelJobButton.stories.tsx renamed to site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/CancelJobButton.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { MockProvisionerJob } from "testHelpers/entities";
44
import { CancelJobButton } from "./CancelJobButton";
55

66
const meta: Meta<typeof CancelJobButton> = {
7-
title: "pages/OrganizationSettingsPage/ProvisionersPage/CancelJobButton",
7+
title: "pages/OrganizationProvisionerJobsPage/CancelJobButton",
88
component: CancelJobButton,
99
args: {
1010
job: {
@@ -28,7 +28,7 @@ export const NotCancellable: Story = {
2828
},
2929
};
3030

31-
export const OnClick: Story = {
31+
export const ConfirmOnClick: Story = {
3232
parameters: {
3333
chromatic: { disableSnapshot: true },
3434
},

site/src/pages/OrganizationSettingsPage/ProvisionersPage/CancelJobConfirmationDialog.stories.tsx renamed to site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/CancelJobConfirmationDialog.stories.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import { withGlobalSnackbar } from "testHelpers/storybook";
66
import { CancelJobConfirmationDialog } from "./CancelJobConfirmationDialog";
77

88
const meta: Meta<typeof CancelJobConfirmationDialog> = {
9-
title:
10-
"pages/OrganizationSettingsPage/ProvisionersPage/CancelJobConfirmationDialog",
9+
title: "pages/OrganizationProvisionerJobsPage/CancelJobConfirmationDialog",
1110
component: CancelJobConfirmationDialog,
1211
args: {
1312
open: true,
@@ -40,7 +39,7 @@ export const OnCancel: Story = {
4039
},
4140
};
4241

43-
export const onConfirmSuccess: Story = {
42+
export const OnConfirmSuccess: Story = {
4443
parameters: {
4544
chromatic: { disableSnapshot: true },
4645
},
@@ -60,7 +59,7 @@ export const onConfirmSuccess: Story = {
6059
},
6160
};
6261

63-
export const onConfirmFailure: Story = {
62+
export const OnConfirmFailure: Story = {
6463
parameters: {
6564
chromatic: { disableSnapshot: true },
6665
},
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { expect, userEvent, waitFor, within } from "@storybook/test";
3+
import { Table, TableBody } from "components/Table/Table";
4+
import { MockProvisionerJob } from "testHelpers/entities";
5+
import { daysAgo } from "utils/time";
6+
import { JobRow } from "./JobRow";
7+
8+
const meta: Meta<typeof JobRow> = {
9+
title: "pages/OrganizationProvisionerJobsPage/JobRow",
10+
component: JobRow,
11+
args: {
12+
job: {
13+
...MockProvisionerJob,
14+
created_at: daysAgo(2),
15+
},
16+
},
17+
render: (args) => {
18+
return (
19+
<Table>
20+
<TableBody>
21+
<JobRow {...args} />
22+
</TableBody>
23+
</Table>
24+
);
25+
},
26+
};
27+
28+
export default meta;
29+
type Story = StoryObj<typeof JobRow>;
30+
31+
export const Close: Story = {};
32+
33+
export const OpenOnClick: Story = {
34+
play: async ({ canvasElement, args }) => {
35+
const canvas = within(canvasElement);
36+
const showMoreButton = canvas.getByRole("button", { name: /show more/i });
37+
38+
await userEvent.click(showMoreButton);
39+
40+
const jobId = canvas.getByText(args.job.id);
41+
expect(jobId).toBeInTheDocument();
42+
},
43+
};
44+
45+
export const HideOnClick: Story = {
46+
play: async ({ canvasElement, args }) => {
47+
const canvas = within(canvasElement);
48+
49+
const showMoreButton = canvas.getByRole("button", { name: /show more/i });
50+
await userEvent.click(showMoreButton);
51+
52+
const hideButton = canvas.getByRole("button", { name: /hide/i });
53+
await userEvent.click(hideButton);
54+
55+
const jobId = canvas.queryByText(args.job.id);
56+
expect(jobId).not.toBeInTheDocument();
57+
},
58+
};

site/src/pages/OrganizationSettingsPage/ProvisionersPage/ProvisionerJobsPage.tsx renamed to site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.tsx

Lines changed: 20 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,24 @@
1-
import { provisionerJobs } from "api/queries/organizations";
21
import type { ProvisionerJob } from "api/typesGenerated";
32
import { Avatar } from "components/Avatar/Avatar";
43
import { Badge } from "components/Badge/Badge";
5-
import { Button } from "components/Button/Button";
6-
import { EmptyState } from "components/EmptyState/EmptyState";
7-
import { Link } from "components/Link/Link";
8-
import { Loader } from "components/Loader/Loader";
9-
import {
10-
Table,
11-
TableBody,
12-
TableCell,
13-
TableHead,
14-
TableHeader,
15-
TableRow,
16-
} from "components/Table/Table";
4+
import { TableCell, TableRow } from "components/Table/Table";
175
import {
186
ChevronDownIcon,
197
ChevronRightIcon,
208
TriangleAlertIcon,
219
} from "lucide-react";
2210
import { type FC, useState } from "react";
23-
import { useQuery } from "react-query";
2411
import { cn } from "utils/cn";
25-
import { docs } from "utils/docs";
2612
import { relativeTime } from "utils/time";
2713
import { CancelJobButton } from "./CancelJobButton";
28-
import { DataGrid } from "./DataGrid";
2914
import { JobStatusIndicator } from "./JobStatusIndicator";
3015
import { Tag, Tags, TruncateTags } from "./Tags";
3116

32-
type ProvisionerJobsPageProps = {
33-
orgId: string;
34-
};
35-
36-
export const ProvisionerJobsPage: FC<ProvisionerJobsPageProps> = ({
37-
orgId,
38-
}) => {
39-
const {
40-
data: jobs,
41-
isLoadingError,
42-
refetch,
43-
} = useQuery(provisionerJobs(orgId));
44-
45-
return (
46-
<section className="flex flex-col gap-8">
47-
<h2 className="sr-only">Provisioner jobs</h2>
48-
<p className="text-sm text-content-secondary m-0 mt-2">
49-
Provisioner Jobs are the individual tasks assigned to Provisioners when
50-
the workspaces are being built.{" "}
51-
<Link href={docs("/admin/provisioners")}>View docs</Link>
52-
</p>
53-
54-
<Table>
55-
<TableHeader>
56-
<TableRow>
57-
<TableHead>Created</TableHead>
58-
<TableHead>Type</TableHead>
59-
<TableHead>Template</TableHead>
60-
<TableHead>Tags</TableHead>
61-
<TableHead>Status</TableHead>
62-
<TableHead />
63-
</TableRow>
64-
</TableHeader>
65-
<TableBody>
66-
{jobs ? (
67-
jobs.length > 0 ? (
68-
jobs.map((j) => <JobRow key={j.id} job={j} />)
69-
) : (
70-
<TableRow>
71-
<TableCell colSpan={999}>
72-
<EmptyState message="No provisioner jobs found" />
73-
</TableCell>
74-
</TableRow>
75-
)
76-
) : isLoadingError ? (
77-
<TableRow>
78-
<TableCell colSpan={999}>
79-
<EmptyState
80-
message="Error loading the provisioner jobs"
81-
cta={<Button onClick={() => refetch()}>Retry</Button>}
82-
/>
83-
</TableCell>
84-
</TableRow>
85-
) : (
86-
<TableRow>
87-
<TableCell colSpan={999}>
88-
<Loader />
89-
</TableCell>
90-
</TableRow>
91-
)}
92-
</TableBody>
93-
</Table>
94-
</section>
95-
);
96-
};
97-
9817
type JobRowProps = {
9918
job: ProvisionerJob;
10019
};
10120

102-
const JobRow: FC<JobRowProps> = ({ job }) => {
21+
export const JobRow: FC<JobRowProps> = ({ job }) => {
10322
const metadata = job.metadata;
10423
const [isOpen, setIsOpen] = useState(false);
10524

@@ -133,20 +52,16 @@ const JobRow: FC<JobRowProps> = ({ job }) => {
13352
<Badge size="sm">{job.type}</Badge>
13453
</TableCell>
13554
<TableCell>
136-
{job.metadata.template_name ? (
137-
<div className="flex items-center gap-1 whitespace-nowrap">
138-
<Avatar
139-
variant="icon"
140-
src={metadata.template_icon}
141-
fallback={
142-
metadata.template_display_name || metadata.template_name
143-
}
144-
/>
145-
{metadata.template_display_name ?? metadata.template_name}
146-
</div>
147-
) : (
148-
<span className="whitespace-nowrap">Not linked</span>
149-
)}
55+
<div className="flex items-center gap-1 whitespace-nowrap">
56+
<Avatar
57+
variant="icon"
58+
src={metadata.template_icon}
59+
fallback={
60+
metadata.template_display_name || metadata.template_name
61+
}
62+
/>
63+
{metadata.template_display_name || metadata.template_name}
64+
</div>
15065
</TableCell>
15166
<TableCell>
15267
<TruncateTags tags={job.tags} />
@@ -173,7 +88,13 @@ const JobRow: FC<JobRowProps> = ({ job }) => {
17388
<span className="[&:first-letter]:uppercase">{job.error}</span>
17489
</div>
17590
)}
176-
<DataGrid>
91+
<dl
92+
className={cn([
93+
"text-xs text-content-secondary",
94+
"m-0 grid grid-cols-[auto_1fr] gap-x-4 items-center",
95+
"[&_dd]:text-content-primary [&_dd]:font-mono [&_dd]:leading-[22px] [&_dt]:font-medium",
96+
])}
97+
>
17798
<dt>Job ID:</dt>
17899
<dd>{job.id}</dd>
179100

@@ -206,7 +127,7 @@ const JobRow: FC<JobRowProps> = ({ job }) => {
206127
))}
207128
</Tags>
208129
</dd>
209-
</DataGrid>
130+
</dl>
210131
</TableCell>
211132
</TableRow>
212133
)}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { MockProvisionerJob } from "testHelpers/entities";
3+
import { JobStatusIndicator } from "./JobStatusIndicator";
4+
5+
const meta: Meta<typeof JobStatusIndicator> = {
6+
title: "pages/OrganizationProvisionerJobsPage/JobStatusIndicator",
7+
component: JobStatusIndicator,
8+
};
9+
10+
export default meta;
11+
type Story = StoryObj<typeof JobStatusIndicator>;
12+
13+
export const Succeeded: Story = {
14+
args: {
15+
job: {
16+
...MockProvisionerJob,
17+
status: "succeeded",
18+
},
19+
},
20+
};
21+
22+
export const Failed: Story = {
23+
args: {
24+
job: {
25+
...MockProvisionerJob,
26+
status: "failed",
27+
},
28+
},
29+
};
30+
31+
export const Pending: Story = {
32+
args: {
33+
job: {
34+
...MockProvisionerJob,
35+
status: "pending",
36+
queue_position: 1,
37+
queue_size: 1,
38+
},
39+
},
40+
};
41+
42+
export const Running: Story = {
43+
args: {
44+
job: {
45+
...MockProvisionerJob,
46+
status: "running",
47+
},
48+
},
49+
};
50+
51+
export const Canceling: Story = {
52+
args: {
53+
job: {
54+
...MockProvisionerJob,
55+
status: "canceling",
56+
},
57+
},
58+
};
59+
60+
export const Canceled: Story = {
61+
args: {
62+
job: {
63+
...MockProvisionerJob,
64+
status: "canceled",
65+
},
66+
},
67+
};
68+
69+
export const Unknown: Story = {
70+
args: {
71+
job: {
72+
...MockProvisionerJob,
73+
status: "unknown",
74+
},
75+
},
76+
};

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy