Skip to content

Commit 3ab3ef8

Browse files
feat: add link to provisioner jobs and daemons (#17509)
Close #17314 **Demo** https://github.com/user-attachments/assets/db37aa67-4755-4b72-a54d-2c3f0c297b7d **Changes** - Added the `xs` button variant - Display all the daemons - idle and offline - and set a size limit to 100 results (explanation in the demo) - Filter daemons and jobs by ID
1 parent 5ca90ae commit 3ab3ef8

16 files changed

+244
-75
lines changed

site/src/api/api.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,17 @@ export class MissingBuildParameters extends Error {
396396
}
397397

398398
export type GetProvisionerJobsParams = {
399-
status?: TypesGen.ProvisionerJobStatus;
399+
status?: string;
400+
limit?: number;
401+
// IDs separated by comma
402+
ids?: string;
403+
};
404+
405+
export type GetProvisionerDaemonsParams = {
406+
// IDs separated by comma
407+
ids?: string;
408+
// Stringified JSON Object
409+
tags?: string;
400410
limit?: number;
401411
};
402412

@@ -711,22 +721,13 @@ class ApiMethods {
711721
return response.data;
712722
};
713723

714-
/**
715-
* @param organization Can be the organization's ID or name
716-
* @param tags to filter provisioner daemons by.
717-
*/
718724
getProvisionerDaemonsByOrganization = async (
719725
organization: string,
720-
tags?: Record<string, string>,
726+
params?: GetProvisionerDaemonsParams,
721727
): Promise<TypesGen.ProvisionerDaemon[]> => {
722-
const params = new URLSearchParams();
723-
724-
if (tags) {
725-
params.append("tags", JSON.stringify(tags));
726-
}
727-
728728
const response = await this.axios.get<TypesGen.ProvisionerDaemon[]>(
729-
`/api/v2/organizations/${organization}/provisionerdaemons?${params}`,
729+
`/api/v2/organizations/${organization}/provisionerdaemons`,
730+
{ params },
730731
);
731732
return response.data;
732733
};

site/src/api/queries/organizations.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { API, type GetProvisionerJobsParams } from "api/api";
1+
import {
2+
API,
3+
type GetProvisionerDaemonsParams,
4+
type GetProvisionerJobsParams,
5+
} from "api/api";
26
import type {
37
CreateOrganizationRequest,
48
GroupSyncSettings,
@@ -164,16 +168,17 @@ export const organizations = () => {
164168

165169
export const getProvisionerDaemonsKey = (
166170
organization: string,
167-
tags?: Record<string, string>,
168-
) => ["organization", organization, tags, "provisionerDaemons"];
171+
params?: GetProvisionerDaemonsParams,
172+
) => ["organization", organization, "provisionerDaemons", params];
169173

170174
export const provisionerDaemons = (
171175
organization: string,
172-
tags?: Record<string, string>,
176+
params?: GetProvisionerDaemonsParams,
173177
) => {
174178
return {
175-
queryKey: getProvisionerDaemonsKey(organization, tags),
176-
queryFn: () => API.getProvisionerDaemonsByOrganization(organization, tags),
179+
queryKey: getProvisionerDaemonsKey(organization, params),
180+
queryFn: () =>
181+
API.getProvisionerDaemonsByOrganization(organization, params),
177182
};
178183
};
179184

site/src/components/Button/Button.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { forwardRef } from "react";
88
import { cn } from "utils/cn";
99

1010
export const buttonVariants = cva(
11-
`inline-flex items-center justify-center gap-1 whitespace-nowrap
11+
`inline-flex items-center justify-center gap-1 whitespace-nowrap font-sans
1212
border-solid rounded-md transition-colors
1313
text-sm font-semibold font-medium cursor-pointer no-underline
1414
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-content-link
@@ -30,6 +30,7 @@ export const buttonVariants = cva(
3030
size: {
3131
lg: "min-w-20 h-10 px-3 py-2 [&_svg]:size-icon-lg",
3232
sm: "min-w-20 h-8 px-2 py-1.5 text-xs [&_svg]:size-icon-sm",
33+
xs: "min-w-8 py-1 px-2 text-2xs rounded-md",
3334
icon: "size-8 px-1.5 [&_svg]:size-icon-sm",
3435
"icon-lg": "size-10 px-2 [&_svg]:size-icon-lg",
3536
},

site/src/pages/CreateTokenPage/CreateTokenForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ export const CreateTokenForm: FC<CreateTokenFormProps> = ({
119119

120120
{lifetimeDays === "custom" && (
121121
<TextField
122-
data-chromatic="ignore"
123122
type="date"
124123
label="Expires on"
125124
defaultValue={dayjs().add(expDays, "day").format("YYYY-MM-DD")}
@@ -130,6 +129,7 @@ export const CreateTokenForm: FC<CreateTokenFormProps> = ({
130129
setExpDays(lt);
131130
}}
132131
inputProps={{
132+
"data-chromatic": "ignore",
133133
min: dayjs().add(1, "day").format("YYYY-MM-DD"),
134134
max: maxTokenLifetime
135135
? dayjs()

site/src/pages/OrganizationSettingsPage/CustomRolesPage/PermissionPillsList.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const meta: Meta<typeof PermissionPillsList> = {
1515
],
1616
parameters: {
1717
chromatic: {
18-
diffThreshold: 0.5,
18+
diffThreshold: 0.6,
1919
},
2020
},
2121
};

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

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,19 @@ import {
1515
ProvisionerTruncateTags,
1616
} from "modules/provisioners/ProvisionerTags";
1717
import { type FC, useState } from "react";
18+
import { Link as RouterLink } from "react-router-dom";
1819
import { cn } from "utils/cn";
1920
import { relativeTime } from "utils/time";
2021
import { CancelJobButton } from "./CancelJobButton";
2122

2223
type JobRowProps = {
2324
job: ProvisionerJob;
25+
defaultIsOpen: boolean;
2426
};
2527

26-
export const JobRow: FC<JobRowProps> = ({ job }) => {
28+
export const JobRow: FC<JobRowProps> = ({ job, defaultIsOpen = false }) => {
2729
const metadata = job.metadata;
28-
const [isOpen, setIsOpen] = useState(false);
30+
const [isOpen, setIsOpen] = useState(defaultIsOpen);
2931
const queue = {
3032
size: job.queue_size,
3133
position: job.queue_position,
@@ -114,19 +116,36 @@ export const JobRow: FC<JobRowProps> = ({ job }) => {
114116
: "[]"}
115117
</dd>
116118

117-
<dt>Completed by provisioner:</dt>
118-
<dd>{job.worker_id}</dd>
119+
{job.worker_id && (
120+
<>
121+
<dt>Completed by provisioner:</dt>
122+
<dd className="flex items-center gap-2">
123+
<span>{job.worker_id}</span>
124+
<Button size="xs" variant="outline" asChild>
125+
<RouterLink
126+
to={`../provisioners?${new URLSearchParams({ ids: job.worker_id })}`}
127+
>
128+
View provisioner
129+
</RouterLink>
130+
</Button>
131+
</dd>
132+
</>
133+
)}
119134

120135
<dt>Associated workspace:</dt>
121136
<dd>{job.metadata.workspace_name ?? "null"}</dd>
122137

123138
<dt>Creation time:</dt>
124139
<dd data-chromatic="ignore">{job.created_at}</dd>
125140

126-
<dt>Queue:</dt>
127-
<dd>
128-
{job.queue_position}/{job.queue_size}
129-
</dd>
141+
{job.queue_position > 0 && (
142+
<>
143+
<dt>Queue:</dt>
144+
<dd>
145+
{job.queue_position}/{job.queue_size}
146+
</dd>
147+
</>
148+
)}
130149

131150
<dt>Tags:</dt>
132151
<dd>

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,18 @@ const OrganizationProvisionerJobsPage: FC = () => {
1111
const { organization } = useOrganizationSettings();
1212
const [searchParams, setSearchParams] = useSearchParams();
1313
const filter = {
14-
status: searchParams.get("status") || "",
14+
status: searchParams.get("status") ?? "",
15+
ids: searchParams.get("ids") ?? "",
1516
};
16-
const queryParams = {
17-
...filter,
18-
limit: 100,
19-
} as GetProvisionerJobsParams;
2017
const {
2118
data: jobs,
2219
isLoadingError,
2320
refetch,
2421
} = useQuery({
25-
...provisionerJobs(organization?.id || "", queryParams),
22+
...provisionerJobs(organization?.id ?? "", {
23+
...filter,
24+
limit: 100,
25+
}),
2626
enabled: organization !== undefined,
2727
});
2828

site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.stories.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const meta: Meta<typeof OrganizationProvisionerJobsPageView> = {
2121
args: {
2222
organization: MockOrganization,
2323
jobs: MockProvisionerJobs,
24-
filter: { status: "" },
24+
filter: { status: "", ids: "" },
2525
onRetry: fn(),
2626
},
2727
};
@@ -81,8 +81,8 @@ export const Empty: Story = {
8181
export const OnFilter: Story = {
8282
render: function FilterWithState({ ...args }) {
8383
const [jobs, setJobs] = useState<ProvisionerJob[]>([]);
84-
const [filter, setFilter] = useState({ status: "pending" });
85-
const handleFilterChange = (newFilter: { status: string }) => {
84+
const [filter, setFilter] = useState({ status: "pending", ids: "" });
85+
const handleFilterChange = (newFilter: { status: string; ids: string }) => {
8686
setFilter(newFilter);
8787
const filteredJobs = MockProvisionerJobs.filter((job) =>
8888
newFilter.status ? job.status === newFilter.status : true,
@@ -109,3 +109,13 @@ export const OnFilter: Story = {
109109
await userEvent.click(option);
110110
},
111111
};
112+
113+
export const FilterByID: Story = {
114+
args: {
115+
jobs: [MockProvisionerJob],
116+
filter: {
117+
ids: MockProvisionerJob.id,
118+
status: "",
119+
},
120+
},
121+
};

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

Lines changed: 72 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
ProvisionerJob,
44
ProvisionerJobStatus,
55
} from "api/typesGenerated";
6+
import { Badge } from "components/Badge/Badge";
67
import { Button } from "components/Button/Button";
78
import { EmptyState } from "components/EmptyState/EmptyState";
89
import { Link } from "components/Link/Link";
@@ -33,6 +34,13 @@ import {
3334
TableHeader,
3435
TableRow,
3536
} from "components/Table/Table";
37+
import {
38+
Tooltip,
39+
TooltipContent,
40+
TooltipProvider,
41+
TooltipTrigger,
42+
} from "components/Tooltip/Tooltip";
43+
import { XIcon } from "lucide-react";
3644
import type { FC } from "react";
3745
import { Helmet } from "react-helmet-async";
3846
import { docs } from "utils/docs";
@@ -64,6 +72,7 @@ const StatusFilters: ProvisionerJobStatus[] = [
6472

6573
type JobProvisionersFilter = {
6674
status: string;
75+
ids: string;
6776
};
6877

6978
type OrganizationProvisionerJobsPageViewProps = {
@@ -110,30 +119,62 @@ const OrganizationProvisionerJobsPageView: FC<
110119
</SettingsHeaderDescription>
111120
</SettingsHeader>
112121

113-
<Select
114-
value={filter.status}
115-
onValueChange={(status) => {
116-
onFilterChange({ status: status as ProvisionerJobStatus });
117-
}}
118-
>
119-
<SelectTrigger className="w-[180px]" data-testid="status-filter">
120-
<SelectValue placeholder="All statuses" />
121-
</SelectTrigger>
122-
<SelectContent>
123-
<SelectGroup>
124-
{StatusFilters.map((status) => (
125-
<SelectItem key={status} value={status}>
126-
<StatusIndicator variant={variantByStatus[status]}>
127-
<StatusIndicatorDot />
128-
<span className="block first-letter:uppercase">
129-
{status}
130-
</span>
131-
</StatusIndicator>
132-
</SelectItem>
133-
))}
134-
</SelectGroup>
135-
</SelectContent>
136-
</Select>
122+
<div className="flex items-center gap-2">
123+
{filter.ids && (
124+
<div className="relative">
125+
<Badge className="h-10 text-sm pl-3 pr-10 font-mono">
126+
{filter.ids}
127+
</Badge>
128+
<div className="size-10 flex items-center justify-center absolute top-0 right-0">
129+
<TooltipProvider>
130+
<Tooltip>
131+
<TooltipTrigger asChild>
132+
<Button
133+
size="icon"
134+
variant="subtle"
135+
onClick={() => {
136+
onFilterChange({ ...filter, ids: "" });
137+
}}
138+
>
139+
<span className="sr-only">Clear ID</span>
140+
<XIcon />
141+
</Button>
142+
</TooltipTrigger>
143+
<TooltipContent>Clear ID</TooltipContent>
144+
</Tooltip>
145+
</TooltipProvider>
146+
</div>
147+
</div>
148+
)}
149+
150+
<Select
151+
value={filter.status}
152+
onValueChange={(status) => {
153+
onFilterChange({
154+
...filter,
155+
status,
156+
});
157+
}}
158+
>
159+
<SelectTrigger className="w-[180px]" data-testid="status-filter">
160+
<SelectValue placeholder="All statuses" />
161+
</SelectTrigger>
162+
<SelectContent>
163+
<SelectGroup>
164+
{StatusFilters.map((status) => (
165+
<SelectItem key={status} value={status}>
166+
<StatusIndicator variant={variantByStatus[status]}>
167+
<StatusIndicatorDot />
168+
<span className="block first-letter:uppercase">
169+
{status}
170+
</span>
171+
</StatusIndicator>
172+
</SelectItem>
173+
))}
174+
</SelectGroup>
175+
</SelectContent>
176+
</Select>
177+
</div>
137178

138179
<Table className="mt-6">
139180
<TableHeader>
@@ -149,7 +190,13 @@ const OrganizationProvisionerJobsPageView: FC<
149190
<TableBody>
150191
{jobs ? (
151192
jobs.length > 0 ? (
152-
jobs.map((j) => <JobRow key={j.id} job={j} />)
193+
jobs.map((j) => (
194+
<JobRow
195+
defaultIsOpen={filter.ids.includes(j.id)}
196+
key={j.id}
197+
job={j}
198+
/>
199+
))
153200
) : (
154201
<TableRow>
155202
<TableCell colSpan={999}>

0 commit comments

Comments
 (0)