Skip to content

Commit 3bfafe3

Browse files
1 parent 43d584c commit 3bfafe3

File tree

7 files changed

+161
-25
lines changed

7 files changed

+161
-25
lines changed

site/src/api/api.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,11 @@ export class MissingBuildParameters extends Error {
400400
}
401401
}
402402

403+
export type GetProvisionerJobsParams = {
404+
status?: TypesGen.ProvisionerJobStatus;
405+
limit?: number;
406+
};
407+
403408
/**
404409
* This is the container for all API methods. It's split off to make it more
405410
* clear where API methods should go, but it is eventually merged into the Api
@@ -2395,9 +2400,13 @@ class ApiMethods {
23952400
return res.data;
23962401
};
23972402

2398-
getProvisionerJobs = async (orgId: string) => {
2403+
getProvisionerJobs = async (
2404+
orgId: string,
2405+
params: GetProvisionerJobsParams = {},
2406+
) => {
23992407
const res = await this.axios.get<TypesGen.ProvisionerJob[]>(
24002408
`/api/v2/organizations/${orgId}/provisionerjobs`,
2409+
{ params },
24012410
);
24022411
return res.data;
24032412
};

site/src/api/queries/organizations.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { API } from "api/api";
1+
import { API, type GetProvisionerJobsParams } from "api/api";
22
import type {
33
CreateOrganizationRequest,
44
GroupSyncSettings,
55
PaginatedMembersRequest,
66
PaginatedMembersResponse,
7+
ProvisionerJobStatus,
78
RoleSyncSettings,
89
UpdateOrganizationRequest,
910
} from "api/typesGenerated";
@@ -241,16 +242,18 @@ export const patchRoleSyncSettings = (
241242
};
242243
};
243244

244-
export const provisionerJobQueryKey = (orgId: string) => [
245-
"organization",
246-
orgId,
247-
"provisionerjobs",
248-
];
245+
export const provisionerJobsQueryKey = (
246+
orgId: string,
247+
params: GetProvisionerJobsParams = {},
248+
) => ["organization", orgId, "provisionerjobs", params];
249249

250-
export const provisionerJobs = (orgId: string) => {
250+
export const provisionerJobs = (
251+
orgId: string,
252+
params: GetProvisionerJobsParams = {},
253+
) => {
251254
return {
252-
queryKey: provisionerJobQueryKey(orgId),
253-
queryFn: () => API.getProvisionerJobs(orgId),
255+
queryKey: provisionerJobsQueryKey(orgId, params),
256+
queryFn: () => API.getProvisionerJobs(orgId, params),
254257
};
255258
};
256259

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { API } from "api/api";
22
import {
33
getProvisionerDaemonsKey,
4-
provisionerJobQueryKey,
4+
provisionerJobsQueryKey,
55
} from "api/queries/organizations";
66
import type { ProvisionerJob } from "api/typesGenerated";
77
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
@@ -28,7 +28,7 @@ export const CancelJobConfirmationDialog: FC<
2828
mutationFn: cancelProvisionerJob,
2929
onSuccess: () => {
3030
queryClient.invalidateQueries(
31-
provisionerJobQueryKey(job.organization_id),
31+
provisionerJobsQueryKey(job.organization_id),
3232
);
3333
queryClient.invalidateQueries(
3434
getProvisionerDaemonsKey(job.organization_id, job.tags),

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

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,20 @@ export const JobRow: FC<JobRowProps> = ({ job }) => {
5252
<Badge size="sm">{job.type}</Badge>
5353
</TableCell>
5454
<TableCell>
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>
55+
{job.metadata.template_name !== "" ? (
56+
<div className="flex items-center gap-1 whitespace-nowrap">
57+
<Avatar
58+
variant="icon"
59+
src={metadata.template_icon}
60+
fallback={
61+
metadata.template_display_name || metadata.template_name
62+
}
63+
/>
64+
{metadata.template_display_name || metadata.template_name}
65+
</div>
66+
) : (
67+
<span>-</span>
68+
)}
6569
</TableCell>
6670
<TableCell>
6771
<TruncateTags tags={job.tags} />

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,39 @@
1+
import type { GetProvisionerJobsParams } from "api/api";
12
import { provisionerJobs } from "api/queries/organizations";
3+
import type { ProvisionerJobStatus } from "api/typesGenerated";
24
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
35
import type { FC } from "react";
46
import { useQuery } from "react-query";
7+
import { useSearchParams } from "react-router-dom";
58
import OrganizationProvisionerJobsPageView from "./OrganizationProvisionerJobsPageView";
69

710
const OrganizationProvisionerJobsPage: FC = () => {
811
const { organization } = useOrganizationSettings();
12+
const [searchParams, setSearchParams] = useSearchParams();
13+
const filter = {
14+
status: searchParams.get("status") || "",
15+
};
16+
const queryParams = {
17+
...filter,
18+
limit: 100,
19+
} as GetProvisionerJobsParams;
920
const {
1021
data: jobs,
1122
isLoadingError,
1223
refetch,
1324
} = useQuery({
14-
...provisionerJobs(organization?.id || ""),
25+
...provisionerJobs(organization?.id || "", queryParams),
1526
enabled: organization !== undefined,
1627
});
1728

1829
return (
1930
<OrganizationProvisionerJobsPageView
2031
jobs={jobs}
32+
filter={filter}
2133
organization={organization}
2234
error={isLoadingError}
2335
onRetry={refetch}
36+
onFilterChange={setSearchParams}
2437
/>
2538
);
2639
};

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Meta, StoryObj } from "@storybook/react";
22
import { expect, fn, userEvent, waitFor, within } from "@storybook/test";
33
import type { ProvisionerJob } from "api/typesGenerated";
4+
import { useState } from "react";
45
import { MockOrganization, MockProvisionerJob } from "testHelpers/entities";
56
import { daysAgo } from "utils/time";
67
import OrganizationProvisionerJobsPageView from "./OrganizationProvisionerJobsPageView";
@@ -20,6 +21,7 @@ const meta: Meta<typeof OrganizationProvisionerJobsPageView> = {
2021
args: {
2122
organization: MockOrganization,
2223
jobs: MockProvisionerJobs,
24+
filter: { status: "" },
2325
onRetry: fn(),
2426
},
2527
};
@@ -75,3 +77,35 @@ export const Empty: Story = {
7577
jobs: [],
7678
},
7779
};
80+
81+
export const OnFilter: Story = {
82+
render: function FilterWithState({ ...args }) {
83+
const [jobs, setJobs] = useState<ProvisionerJob[]>([]);
84+
const [filter, setFilter] = useState({ status: "pending" });
85+
const handleFilterChange = (newFilter: { status: string }) => {
86+
setFilter(newFilter);
87+
const filteredJobs = MockProvisionerJobs.filter((job) =>
88+
newFilter.status ? job.status === newFilter.status : true,
89+
);
90+
setJobs(filteredJobs);
91+
};
92+
93+
return (
94+
<OrganizationProvisionerJobsPageView
95+
{...args}
96+
filter={filter}
97+
jobs={jobs}
98+
onFilterChange={handleFilterChange}
99+
/>
100+
);
101+
},
102+
play: async ({ canvasElement }) => {
103+
const canvas = within(canvasElement);
104+
const statusFilter = canvas.getByTestId("status-filter");
105+
await userEvent.click(statusFilter);
106+
107+
const body = within(canvasElement.ownerDocument.body);
108+
const option = await body.findByRole("option", { name: "succeeded" });
109+
await userEvent.click(option);
110+
},
111+
};

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

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
1-
import type { Organization, ProvisionerJob } from "api/typesGenerated";
1+
import type {
2+
Organization,
3+
ProvisionerJob,
4+
ProvisionerJobStatus,
5+
} from "api/typesGenerated";
26
import { Button } from "components/Button/Button";
37
import { EmptyState } from "components/EmptyState/EmptyState";
48
import { Link } from "components/Link/Link";
59
import { Loader } from "components/Loader/Loader";
10+
import {
11+
Select,
12+
SelectContent,
13+
SelectGroup,
14+
SelectItem,
15+
SelectTrigger,
16+
SelectValue,
17+
} from "components/Select/Select";
18+
import {
19+
StatusIndicator,
20+
StatusIndicatorDot,
21+
type StatusIndicatorProps,
22+
} from "components/StatusIndicator/StatusIndicator";
623
import {
724
Table,
825
TableBody,
@@ -17,16 +34,45 @@ import { docs } from "utils/docs";
1734
import { pageTitle } from "utils/page";
1835
import { JobRow } from "./JobRow";
1936

37+
const variantByStatus: Record<
38+
ProvisionerJobStatus,
39+
StatusIndicatorProps["variant"]
40+
> = {
41+
succeeded: "success",
42+
failed: "failed",
43+
pending: "pending",
44+
running: "pending",
45+
canceling: "pending",
46+
canceled: "inactive",
47+
unknown: "inactive",
48+
};
49+
50+
const StatusFilters: ProvisionerJobStatus[] = [
51+
"succeeded",
52+
"pending",
53+
"running",
54+
"canceling",
55+
"canceled",
56+
"failed",
57+
"unknown",
58+
];
59+
60+
type JobProvisionersFilter = {
61+
status: string;
62+
};
63+
2064
type OrganizationProvisionerJobsPageViewProps = {
2165
jobs: ProvisionerJob[] | undefined;
2266
organization: Organization | undefined;
2367
error: unknown;
68+
filter: JobProvisionersFilter;
2469
onRetry: () => void;
70+
onFilterChange: (filter: JobProvisionersFilter) => void;
2571
};
2672

2773
const OrganizationProvisionerJobsPageView: FC<
2874
OrganizationProvisionerJobsPageViewProps
29-
> = ({ jobs, organization, error, onRetry }) => {
75+
> = ({ jobs, organization, error, filter, onFilterChange, onRetry }) => {
3076
if (!organization) {
3177
return (
3278
<>
@@ -61,6 +107,33 @@ const OrganizationProvisionerJobsPageView: FC<
61107
</div>
62108
</header>
63109

110+
<div>
111+
<Select
112+
value={filter.status}
113+
onValueChange={(status) => {
114+
onFilterChange({ status: status as ProvisionerJobStatus });
115+
}}
116+
>
117+
<SelectTrigger className="w-[180px]" data-testid="status-filter">
118+
<SelectValue placeholder="All statuses" />
119+
</SelectTrigger>
120+
<SelectContent>
121+
<SelectGroup>
122+
{StatusFilters.map((status) => (
123+
<SelectItem key={status} value={status}>
124+
<StatusIndicator variant={variantByStatus[status]}>
125+
<StatusIndicatorDot />
126+
<span className="block first-letter:uppercase">
127+
{status}
128+
</span>
129+
</StatusIndicator>
130+
</SelectItem>
131+
))}
132+
</SelectGroup>
133+
</SelectContent>
134+
</Select>
135+
</div>
136+
64137
<Table>
65138
<TableHeader>
66139
<TableRow>

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