Merge ticket 055: make ottyl_* reserved params usable in dashboard PromQL queries

Resolved conflicts with ticket 051's unknown-metric guard in evaluate_promql
(reordered so ottyl_fmt's categorical rejection and the reserved-name
skip both run before 051's declaration/bucket-data check, since
ottyl_seq/ottyl_ts are synthesized from the log index and never have a
MetricDeclaration or bucket entries) and merged both tickets' additions
to the reserved-params Docs.tsx paragraph.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
This commit is contained in:
Hugh Kaznowski
2026-09-06 09:44:52 +01:00
co-authored by Claude Sonnet 5
7 changed files with 281 additions and 96 deletions
+11 -1
View File
@@ -107,7 +107,9 @@ const SECTIONS: DocSection[] = [
auto-detected format of the line's text — <Code>log_stream</Code> (macOS <Code>log stream</Code> output),{' '}
<Code>json</Code>, <Code>logfmt</Code>, or <Code>none</Code>. A <Code>log_stream</Code> line also gets{' '}
<Code>log_type</Code>, <Code>process</Code>, and <Code>pid</Code> params derived automatically from its
fixed-column header.
fixed-column header. All three reserved names also appear (flagged <Code>reserved</Code>) in{' '}
<Code>GET /api/params</Code> for discoverability, though there's no UI to edit or remove them — see also{' '}
<em>Dashboards</em> below for using <Code>ottyl_seq</Code>/<Code>ottyl_ts</Code> in a panel query.
</p>
<p className="font-semibold text-text">Date/time formats</p>
<p>Timestamp comparisons accept three forms:</p>
@@ -338,6 +340,14 @@ const SECTIONS: DocSection[] = [
Dashboards are saved per profile, so they travel with it when you switch or copy it — an active profile is
required to create or save one; pick or create one from the settings menu first.
</p>
<p>
The reserved <Code>ottyl_seq</Code>/<Code>ottyl_ts</Code> names (see <em>Filtering</em> above) work in panel
queries the same as any declared metric — e.g. <Code>count(ottyl_seq)</Code> for log volume, or{' '}
<Code>rate(ottyl_ts[1m])</Code> for ingest rate — since every log line carries them without a metric
declaration. <Code>ottyl_fmt</Code> is categorical (<Code>json</Code>/<Code>logfmt</Code>/<Code>none</Code>
), not a number, so it's rejected in a panel query with an explicit error rather than silently charting
nothing — filter on it in the filter bar instead.
</p>
</div>
),
},
+28 -2
View File
@@ -52,6 +52,12 @@ export function Params({ params, onDeclared }: { params: ParamDeclaration[]; onD
const [clearResult, setClearResult] = useState<string | null>(null)
const [clearError, setClearError] = useState<string | null>(null)
// `reserved` params (`ottyl_seq`/`ottyl_ts`/`ottyl_fmt`) are synthesized server-side purely for
// discoverability (see `GET /api/params`) — they aren't real declarations, so they're kept out of
// the editable/clickable list and shown separately, non-interactively.
const declaredParams = useMemo(() => params.filter((p) => !p.reserved), [params])
const reservedParams = useMemo(() => params.filter((p) => p.reserved), [params])
const isBoolean = kind === 'boolean'
const isElapsedSinceLast = source === 'elapsed_since_last'
// The message match regex doubles as a sample line here so a pasted JSON log line unlocks the
@@ -307,8 +313,10 @@ export function Params({ params, onDeclared }: { params: ParamDeclaration[]; onD
{clearResult && <div className="mb-1 text-xs text-text-subtle">{clearResult}</div>}
{clearError && <div className="mb-1 text-xs text-danger-text">{clearError}</div>}
<div className="flex flex-wrap gap-1">
{params.length === 0 && <span className="text-xs text-text-faint">No params declared yet.</span>}
{params.map((p) => (
{declaredParams.length === 0 && (
<span className="text-xs text-text-faint">No params declared yet.</span>
)}
{declaredParams.map((p) => (
<button
key={p.name}
type="button"
@@ -330,6 +338,24 @@ export function Params({ params, onDeclared }: { params: ParamDeclaration[]; onD
</button>
))}
</div>
{reservedParams.length > 0 && (
<>
<div className="mb-1 mt-2 text-xs text-text-subtle">
Reserved (built in — present on every line, not editable)
</div>
<div className="flex flex-wrap gap-1">
{reservedParams.map((p) => (
<span
key={p.name}
className="rounded border border-dashed border-border-soft px-2 py-1 text-xs text-text-faint"
title={`${p.name} (${p.kind}, reserved)`}
>
{p.name} ({p.kind})
</span>
))}
</div>
</>
)}
</div>
</div>
</div>