From 12df3f43091141353457bf5058e9524e6d7a910c Mon Sep 17 00:00:00 2001 From: Eugene Blikh Date: Sun, 26 Apr 2026 16:42:10 +0300 Subject: [PATCH] =?UTF-8?q?web:=20adapter=20=E2=80=94=20add=20Session.sess?= =?UTF-8?q?ionId,=20SavedSearch=20DTO;=20fix=20composite-id=20call=20sites?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/api/adapters.test.ts | 47 ++++++++++++++++++++++++++++++++++-- web/src/api/adapters.ts | 27 +++++++++++++++++++++ web/src/routes/index.tsx | 2 +- web/src/routes/project.$.tsx | 2 +- 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/web/src/api/adapters.test.ts b/web/src/api/adapters.test.ts index 3a9dc24f63804dd32a919256ff4f47075cd2aa0d..b1be89cd2b559e8182dd45babc8381e81457e67d 100644 --- a/web/src/api/adapters.test.ts +++ b/web/src/api/adapters.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest' -import { adaptSession, adaptProject, adaptStats } from './adapters' -import type { SessionDTO, ProjectDTO, StatsDTO } from './adapters' +import { adaptSession, adaptProject, adaptStats, adaptSavedSearch } from './adapters' +import type { SessionDTO, ProjectDTO, StatsDTO, SavedSearchDTO } from './adapters' function makeDTO(overrides: Partial = {}): SessionDTO { return { @@ -56,6 +56,19 @@ describe('adaptSession', () => { const s = adaptSession(makeDTO({ started_at: startedAt, ended_at: endedAt })) expect(s.ended).toBe(new Date(endedAt * 1000).toISOString()) }) + + it('sessionId === d.session_id (bare)', () => { + const s = adaptSession(makeDTO()) + expect(s.sessionId).toBe('abc123') + const s2 = adaptSession(makeDTO({ session_id: 'xyz789' })) + expect(s2.sessionId).toBe('xyz789') + }) + + it('composite id and bare sessionId coexist (IV6)', () => { + const s = adaptSession(makeDTO({ tool: 'opencode', host: 'workpc', session_id: 'xyz789' })) + expect(s.id).toBe('opencode/workpc/xyz789') + expect(s.sessionId).toBe('xyz789') + }) }) function makeProjectDTO(overrides: Partial = {}): ProjectDTO { @@ -178,3 +191,33 @@ describe('adaptStats', () => { expect(s.perTool[0].dailySparkline).toEqual(sparkline) }) }) + +// ── adaptSavedSearch ───────────────────────────────────────────────────────── + +function makeSavedSearchDTO(overrides: Partial = {}): SavedSearchDTO { + return { + name: 'recent-errors', + query: 'level:error', + created_at: 1700000000, + updated_at: 1700000000, + ...overrides, + } +} + +describe('adaptSavedSearch', () => { + it('created_at=1700000000 → createdAt === "2023-11-14T22:13:20.000Z"', () => { + const ss = adaptSavedSearch(makeSavedSearchDTO()) + expect(ss.createdAt).toBe('2023-11-14T22:13:20.000Z') + }) + + it('updated_at=1700000050 → updatedAt is the ISO of that unix epoch', () => { + const ss = adaptSavedSearch(makeSavedSearchDTO({ updated_at: 1700000050 })) + expect(ss.updatedAt).toBe(new Date(1700000050 * 1000).toISOString()) + }) + + it('name and query pass through unchanged', () => { + const ss = adaptSavedSearch(makeSavedSearchDTO({ name: 'my-search', query: 'tool:opencode' })) + expect(ss.name).toBe('my-search') + expect(ss.query).toBe('tool:opencode') + }) +}) diff --git a/web/src/api/adapters.ts b/web/src/api/adapters.ts index e593981fef10cae5a4cdd88676477259f649210c..b31a45d8ff645d4f4d1e3d932523cda85a271d67 100644 --- a/web/src/api/adapters.ts +++ b/web/src/api/adapters.ts @@ -20,6 +20,7 @@ export interface SessionDTO { export interface Session { id: string // `${tool}/${host}/${session_id}` + sessionId: string // bare session_id (IV6) tool: string host: string cwd: string // working_dir ?? '' @@ -36,6 +37,7 @@ export interface Session { export function adaptSession(d: SessionDTO): Session { return { id: `${d.tool}/${d.host}/${d.session_id}`, + sessionId: d.session_id, tool: d.tool, host: d.host, cwd: d.working_dir ?? '', @@ -201,3 +203,28 @@ export function adaptStats(d: StatsDTO): Stats { hostSplit: d.host_split, } } + +// ── Saved searches ────────────────────────────────────────────────────────── + +export interface SavedSearchDTO { + name: string + query: string + created_at: number + updated_at: number +} + +export interface SavedSearch { + name: string + query: string + createdAt: string // ISO 8601 + updatedAt: string // ISO 8601 +} + +export function adaptSavedSearch(d: SavedSearchDTO): SavedSearch { + return { + name: d.name, + query: d.query, + createdAt: new Date(d.created_at * 1000).toISOString(), + updatedAt: new Date(d.updated_at * 1000).toISOString(), + } +} diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx index a231fbef1d5f2b50ba0adfcc3b48c48af3e766eb..b5d75d19b4e255f633fd4c1ac7ee35e158623436 100644 --- a/web/src/routes/index.tsx +++ b/web/src/routes/index.tsx @@ -45,7 +45,7 @@ function HomeRoute(): React.JSX.Element { const { data: sessions, isLoading, error } = useSessions(filters) const handleOpen = useCallback((s: Session) => { - void navigate({ to: '/session/$tool/$host/$id', params: { tool: s.tool, host: s.host, id: s.id } }) + void navigate({ to: '/session/$tool/$host/$id', params: { tool: s.tool, host: s.host, id: s.sessionId } }) }, [navigate]) const { cursor, move, activate, jumpTo } = useHomeCursor( diff --git a/web/src/routes/project.$.tsx b/web/src/routes/project.$.tsx index 6d9e100846efaa7fea6c29a08240c688cd4e03e2..b4b1bfdc42274e1719000ae839452d1090e2afc8 100644 --- a/web/src/routes/project.$.tsx +++ b/web/src/routes/project.$.tsx @@ -30,7 +30,7 @@ function ProjectRoute(): React.JSX.Element { const error = sessionsError const handleOpen = useCallback((s: Session) => { - void navigate({ to: '/session/$tool/$host/$id', params: { tool: s.tool, host: s.host, id: s.id } }) + void navigate({ to: '/session/$tool/$host/$id', params: { tool: s.tool, host: s.host, id: s.sessionId } }) }, [navigate]) const { cursor, move, activate, jumpTo } = useHomeCursor(