diff --git a/packages/app/src/features/jobs/list.tsx b/packages/app/src/features/jobs/list.tsx
index c734254..f30969c 100644
--- a/packages/app/src/features/jobs/list.tsx
+++ b/packages/app/src/features/jobs/list.tsx
@@ -1,4 +1,4 @@
-import { Button, Card, Label, Select, SortableHeader, Table, tableCellClasses, tableHeadClasses, tableRowClasses } from "../../shared/ui";
+import { Button, Card, MultiSelectHeader, SortableHeader, Table, tableCellClasses, tableHeadClasses, tableRowClasses } from "../../shared/ui";
import { Link, useNavigate, useSearch } from "@tanstack/react-router";
import { useQuery } from "@tanstack/react-query";
import { jobsQuery, type JobsOrder, type JobsSearch, type JobsSort } from "./queries";
@@ -7,13 +7,7 @@ import { Page, PageHeader } from "../../shared/ui";
export function GlobalJobsList() {
const search = useSearch({ from: "/jobs" });
- return ;
-}
-
-function ProjectFilter({ search }: { search: JobsSearch }) {
- const navigate = useNavigate({ from: "/jobs" });
- const projects = useQuery(projectsQuery);
- return ;
+ return Manage projects
;
}
export function JobsTable({ projectId, global = false }: { projectId?: string; global?: boolean }) {
@@ -33,7 +27,7 @@ export function JobsTable({ projectId, global = false }: { projectId?: string; g
{!!query.data.items.length &&
chooseSort("running")} />
- {global && | Project | }
+ {global && ({ id: project.id, label: project.name }))} selected={projectId ? [projectId] : []} onChange={(id, checked) => void globalNavigate({ search: { ...search, projectId: checked ? id : undefined, cursor: undefined } })} onClear={() => void globalNavigate({ search: { ...search, projectId: undefined, cursor: undefined } })} />}
chooseSort("title")} />
{query.data.items.map(job =>
diff --git a/packages/app/test/jobs.browser.test.mjs b/packages/app/test/jobs.browser.test.mjs
index 0628574..f8a9819 100644
--- a/packages/app/test/jobs.browser.test.mjs
+++ b/packages/app/test/jobs.browser.test.mjs
@@ -25,6 +25,30 @@ async function contextFor(options) {
await page.route(`**/api/v1/projects/${projectId}`, route => route.fulfill({ json: { id: projectId, name: "Release project", description: "Project jobs", jobCount: 31 } }));
return { context, page };
}
+test("global jobs use the Runs-style project filter", async () => {
+ const { page, context } = await contextFor({ viewport: { width: 1280, height: 844 } });
+ const otherProjectId = "00000000-0000-4000-8000-000000000011";
+ await page.route("**/api/v1/projects", route => route.fulfill({ json: [
+ { id: projectId, name: "Release project", description: "", jobCount: 1 },
+ { id: otherProjectId, name: "Other project", description: "", jobCount: 1 },
+ ] }));
+ await page.route("**/api/v1/job-summaries?**", route => {
+ return route.fulfill({ json: { items: [{ ...summary, projectId }], nextCursor: null } });
+ });
+ await page.goto(origin + "/jobs");
+ const table = page.getByRole("table", { name: "Jobs", exact: true });
+ await table.getByRole("button", { name: /Project, all projects/ }).click();
+ const dialog = page.getByRole("dialog", { name: "Project filter" });
+ const filteredRequest = page.waitForRequest(request => request.url().includes("/api/v1/job-summaries?") && new URL(request.url()).searchParams.has("projectId"));
+ await dialog.getByLabel("Release project").click({ noWaitAfter: true });
+ await page.waitForURL(/projectId=/);
+ const filtered = await filteredRequest;
+ assert.equal(new URL(filtered.url()).searchParams.get("projectId"), projectId);
+ await table.getByRole("button", { name: /Project/ }).click();
+ await page.getByRole("dialog", { name: "Project filter" }).getByRole("button", { name: "Clear project filters" }).click();
+ await page.waitForFunction(() => !new URL(location.href).searchParams.has("projectId"));
+ await context.close();
+});
test("project details can rename and describe a project", async () => {
const { page, context } = await contextFor({ viewport: { width: 1280, height: 844 } });
const saved = [];