Skip to content

Commit 17664f4

Browse files
refactor: update provisioners page to match the new design (#17232)
**Demo** https://github.com/user-attachments/assets/b880326c-7e94-4778-8166-91af7699901e Closes #17221
1 parent 900eb25 commit 17664f4

22 files changed

+853
-463
lines changed

site/src/components/StatusIndicator/StatusIndicator.tsx

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type VariantProps, cva } from "class-variance-authority";
2-
import { type FC, createContext, useContext } from "react";
2+
import { type FC, createContext, forwardRef, useContext } from "react";
33
import { cn } from "utils/cn";
44

55
const statusIndicatorVariants = cva(
@@ -33,21 +33,19 @@ export interface StatusIndicatorProps
3333
extends React.HTMLAttributes<HTMLDivElement>,
3434
StatusIndicatorContextValue {}
3535

36-
export const StatusIndicator: FC<StatusIndicatorProps> = ({
37-
size,
38-
variant,
39-
className,
40-
...props
41-
}) => {
42-
return (
43-
<StatusIndicatorContext.Provider value={{ size, variant }}>
44-
<div
45-
className={cn(statusIndicatorVariants({ variant, size }), className)}
46-
{...props}
47-
/>
48-
</StatusIndicatorContext.Provider>
49-
);
50-
};
36+
export const StatusIndicator = forwardRef<HTMLDivElement, StatusIndicatorProps>(
37+
({ size, variant, className, ...props }, ref) => {
38+
return (
39+
<StatusIndicatorContext.Provider value={{ size, variant }}>
40+
<div
41+
ref={ref}
42+
className={cn(statusIndicatorVariants({ variant, size }), className)}
43+
{...props}
44+
/>
45+
</StatusIndicatorContext.Provider>
46+
);
47+
},
48+
);
5149

5250
const dotVariants = cva("rounded-full inline-block border-4 border-solid", {
5351
variants: {
Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { MockProvisionerJob } from "testHelpers/entities";
33
import { JobStatusIndicator } from "./JobStatusIndicator";
44

55
const meta: Meta<typeof JobStatusIndicator> = {
6-
title: "pages/OrganizationProvisionerJobsPage/JobStatusIndicator",
6+
title: "modules/provisioners/JobStatusIndicator",
77
component: JobStatusIndicator,
88
};
99

@@ -12,65 +12,43 @@ type Story = StoryObj<typeof JobStatusIndicator>;
1212

1313
export const Succeeded: Story = {
1414
args: {
15-
job: {
16-
...MockProvisionerJob,
17-
status: "succeeded",
18-
},
15+
status: "succeeded",
1916
},
2017
};
2118

2219
export const Failed: Story = {
2320
args: {
24-
job: {
25-
...MockProvisionerJob,
26-
status: "failed",
27-
},
21+
status: "failed",
2822
},
2923
};
3024

3125
export const Pending: Story = {
3226
args: {
33-
job: {
34-
...MockProvisionerJob,
35-
status: "pending",
36-
queue_position: 1,
37-
queue_size: 1,
38-
},
27+
status: "pending",
28+
queue: { size: 1, position: 1 },
3929
},
4030
};
4131

4232
export const Running: Story = {
4333
args: {
44-
job: {
45-
...MockProvisionerJob,
46-
status: "running",
47-
},
34+
status: "running",
4835
},
4936
};
5037

5138
export const Canceling: Story = {
5239
args: {
53-
job: {
54-
...MockProvisionerJob,
55-
status: "canceling",
56-
},
40+
status: "canceling",
5741
},
5842
};
5943

6044
export const Canceled: Story = {
6145
args: {
62-
job: {
63-
...MockProvisionerJob,
64-
status: "canceled",
65-
},
46+
status: "canceled",
6647
},
6748
};
6849

6950
export const Unknown: Story = {
7051
args: {
71-
job: {
72-
...MockProvisionerJob,
73-
status: "unknown",
74-
},
52+
status: "unknown",
7553
},
7654
};
Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ProvisionerJob, ProvisionerJobStatus } from "api/typesGenerated";
1+
import type { ProvisionerJobStatus } from "api/typesGenerated";
22
import {
33
StatusIndicator,
44
StatusIndicatorDot,
@@ -21,18 +21,22 @@ const variantByStatus: Record<
2121
};
2222

2323
type JobStatusIndicatorProps = {
24-
job: ProvisionerJob;
24+
status: ProvisionerJobStatus;
25+
queue?: { size: number; position: number };
2526
};
2627

27-
export const JobStatusIndicator: FC<JobStatusIndicatorProps> = ({ job }) => {
28+
export const JobStatusIndicator: FC<JobStatusIndicatorProps> = ({
29+
status,
30+
queue,
31+
}) => {
2832
return (
29-
<StatusIndicator size="sm" variant={variantByStatus[job.status]}>
33+
<StatusIndicator size="sm" variant={variantByStatus[status]}>
3034
<StatusIndicatorDot />
31-
<span className="[&:first-letter]:uppercase">{job.status}</span>
32-
{job.status === "failed" && (
35+
<span className="[&:first-letter]:uppercase">{status}</span>
36+
{status === "failed" && (
3337
<TriangleAlertIcon className="size-icon-xs p-[1px]" />
3438
)}
35-
{job.status === "pending" && `(${job.queue_position}/${job.queue_size})`}
39+
{status === "pending" && queue && `(${queue.position}/${queue.size})`}
3640
</StatusIndicator>
3741
);
3842
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import {
3+
ProvisionerTag,
4+
ProvisionerTags,
5+
ProvisionerTruncateTags,
6+
} from "./ProvisionerTags";
7+
8+
const meta: Meta = {
9+
title: "modules/provisioners/ProvisionerTags",
10+
};
11+
12+
export default meta;
13+
type Story = StoryObj;
14+
15+
export const Tag: Story = {
16+
render: () => {
17+
return <ProvisionerTag label="cluster" value="dogfood-v2" />;
18+
},
19+
};
20+
21+
export const Tags: Story = {
22+
render: () => {
23+
return (
24+
<ProvisionerTags>
25+
<ProvisionerTag label="cluster" value="dogfood-v2" />
26+
<ProvisionerTag label="env" value="gke" />
27+
<ProvisionerTag label="scope" value="organization" />
28+
</ProvisionerTags>
29+
);
30+
},
31+
};
32+
33+
export const TruncateTags: Story = {
34+
render: () => {
35+
return (
36+
<ProvisionerTruncateTags
37+
tags={{
38+
cluster: "dogfood-v2",
39+
env: "gke",
40+
scope: "organization",
41+
}}
42+
/>
43+
);
44+
},
45+
};
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Badge } from "components/Badge/Badge";
22
import type { FC, HTMLProps } from "react";
33
import { cn } from "utils/cn";
44

5-
export const Tags: FC<HTMLProps<HTMLDivElement>> = ({
5+
export const ProvisionerTags: FC<HTMLProps<HTMLDivElement>> = ({
66
className,
77
...props
88
}) => {
@@ -14,12 +14,12 @@ export const Tags: FC<HTMLProps<HTMLDivElement>> = ({
1414
);
1515
};
1616

17-
type TagProps = {
17+
type ProvisionerTagProps = {
1818
label: string;
1919
value?: string;
2020
};
2121

22-
export const Tag: FC<TagProps> = ({ label, value }) => {
22+
export const ProvisionerTag: FC<ProvisionerTagProps> = ({ label, value }) => {
2323
return (
2424
<Badge size="sm" className="whitespace-nowrap">
2525
[{label}
@@ -28,11 +28,11 @@ export const Tag: FC<TagProps> = ({ label, value }) => {
2828
);
2929
};
3030

31-
type TagsProps = {
31+
type ProvisionerTagsProps = {
3232
tags: Record<string, string>;
3333
};
3434

35-
export const TruncateTags: FC<TagsProps> = ({ tags }) => {
35+
export const ProvisionerTruncateTags: FC<ProvisionerTagsProps> = ({ tags }) => {
3636
const keys = Object.keys(tags);
3737

3838
if (keys.length === 0) {
@@ -44,9 +44,9 @@ export const TruncateTags: FC<TagsProps> = ({ tags }) => {
4444
const remainderCount = keys.length - 1;
4545

4646
return (
47-
<Tags>
48-
<Tag label={firstKey} value={firstValue} />
47+
<ProvisionerTags>
48+
<ProvisionerTag label={firstKey} value={firstValue} />
4949
{remainderCount > 0 && <Badge size="sm">+{remainderCount}</Badge>}
50-
</Tags>
50+
</ProvisionerTags>
5151
);
5252
};

site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.tsx

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
import type { ProvisionerJob } from "api/typesGenerated";
22
import { Avatar } from "components/Avatar/Avatar";
33
import { Badge } from "components/Badge/Badge";
4+
import { Button } from "components/Button/Button";
45
import { TableCell, TableRow } from "components/Table/Table";
56
import {
67
ChevronDownIcon,
78
ChevronRightIcon,
89
TriangleAlertIcon,
910
} from "lucide-react";
11+
import { JobStatusIndicator } from "modules/provisioners/JobStatusIndicator";
12+
import {
13+
ProvisionerTag,
14+
ProvisionerTags,
15+
ProvisionerTruncateTags,
16+
} from "modules/provisioners/ProvisionerTags";
1017
import { type FC, useState } from "react";
1118
import { cn } from "utils/cn";
1219
import { relativeTime } from "utils/time";
1320
import { CancelJobButton } from "./CancelJobButton";
14-
import { JobStatusIndicator } from "./JobStatusIndicator";
15-
import { Tag, Tags, TruncateTags } from "./Tags";
1621

1722
type JobRowProps = {
1823
job: ProvisionerJob;
@@ -21,32 +26,32 @@ type JobRowProps = {
2126
export const JobRow: FC<JobRowProps> = ({ job }) => {
2227
const metadata = job.metadata;
2328
const [isOpen, setIsOpen] = useState(false);
29+
const queue = {
30+
size: job.queue_size,
31+
position: job.queue_position,
32+
};
2433

2534
return (
2635
<>
2736
<TableRow key={job.id}>
2837
<TableCell>
29-
<button
38+
<Button
39+
variant="subtle"
40+
size="sm"
3041
className={cn([
31-
"flex items-center gap-1 p-0 bg-transparent border-0 text-inherit text-xs cursor-pointer",
32-
"transition-colors hover:text-content-primary font-medium whitespace-nowrap",
3342
isOpen && "text-content-primary",
43+
"p-0 h-auto min-w-0 align-middle",
3444
])}
35-
type="button"
3645
onClick={() => {
3746
setIsOpen((v) => !v);
3847
}}
3948
>
40-
{isOpen ? (
41-
<ChevronDownIcon className="size-icon-sm p-0.5" />
42-
) : (
43-
<ChevronRightIcon className="size-icon-sm p-0.5" />
44-
)}
49+
{isOpen ? <ChevronDownIcon /> : <ChevronRightIcon />}
4550
<span className="sr-only">({isOpen ? "Hide" : "Show more"})</span>
46-
<span className="[&:first-letter]:uppercase">
51+
<span className="block first-letter:uppercase">
4752
{relativeTime(new Date(job.created_at))}
4853
</span>
49-
</button>
54+
</Button>
5055
</TableCell>
5156
<TableCell>
5257
<Badge size="sm">{job.type}</Badge>
@@ -68,10 +73,10 @@ export const JobRow: FC<JobRowProps> = ({ job }) => {
6873
)}
6974
</TableCell>
7075
<TableCell>
71-
<TruncateTags tags={job.tags} />
76+
<ProvisionerTruncateTags tags={job.tags} />
7277
</TableCell>
7378
<TableCell>
74-
<JobStatusIndicator job={job} />
79+
<JobStatusIndicator status={job.status} queue={queue} />
7580
</TableCell>
7681
<TableCell className="text-right">
7782
<CancelJobButton job={job} />
@@ -125,11 +130,11 @@ export const JobRow: FC<JobRowProps> = ({ job }) => {
125130

126131
<dt>Tags:</dt>
127132
<dd>
128-
<Tags>
133+
<ProvisionerTags>
129134
{Object.entries(job.tags).map(([key, value]) => (
130-
<Tag key={key} label={key} value={value} />
135+
<ProvisionerTag key={key} label={key} value={value} />
131136
))}
132-
</Tags>
137+
</ProvisionerTags>
133138
</dd>
134139
</dl>
135140
</TableCell>

0 commit comments

Comments
 (0)