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

Skip to content

Commit 5ffe0c8

Browse files
BrunoQuaresmaEdwardAngert
authored andcommitted
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 47628c9 commit 5ffe0c8

21 files changed

+455
-515
lines changed

site/src/components/Badge/Badge.tsx

+1-1
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

+3-2
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

+12-5
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

+2-2
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

+3-4
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
},
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

+20-99
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
)}
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)