d25d482dc2
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
146 lines
4.8 KiB
TypeScript
146 lines
4.8 KiB
TypeScript
import { createParser, parseAsArrayOf, parseAsString, parseAsStringLiteral } from 'nuqs/server'
|
|
import {
|
|
CORE_TRIGGER_TYPES,
|
|
type LogLevel,
|
|
type TimeRange,
|
|
type TriggerType,
|
|
} from '@/stores/logs/filters/types'
|
|
|
|
/**
|
|
* Co-located, typed URL query-param definitions for the logs feature. The
|
|
* client hook (`useLogFilters`) consumes this typed param definition as the
|
|
* single source of truth.
|
|
*
|
|
* The encoding here intentionally preserves the exact wire format the logs page
|
|
* shipped before nuqs: `timeRange` uses kebab tokens, `level` / `workflowIds` /
|
|
* `folderIds` / `triggers` are comma-joined, and `search` is trimmed.
|
|
*/
|
|
|
|
const DEFAULT_TIME_RANGE: TimeRange = 'All time'
|
|
|
|
/** Maps a {@link TimeRange} label to its stable URL token and back. */
|
|
const TIME_RANGE_TO_TOKEN: Record<TimeRange, string> = {
|
|
'All time': 'all-time',
|
|
'Past 30 minutes': 'past-30-minutes',
|
|
'Past hour': 'past-hour',
|
|
'Past 6 hours': 'past-6-hours',
|
|
'Past 12 hours': 'past-12-hours',
|
|
'Past 24 hours': 'past-24-hours',
|
|
'Past 3 days': 'past-3-days',
|
|
'Past 7 days': 'past-7-days',
|
|
'Past 14 days': 'past-14-days',
|
|
'Past 30 days': 'past-30-days',
|
|
'Custom range': 'custom',
|
|
}
|
|
|
|
const TOKEN_TO_TIME_RANGE: Record<string, TimeRange> = Object.fromEntries(
|
|
Object.entries(TIME_RANGE_TO_TOKEN).map(([label, token]) => [token, label as TimeRange])
|
|
) as Record<string, TimeRange>
|
|
|
|
/**
|
|
* Parser for the `timeRange` param. Serializes labels to kebab tokens and
|
|
* tolerantly maps unknown tokens back to the default ("All time").
|
|
*/
|
|
export const parseAsTimeRange = createParser<TimeRange>({
|
|
parse(value) {
|
|
return TOKEN_TO_TIME_RANGE[value] ?? DEFAULT_TIME_RANGE
|
|
},
|
|
serialize(value) {
|
|
return TIME_RANGE_TO_TOKEN[value] ?? 'all-time'
|
|
},
|
|
})
|
|
|
|
const VALID_LEVELS = ['error', 'info', 'running', 'pending'] as const
|
|
|
|
/**
|
|
* Parser for the `level` param. `level` is a comma-joined list of statuses on
|
|
* the wire but is surfaced as a single `LogLevel` value ("all", a single status,
|
|
* or a comma-joined string) to match the existing store contract.
|
|
*/
|
|
export const parseAsLogLevel = createParser<LogLevel>({
|
|
parse(value) {
|
|
const levels = value
|
|
.split(',')
|
|
.filter((l): l is (typeof VALID_LEVELS)[number] =>
|
|
(VALID_LEVELS as readonly string[]).includes(l)
|
|
)
|
|
if (levels.length === 0) return 'all'
|
|
if (levels.length === 1) return levels[0]
|
|
return levels.join(',') as LogLevel
|
|
},
|
|
serialize(value) {
|
|
return value
|
|
},
|
|
})
|
|
|
|
const CORE_TRIGGER_SET = new Set<string>(CORE_TRIGGER_TYPES)
|
|
|
|
/**
|
|
* Parser for the `triggers` param, restricted to known core trigger types.
|
|
* Surfaced as `TriggerType[]` to match the consumer contract — unknown tokens
|
|
* are dropped (mirrors the prior `parseTriggerArrayFromURL` behavior).
|
|
*/
|
|
export const parseAsTriggers = createParser<TriggerType[]>({
|
|
parse(value) {
|
|
const triggers = value.split(',').filter((t): t is TriggerType => CORE_TRIGGER_SET.has(t))
|
|
return triggers
|
|
},
|
|
serialize(value) {
|
|
return value.join(',')
|
|
},
|
|
eq(a, b) {
|
|
return a.length === b.length && a.every((v, i) => v === b[i])
|
|
},
|
|
}).withDefault([])
|
|
|
|
/**
|
|
* The nuqs parser map for every URL-synced logs filter. `clearOnDefault` keeps
|
|
* the URL clean (params drop out when they hold their default value) and
|
|
* `history: 'replace'` matches the prior `history.replaceState` behavior so
|
|
* filter changes don't pollute the browser back stack.
|
|
*/
|
|
export const logFilterParsers = {
|
|
timeRange: parseAsTimeRange.withDefault(DEFAULT_TIME_RANGE),
|
|
startDate: parseAsString,
|
|
endDate: parseAsString,
|
|
level: parseAsLogLevel.withDefault('all'),
|
|
workflowIds: parseAsArrayOf(parseAsString).withDefault([]),
|
|
folderIds: parseAsArrayOf(parseAsString).withDefault([]),
|
|
triggers: parseAsTriggers,
|
|
search: parseAsString.withDefault(''),
|
|
} as const
|
|
|
|
/** Shared nuqs options for the logs filters: clean URLs, no back-stack churn. */
|
|
export const logFilterUrlKeys = {
|
|
history: 'replace',
|
|
clearOnDefault: true,
|
|
} as const
|
|
|
|
/**
|
|
* Read-only deep link to a specific execution. Resolves to a log row and opens
|
|
* the details sidebar on load. Intentionally NOT stripped — the link stays
|
|
* shareable — so it carries no `clearOnDefault`/`history` options here.
|
|
*/
|
|
export const executionIdParam = {
|
|
key: 'executionId',
|
|
parser: parseAsString,
|
|
} as const
|
|
|
|
const LOG_DETAILS_TABS = ['overview', 'trace'] as const
|
|
|
|
/**
|
|
* Active tab of the log-details sidebar (`overview` / `trace`). Deep-linkable so
|
|
* a shared link can land on the trace view; `replace` keeps it off the back
|
|
* stack and `clearOnDefault` drops it from the URL when on the default tab.
|
|
*/
|
|
export const logDetailsTabParam = {
|
|
key: 'tab',
|
|
parser: parseAsStringLiteral(LOG_DETAILS_TABS).withDefault('overview'),
|
|
} as const
|
|
|
|
/** Tab change is view-state, not a destination: replace, clean URL on default. */
|
|
export const logDetailsTabUrlKeys = {
|
|
history: 'replace',
|
|
clearOnDefault: true,
|
|
} as const
|