Skip to content

Commit 39b70ce

Browse files
committed
refactor: extract shared adapter and route query helpers
1 parent ee95c7f commit 39b70ce

9 files changed

Lines changed: 218 additions & 174 deletions

File tree

frontend/src/views/AuditFindings.vue

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
severityColor,
2626
titleCaseFallback
2727
} from './auditSupport'
28+
import { optionalFirstTrimmedRouteQueryValue } from './routeQuery'
2829
2930
const ATable = AntTable as unknown as DefineComponent
3031
const ATypographyText = Typography.Text
@@ -35,10 +36,10 @@ const findingsResource = useAsyncResource<AuditFinding[]>([])
3536
const findings = computed(() => findingsResource.data.value)
3637
const loading = findingsResource.loading
3738
const error = findingsResource.error
38-
const categoryFilter = ref<string | undefined>(routeStringQuery('category'))
39-
const severityFilter = ref<string | undefined>(routeStringQuery('severity'))
40-
const shellFilter = ref<string | undefined>(routeStringQuery('shell'))
41-
const search = ref(routeStringQuery('search') || '')
39+
const categoryFilter = ref<string | undefined>(optionalFirstTrimmedRouteQueryValue(route.query.category))
40+
const severityFilter = ref<string | undefined>(optionalFirstTrimmedRouteQueryValue(route.query.severity))
41+
const shellFilter = ref<string | undefined>(optionalFirstTrimmedRouteQueryValue(route.query.shell))
42+
const search = ref(optionalFirstTrimmedRouteQueryValue(route.query.search) || '')
4243
const selectedAgent = computed(() => cleanQueryValue(route.query.agent) || undefined)
4344
let applyingRouteUpdate = false
4445
@@ -173,11 +174,6 @@ const rowCountText = computed(() => {
173174
return hasActiveFilters.value ? t('count.filtered', { count }) : t('count.loaded', { count })
174175
})
175176
176-
function routeStringQuery(key: string) {
177-
const value = cleanQueryValue(route.query[key])
178-
return value || undefined
179-
}
180-
181177
function currentFindingFilters() {
182178
return {
183179
agent: selectedAgent.value,
@@ -220,10 +216,10 @@ async function applyFilters() {
220216
}
221217
222218
function syncFiltersFromRoute() {
223-
categoryFilter.value = routeStringQuery('category')
224-
severityFilter.value = routeStringQuery('severity')
225-
shellFilter.value = routeStringQuery('shell')
226-
search.value = routeStringQuery('search') || ''
219+
categoryFilter.value = optionalFirstTrimmedRouteQueryValue(route.query.category)
220+
severityFilter.value = optionalFirstTrimmedRouteQueryValue(route.query.severity)
221+
shellFilter.value = optionalFirstTrimmedRouteQueryValue(route.query.shell)
222+
search.value = optionalFirstTrimmedRouteQueryValue(route.query.search) || ''
227223
}
228224
229225
function resetFilters() {

frontend/src/views/auditSupport.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { AuditFinding, Session } from '../api/types'
22
import { api } from '../api/client'
33
import { matchesSourceFilter, type SourceIdentityLike } from '../presentation/sourceIdentity'
4+
import { cleanFirstRouteQuery, firstTrimmedRouteQueryValue } from './routeQuery'
45

56
export interface AuditFindingQuery {
67
agent?: string
@@ -22,19 +23,8 @@ export interface AuditFindingDetail {
2223
relatedFindings: AuditFinding[]
2324
}
2425

25-
export function cleanQueryValue(value: unknown): string {
26-
const nextValue = Array.isArray(value) ? value[0] : value
27-
return typeof nextValue === 'string' ? nextValue.trim() : ''
28-
}
29-
30-
export function cleanRouteQuery(values: Record<string, unknown>): Record<string, string> {
31-
const query: Record<string, string> = {}
32-
Object.entries(values).forEach(([key, value]) => {
33-
const cleanValue = cleanQueryValue(value)
34-
if (cleanValue) query[key] = cleanValue
35-
})
36-
return query
37-
}
26+
export const cleanQueryValue = firstTrimmedRouteQueryValue
27+
export const cleanRouteQuery = cleanFirstRouteQuery
3828

3929
export function buildQueryString(values: Record<string, string | number | undefined | null>): string {
4030
const params = new URLSearchParams()

frontend/src/views/routeQuery.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { LocationQuery } from 'vue-router'
2+
3+
export type RouteQueryRecord = Record<string, string>
4+
export type RouteQueryDateTimeBoundary = 'start' | 'end'
5+
6+
export function stringRouteQueryValue(value: unknown) {
7+
return typeof value === 'string' && value ? value : undefined
8+
}
9+
10+
export function trimmedRouteQueryValue(value: unknown) {
11+
return typeof value === 'string' && value.trim() ? value.trim() : undefined
12+
}
13+
14+
export function firstTrimmedRouteQueryValue(value: unknown): string {
15+
const nextValue = Array.isArray(value) ? value[0] : value
16+
return typeof nextValue === 'string' ? nextValue.trim() : ''
17+
}
18+
19+
export function optionalFirstTrimmedRouteQueryValue(value: unknown) {
20+
return firstTrimmedRouteQueryValue(value) || undefined
21+
}
22+
23+
export function copyStringRouteQuery(sourceQuery: LocationQuery | Record<string, unknown>): RouteQueryRecord {
24+
const query: RouteQueryRecord = {}
25+
for (const [key, value] of Object.entries(sourceQuery)) {
26+
if (typeof value === 'string') query[key] = value
27+
}
28+
return query
29+
}
30+
31+
export function cleanFirstRouteQuery(values: LocationQuery | Record<string, unknown>): RouteQueryRecord {
32+
const query: RouteQueryRecord = {}
33+
Object.entries(values).forEach(([key, value]) => {
34+
const cleanValue = firstTrimmedRouteQueryValue(value)
35+
if (cleanValue) query[key] = cleanValue
36+
})
37+
return query
38+
}
39+
40+
export function setRouteQueryValue(query: RouteQueryRecord, key: string, value?: string) {
41+
if (value) query[key] = value
42+
else delete query[key]
43+
}
44+
45+
export function setTrimmedRouteQueryValue(query: RouteQueryRecord, key: string, value?: string) {
46+
const next = value?.trim()
47+
if (next) query[key] = next
48+
else delete query[key]
49+
}
50+
51+
export function routeDateTimeInputValue(query: LocationQuery, key: string) {
52+
const value = stringRouteQueryValue(query[key])
53+
if (!value) return ''
54+
return value.endsWith('Z') ? toLocalDateTimeInputValue(value) : value
55+
}
56+
57+
export function toLocalDateTimeInputValue(value: string) {
58+
const date = new Date(value)
59+
if (Number.isNaN(date.getTime())) return value
60+
const pad = (part: number) => String(part).padStart(2, '0')
61+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`
62+
}
63+
64+
export function dateTimeInputToQueryIso(value: string, boundary: RouteQueryDateTimeBoundary = 'start') {
65+
if (!value) return undefined
66+
const date = new Date(value)
67+
if (Number.isNaN(date.getTime())) return undefined
68+
if (boundary === 'end' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(value)) {
69+
date.setSeconds(59, 999)
70+
}
71+
return date.toISOString()
72+
}

frontend/src/views/tools/useToolCallExplorer.ts

Lines changed: 31 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { computed, onMounted, ref, watch } from 'vue'
2-
import { useRoute, useRouter } from 'vue-router'
2+
import { useRoute, useRouter, type LocationQuery } from 'vue-router'
33
import { api, type AgentUsage, type ToolCall, type ToolCallFilters, type ToolStat } from '../../api'
4+
import {
5+
copyStringRouteQuery,
6+
dateTimeInputToQueryIso,
7+
routeDateTimeInputValue,
8+
setRouteQueryValue,
9+
stringRouteQueryValue
10+
} from '../routeQuery'
411
import { invokedCommand, isShellToolName } from './shellTool'
512

613
export const DEFAULT_SORT = 'recent'
@@ -22,12 +29,12 @@ export function useToolCallExplorer(mode: ToolCallExplorerMode) {
2229
const agents = ref<AgentUsage[]>([])
2330
const toolCalls = ref<ToolCall[]>([])
2431
const commandOptions = ref<ShellCommandStat[]>([])
25-
const toolFilter = ref<string | undefined>(routeStringQuery(route, 'tool'))
26-
const commandFilter = ref<string | undefined>(mode === 'shell' ? routeStringQuery(route, 'command') : undefined)
27-
const agentFilter = ref<string | undefined>(routeStringQuery(route, 'agent'))
28-
const fromFilter = ref(routeDateTimeQuery(route, 'from'))
29-
const toFilter = ref(routeDateTimeQuery(route, 'to'))
30-
const sortFilter = ref<ToolCallSort>(routeSortQuery(route))
32+
const toolFilter = ref<string | undefined>(stringRouteQueryValue(route.query.tool))
33+
const commandFilter = ref<string | undefined>(mode === 'shell' ? stringRouteQueryValue(route.query.command) : undefined)
34+
const agentFilter = ref<string | undefined>(stringRouteQueryValue(route.query.agent))
35+
const fromFilter = ref(routeDateTimeInputValue(route.query, 'from'))
36+
const toFilter = ref(routeDateTimeInputValue(route.query, 'to'))
37+
const sortFilter = ref<ToolCallSort>(routeSortQuery(route.query))
3138
const selectedToolCall = ref<ToolCall | null>(null)
3239
const routePath = mode === 'shell' ? '/tools/shell' : '/tools/calls'
3340
const availableTools = computed(() => (mode === 'shell' ? tools.value.filter((item) => isShellToolName(item.toolName)) : tools.value))
@@ -86,8 +93,8 @@ export function useToolCallExplorer(mode: ToolCallExplorerMode) {
8693
return {
8794
tool: mode === 'all' ? toolFilter.value : undefined,
8895
agent: agentFilter.value,
89-
from: toQueryDateTime(fromFilter.value),
90-
to: toQueryDateTime(toFilter.value, 'end'),
96+
from: dateTimeInputToQueryIso(fromFilter.value),
97+
to: dateTimeInputToQueryIso(toFilter.value, 'end'),
9198
sort: sortFilter.value === DEFAULT_SORT ? undefined : sortFilter.value,
9299
limit: TOOL_CALL_LIMIT
93100
}
@@ -111,16 +118,13 @@ export function useToolCallExplorer(mode: ToolCallExplorerMode) {
111118
}
112119

113120
function currentRouteQuery() {
114-
const query: Record<string, string> = {}
115-
for (const [key, value] of Object.entries(route.query)) {
116-
if (typeof value === 'string') query[key] = value
117-
}
118-
setQueryValue(query, 'tool', toolFilter.value)
119-
setQueryValue(query, 'command', mode === 'shell' ? commandFilter.value : undefined)
120-
setQueryValue(query, 'agent', agentFilter.value)
121-
setQueryValue(query, 'from', fromFilter.value || undefined)
122-
setQueryValue(query, 'to', toFilter.value || undefined)
123-
setQueryValue(query, 'sort', sortFilter.value === DEFAULT_SORT ? undefined : sortFilter.value)
121+
const query = copyStringRouteQuery(route.query)
122+
setRouteQueryValue(query, 'tool', toolFilter.value)
123+
setRouteQueryValue(query, 'command', mode === 'shell' ? commandFilter.value : undefined)
124+
setRouteQueryValue(query, 'agent', agentFilter.value)
125+
setRouteQueryValue(query, 'from', fromFilter.value || undefined)
126+
setRouteQueryValue(query, 'to', toFilter.value || undefined)
127+
setRouteQueryValue(query, 'sort', sortFilter.value === DEFAULT_SORT ? undefined : sortFilter.value)
124128
return query
125129
}
126130

@@ -140,12 +144,12 @@ export function useToolCallExplorer(mode: ToolCallExplorerMode) {
140144
}
141145

142146
function syncFiltersFromRoute() {
143-
toolFilter.value = routeStringQuery(route, 'tool')
144-
commandFilter.value = mode === 'shell' ? routeStringQuery(route, 'command') : undefined
145-
agentFilter.value = routeStringQuery(route, 'agent')
146-
fromFilter.value = routeDateTimeQuery(route, 'from')
147-
toFilter.value = routeDateTimeQuery(route, 'to')
148-
sortFilter.value = routeSortQuery(route)
147+
toolFilter.value = stringRouteQueryValue(route.query.tool)
148+
commandFilter.value = mode === 'shell' ? stringRouteQueryValue(route.query.command) : undefined
149+
agentFilter.value = stringRouteQueryValue(route.query.agent)
150+
fromFilter.value = routeDateTimeInputValue(route.query, 'from')
151+
toFilter.value = routeDateTimeInputValue(route.query, 'to')
152+
sortFilter.value = routeSortQuery(route.query)
149153
}
150154

151155
function resetFilters() {
@@ -245,41 +249,8 @@ function timestampMs(value?: string) {
245249
return Number.isNaN(parsed) ? 0 : parsed
246250
}
247251

248-
function routeStringQuery(route: ReturnType<typeof useRoute>, key: string) {
249-
const value = route.query[key]
250-
return typeof value === 'string' && value ? value : undefined
251-
}
252-
253-
function routeDateTimeQuery(route: ReturnType<typeof useRoute>, key: string) {
254-
const value = routeStringQuery(route, key)
255-
if (!value) return ''
256-
return value.endsWith('Z') ? toLocalDateTimeInputValue(value) : value
257-
}
258-
259-
function routeSortQuery(route: ReturnType<typeof useRoute>): ToolCallSort {
260-
const value = routeStringQuery(route, 'sort')
252+
function routeSortQuery(query: LocationQuery): ToolCallSort {
253+
const value = stringRouteQueryValue(query.sort)
261254
if (value === 'duration_desc' || value === 'duration_asc') return value
262255
return DEFAULT_SORT
263256
}
264-
265-
function toLocalDateTimeInputValue(value: string) {
266-
const date = new Date(value)
267-
if (Number.isNaN(date.getTime())) return value
268-
const pad = (part: number) => String(part).padStart(2, '0')
269-
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`
270-
}
271-
272-
function toQueryDateTime(value: string, boundary: 'start' | 'end' = 'start') {
273-
if (!value) return undefined
274-
const date = new Date(value)
275-
if (Number.isNaN(date.getTime())) return undefined
276-
if (boundary === 'end' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(value)) {
277-
date.setSeconds(59, 999)
278-
}
279-
return date.toISOString()
280-
}
281-
282-
function setQueryValue(query: Record<string, string>, key: string, value?: string) {
283-
if (value) query[key] = value
284-
else delete query[key]
285-
}

frontend/src/views/useUsageScope.ts

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { computed, ref, watch } from 'vue'
22
import { useRoute, useRouter, type LocationQuery } from 'vue-router'
33
import type { UsageScopeFilters } from '../api'
4+
import { copyStringRouteQuery, setTrimmedRouteQueryValue, trimmedRouteQueryValue } from './routeQuery'
45

56
export interface UsageScopeForm {
67
agent?: string
@@ -19,16 +20,6 @@ const quickRangeDays: Record<string, number> = {
1920
month: 30
2021
}
2122

22-
function cleanQueryValue(value: unknown) {
23-
return typeof value === 'string' && value.trim() ? value.trim() : undefined
24-
}
25-
26-
function setQueryValue(query: Record<string, string>, key: string, value?: string) {
27-
const next = value?.trim()
28-
if (next) query[key] = next
29-
else delete query[key]
30-
}
31-
3223
export function normalizeUsageScope(filters: Partial<UsageScopeForm>): UsageScopeForm {
3324
return {
3425
agent: filters.agent?.trim() || undefined,
@@ -42,12 +33,12 @@ export function normalizeUsageScope(filters: Partial<UsageScopeForm>): UsageScop
4233

4334
export function readUsageScopeQuery(query: LocationQuery): UsageScopeForm {
4435
return normalizeUsageScope({
45-
agent: cleanQueryValue(query.agent),
46-
model: cleanQueryValue(query.model),
47-
project: cleanQueryValue(query.project),
48-
range: cleanQueryValue(query.range),
49-
from: cleanQueryValue(query.from) || '',
50-
to: cleanQueryValue(query.to) || ''
36+
agent: trimmedRouteQueryValue(query.agent),
37+
model: trimmedRouteQueryValue(query.model),
38+
project: trimmedRouteQueryValue(query.project),
39+
range: trimmedRouteQueryValue(query.range),
40+
from: trimmedRouteQueryValue(query.from) || '',
41+
to: trimmedRouteQueryValue(query.to) || ''
5142
})
5243
}
5344

@@ -101,14 +92,11 @@ export function applyUsageScopeToQuery(
10192
filters: UsageScopeForm,
10293
extra: Record<string, string | undefined> = {}
10394
) {
104-
const query: Record<string, string> = {}
105-
for (const [key, value] of Object.entries(sourceQuery)) {
106-
if (typeof value === 'string') query[key] = value
107-
}
95+
const query = copyStringRouteQuery(sourceQuery)
10896

10997
const normalized = normalizeUsageScope(filters)
110-
for (const key of scopeKeys) setQueryValue(query, key, normalized[key])
111-
for (const [key, value] of Object.entries(extra)) setQueryValue(query, key, value)
98+
for (const key of scopeKeys) setTrimmedRouteQueryValue(query, key, normalized[key])
99+
for (const [key, value] of Object.entries(extra)) setTrimmedRouteQueryValue(query, key, value)
112100
return query
113101
}
114102

0 commit comments

Comments
 (0)