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
Manage projects
; -} - -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 && } + {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 = [];
Project