Web: a work item's extra sessions link to Work's Sessions filtered to it
The work panel said "N more on its repositories' Sessions pages", which no longer exist. It links to /<ws>/-/work/sessions?item=N, which now filters to one work item's sessions and says so with a chip that clears it.
- G1T-Session:
- ses_01m56vafhge7ttrgs5288dpxvb
4 files+34−80/4 viewed
| 286 | 286 | export function SessionsFiltersBar({ workspace, filters, repos, people, me }: { workspace: string; filters: SessionsFilters; repos: string[]; people: string[]; me: string | null }) { | |
| 287 | 287 | const [open, setOpen] = useState(false); | |
| 288 | 288 | const to = (change: Partial<SessionsFilters>) => `${sessionsPath(workspace)}${sessionsQuery({ ...filters, ...change })}`; | |
| 289 | − | const count = [filters.repo, filters.person, filters.harness, filters.kind].filter(Boolean).length; | |
| 290 | − | const clear = to({ repo: null, person: null, harness: null, kind: null }); | |
| 289 | + | const count = [filters.repo, filters.person, filters.harness, filters.kind, filters.item].filter(Boolean).length; | |
| 290 | + | const clear = to({ repo: null, person: null, harness: null, kind: null, item: null }); | |
| 291 | + | // A work item's own sessions (`?item=12`, from its page): said, with the way back to all of them. | |
| 292 | + | const onItem = filters.item ? ( | |
| 293 | + | <Link to={to({ item: null })} className="inline-flex h-8 items-center gap-1 rounded-md border border-line px-2.5 text-xs text-fg-soft hover:text-fg" aria-label={`On #${filters.item}: show sessions on every work item`}> | |
| 294 | + | On #{filters.item} | |
| 295 | + | <X size={12} /> | |
| 296 | + | </Link> | |
| 297 | + | ) : null; | |
| 291 | 298 | const everyone = me ? [me, ...people.filter((name) => name !== me)] : people; | |
| 292 | 299 | const menus: { title: string; active: string | null; any: string; field: keyof SessionsFilters; options: { key: string; label: ReactNode }[] }[] = [ | |
| 293 | 300 | { title: "Repository", active: filters.repo, any: "Any repository", field: "repo", options: repos.map((name) => ({ key: name, label: <span className="font-mono text-[0.8125rem]">{name}</span> })) }, | |
| ⋯ | |||
| 298 | 305 | return ( | |
| 299 | 306 | <> | |
| 300 | 307 | <div className="hidden flex-wrap items-center gap-2 sm:flex"> | |
| 308 | + | {onItem} | |
| 301 | 309 | {menus.map((menu) => ( | |
| 302 | 310 | <FilterMenu | |
| 303 | 311 | key={menu.title} | |
| ⋯ | |||
| 327 | 335 | Filters | |
| 328 | 336 | {count > 0 && <span className="rounded-full bg-accent px-1.5 text-[0.6875rem] leading-4.5 font-bold text-bg tabular-nums">{count}</span>} | |
| 329 | 337 | </Button> | |
| 338 | + | {onItem} | |
| 330 | 339 | {filters.repo && <span className="min-w-0 truncate font-mono text-xs text-muted">{filters.repo}</span>} | |
| 331 | 340 | </div> | |
| 332 | 341 | <BottomSheet open={open} onOpenChange={setOpen} title="Filters"> | |
| 56 | 56 | ||
| 57 | 57 | test("Sessions keeps its filters in the address and drops what it does not know", () => { | |
| 58 | 58 | const filters = sessionsFilters(new URLSearchParams("repo=web&person=@bo&harness=codex&kind=bogus&state=recent")); | |
| 59 | − | assert.deepEqual(filters, { state: "recent", repo: "web", person: "bo", harness: "codex", kind: null }); | |
| 59 | + | assert.deepEqual(filters, { state: "recent", repo: "web", person: "bo", harness: "codex", kind: null, item: null }); | |
| 60 | 60 | assert.equal(sessionsQuery(filters), "?state=recent&repo=web&person=bo&harness=codex"); | |
| 61 | 61 | assert.equal(sessionsQuery({ state: "live" }), ""); | |
| 62 | 62 | assert.equal(narrowed(filters), true); | |
| 63 | 63 | assert.equal(narrowed(sessionsFilters(new URLSearchParams("state=recent"))), false); | |
| 64 | 64 | }); | |
| 65 | 65 | ||
| 66 | + | test("Sessions can be a work item's own: ?item=12", () => { | |
| 67 | + | const filters = sessionsFilters(new URLSearchParams("state=recent&item=12")); | |
| 68 | + | assert.equal(filters.item, 12); | |
| 69 | + | assert.equal(narrowed(filters), true); | |
| 70 | + | assert.equal(sessionsQuery(filters), "?state=recent&item=12"); | |
| 71 | + | for (const bad of ["0", "-3", "x", "1.5"]) assert.equal(sessionsFilters(new URLSearchParams(`item=${bad}`)).item, null, bad); | |
| 72 | + | }); | |
| 73 | + | ||
| 66 | 74 | test("live sessions come apart from those that ended, most recently heard from first", () => { | |
| 67 | 75 | const sessions = [ | |
| 68 | 76 | listed("old", { state: "ended" }), |
| 51 | 51 | person: string | null; | |
| 52 | 52 | harness: Harness | null; | |
| 53 | 53 | kind: SessionKind | null; | |
| 54 | + | /** The work item they are on, by number: a work item's own sessions. */ | |
| 55 | + | item: number | null; | |
| 54 | 56 | }; | |
| 55 | 57 | ||
| 56 | 58 | const HARNESSES = Object.keys(HARNESS_NAMES) as Harness[]; | |
| ⋯ | |||
| 66 | 68 | person: text("person"), | |
| 67 | 69 | harness: harness && (HARNESSES as string[]).includes(harness) ? (harness as Harness) : null, | |
| 68 | 70 | kind: kind && (KINDS as string[]).includes(kind) ? (kind as SessionKind) : null, | |
| 71 | + | item: /^\d+$/.test(query.get("item") ?? "") && Number(query.get("item")) > 0 ? Number(query.get("item")) : null, | |
| 69 | 72 | }; | |
| 70 | 73 | } | |
| 71 | 74 | ||
| ⋯ | |||
| 73 | 76 | export function sessionsQuery(filters: Partial<SessionsFilters>): string { | |
| 74 | 77 | const query = new URLSearchParams(); | |
| 75 | 78 | if (filters.state === "recent") query.set("state", "recent"); | |
| 76 | − | for (const key of ["repo", "person", "harness", "kind"] as const) { | |
| 79 | + | for (const key of ["repo", "person", "harness", "kind", "item"] as const) { | |
| 77 | 80 | const value = filters[key]; | |
| 78 | − | if (value) query.set(key, value); | |
| 81 | + | if (value) query.set(key, String(value)); | |
| 79 | 82 | } | |
| 80 | 83 | const text = query.toString(); | |
| 81 | 84 | return text ? `?${text}` : ""; | |
| ⋯ | |||
| 83 | 86 | ||
| 84 | 87 | /** Whether any filter narrows the list. */ | |
| 85 | 88 | export function narrowed(filters: SessionsFilters): boolean { | |
| 86 | − | return Boolean(filters.repo || filters.person || filters.harness || filters.kind); | |
| 89 | + | return Boolean(filters.repo || filters.person || filters.harness || filters.kind || filters.item); | |
| 87 | 90 | } | |
| 88 | 91 | ||
| 89 | 92 | /** | |
| 38 | 38 | const viewer = getViewer(context); | |
| 39 | 39 | const workspace = params.owner.toLowerCase(); | |
| 40 | 40 | const filters = sessionsFilters(new URL(request.url).searchParams); | |
| 41 | − | const narrow = { repo: filters.repo ?? undefined, person: filters.person ?? undefined, harness: filters.harness ?? undefined, kind: filters.kind ?? undefined }; | |
| 41 | + | const narrow = { | |
| 42 | + | repo: filters.repo ?? undefined, | |
| 43 | + | person: filters.person ?? undefined, | |
| 44 | + | harness: filters.harness ?? undefined, | |
| 45 | + | kind: filters.kind ?? undefined, | |
| 46 | + | item: filters.item ?? undefined, | |
| 47 | + | }; | |
| 42 | 48 | const every = filters.state === "recent"; | |
| 43 | 49 | // What is going now is what the page is about; what ended, a part of its own. | |
| 44 | 50 | const [now, ended] = await Promise.all([ | |
| ⋯ | |||
| 265 | 271 | icon={<History size={15} className="text-faint" />} | |
| 266 | 272 | title="Recent" | |
| 267 | 273 | aside={ | |
| 268 | − | <Link to={sessionsPath(workspace, { state: "recent", repo: filters.repo, person: filters.person, harness: filters.harness, kind: filters.kind })} className="hover:text-fg"> | |
| 274 | + | <Link to={sessionsPath(workspace, { state: "recent", repo: filters.repo, person: filters.person, harness: filters.harness, kind: filters.kind, item: filters.item ? String(filters.item) : null })} className="hover:text-fg"> | |
| 269 | 275 | Every session | |
| 270 | 276 | </Link> | |
| 271 | 277 | } | |
Comments
Sign in to comment.