Summary
Add first-class saved-query metadata for one time-range parameter pair and use it in Dashboard runtime to provide Grafana-like synchronized time-series interaction:
- infer and persist one
{from,to} pair when a saved query is authored;
- group Dashboard tiles that resolve to the same filter pair;
- show a vertical crosshair on the active temporal chart;
- synchronize the same timestamp as a vertical line on other compatible charts in the group;
- allow plain mouse drag over a temporal chart to select a new range;
- atomically update and activate both filters on mouse release;
- rerun every dependent tile in the group once.
This issue deliberately does not add a full Dashboard time-range picker, range history, reset, quick ranges, or timezone controls. The existing filter inputs remain the interim editing surface.
Saved-query Spec contract
Add to query-spec-v1:
interface QueryTimeRangeV1 {
from: string;
to: string;
}
interface QuerySpecV1 {
/**
* Absent: authoring may infer and materialize one range.
* Empty: explicit opt-out; never infer.
* One item: authoritative; never infer.
*/
timeRanges?: [] | [QueryTimeRangeV1];
}
Schema requirements:
- array only;
maxItems: 1 in v1;
- each entry requires non-empty
from and to parameter names;
from !== to;
- no unknown fields;
- preserve canonical property ordering and generated schema/types/validators/docs.
timeRanges belongs to the saved-query Spec because it describes query parameter semantics and must travel with the query between Dashboards.
Authoring inference
Inference runs only when timeRanges is absent and the saved query is being created or its SQL is explicitly updated. Dashboard viewing must never mutate saved-query metadata.
Recognized parameter pairs, case-insensitively:
from / to
from_time / to_time
start / end
start_time / end_time
Do not infer start / stop.
Use the parsed parameter model, not regular expressions over raw SQL. Store the actual spelling from the query.
Inference succeeds only when:
- both parameters exist;
- both have supported date/time types;
- neither has conflicting declarations;
- exactly one recognized pair exists;
timeRanges is absent.
When zero pairs are found, leave timeRanges absent. When several candidates exist, do not guess; surface a diagnostic/suggestion requiring explicit Spec authoring. timeRanges: [] is an explicit opt-out.
The inferred Spec update must be validated and committed atomically through the normal saved-query mutation path.
Supported parameter types
Use the shared ClickHouse type parser and the existing shared date/time validation and serialization pipeline used by Workbench and Dashboard.
Accept all valid scalar and wrapper variations of:
Date
Date32
DateTime
DateTime('timezone')
DateTime64(precision)
DateTime64(precision, 'timezone')
including valid supported Nullable(...) / LowCardinality(...) wrapper chains according to the existing parser.
Reject non-date/time types such as String and integer Unix timestamps.
Do not introduce a second timestamp parser or serializer. Selection produces epoch milliseconds, then each bound is formatted and validated according to its declared parameter type through the existing pipeline.
Timezone behavior is unchanged and remains whatever the current server-timezone pipeline provides. Millisecond interaction precision is sufficient. For Date/Date32, do not silently add or subtract days or infer inclusive/exclusive SQL semantics.
Runtime group model
Resolve authored query metadata to Dashboard filters:
interface DashboardTimeRangeGroup {
key: string;
fromFilterId: string;
toFilterId: string;
tileIds: string[];
interactiveChartTileIds: string[];
}
The key derives from resolved filter identities, not display labels.
A Dashboard may contain multiple independent groups. Different queries join the same group when their timeRanges[0] resolves to the same two Dashboard filters.
A tile joins only when its query declares both bounds and both resolve successfully. Invalid or unresolved metadata must produce diagnostics and must not partially join a group.
Tile participation
Every tile in the group reruns when the range changes, including:
- line/area/column charts;
- tables;
- logs;
- KPIs;
- text;
- images;
- future panel types.
Only compatible charts render crosshairs and permit brushing.
Interactive v1 chart families:
- line charts with a horizontal date/time scale;
- area charts with a horizontal date/time scale;
- vertical bar/column charts with a horizontal date/time scale.
Exclude horizontal bar and pie charts from visual interaction. They still rerun when they belong to the group.
Shared hover crosshair
On pointer movement inside an interactive chart plot area:
- preserve the active chart's normal Chart.js tooltip;
- draw a vertical crosshair at the exact timestamp under the pointer;
- publish that timestamp to the Dashboard session's group interaction controller.
On other compatible charts in the same group:
- draw only the vertical line;
- do not show synchronized tooltips;
- do not snap to the nearest row or point;
- convert the exact timestamp through each chart's time scale;
- hide the line when the timestamp is outside that chart's visible range rather than clamping it to an edge.
Leaving the active plot, destroying the chart, rerendering, or leaving the Dashboard clears the synchronized crosshair.
Implement this through a shared Chart.js plugin/interaction seam. Do not add independent Dashboard overlays with duplicated scale math.
Drag-to-select range
Plain primary-button drag inside an interactive chart plot area:
- starts after a small movement threshold;
- draws a translucent selection band;
- shows live start/end labels using existing server-timezone formatting;
- does not update filters during the drag;
- normalizes reverse-direction dragging;
- commits immediately on pointer release.
Selection starts only inside the plot area, not over title, legend, external axes, tile header, resize handles, or edit chrome.
A click or sub-threshold movement does not change filters.
Cancel without mutation on:
- Escape;
pointercancel;
- window blur;
- chart destruction;
- Dashboard rerender/route teardown.
Coordinate with #332: plain drag belongs to chart selection and text selection; tile movement is Command/Ctrl-drag. Command/Ctrl-drag beginning over a chart must move the tile and must not start range selection.
Atomic filter update
Introduce or reuse a batch filter operation, for example:
session.applyFilters([
{ filterId: group.fromFilterId, value: formattedFrom, active: true },
{ filterId: group.toFilterId, value: formattedTo, active: true },
]);
Required semantics:
- format and validate both candidates first;
- commit neither value if either fails;
- update and activate both controls atomically;
- update the existing Workbench/Dashboard-visible filter inputs;
- create one execution generation;
- cancel the prior affected wave once;
- rerun each dependent tile once;
- prevent stale results from the old range replacing newer results.
Range interaction is runtime filter state. It must not mutate saved-query metadata, Dashboard documents, tile placement, workspace revision, or exported layout data.
Interim UI
Until the full time-range control lands:
- keep the existing From and To filter inputs;
- chart selection writes absolute values into those controls;
- manual editing and existing relative-time expressions remain supported;
- do not add history, reset, back/forward, quick ranges, zoom-out, or timezone settings.
Accessibility and fallback
- Crosshair and brush interaction must not remove normal tooltip keyboard/content accessibility.
- Announce a successful range update with a concise live-region message containing the new From and To values.
- Announce validation failure without changing the previous range.
- Pointer-only brushing may be v1; manual filter inputs remain the keyboard-accessible equivalent.
- Touch brushing is out of scope; touch users retain existing filter controls.
Tests
Cover:
Spec and inference
- absent versus empty versus one authored range;
- all recognized name pairs and original spelling preservation;
- no
start/stop inference;
- multiple candidates produce a diagnostic and no write;
- Date, Date32, DateTime, DateTime64, timezone/precision forms, and valid wrappers;
- non-date/time rejection;
- inference occurs only on authoring save/update, never Dashboard view;
- schema/codegen/canonical round-trip.
Group resolution
- same resolved pair creates one group;
- different pairs create independent groups;
- all panel types join execution membership;
- unresolved filters do not partially join;
- one query cannot declare more than one range in v1.
Crosshair
- active chart keeps its tooltip and vertical line;
- other charts show line only;
- exact timestamp synchronization across different sampling intervals;
- out-of-range lines are hidden;
- groups do not leak into each other;
- lifecycle cleanup removes stale lines/listeners.
Range selection
- forward and reverse drag;
- movement threshold;
- immediate mouse-up commit;
- Escape/pointercancel/blur/destruction cancellation;
- Command/Ctrl-drag delegates to tile movement;
- one atomic filter batch and one execution wave;
- Date/Date32 serialize without hidden boundary adjustment;
- stale prior wave cannot overwrite the selected range.
Browser coverage
- light/dark themes;
- Grid Tiles, Full view, Report, 2-column, and 3-column layouts;
- edit and read-only Dashboard modes;
- line, area, and temporal column charts;
- table/log/KPI rerun participation;
- narrow viewport without accidental horizontal page drag.
Acceptance criteria
Non-goals
- full Grafana-style header time picker;
- range history, Back/Forward, Reset, quick ranges, zoom-out, or recent ranges;
- timezone UI or timezone-policy changes;
- relative moving-window semantics after brushing;
- touch brushing;
- multiple ranges per saved query;
- synchronized tooltips on peer charts;
- horizontal-bar or pie brushing;
- inferring SQL bound inclusivity/exclusivity.
Summary
Add first-class saved-query metadata for one time-range parameter pair and use it in Dashboard runtime to provide Grafana-like synchronized time-series interaction:
{from,to}pair when a saved query is authored;This issue deliberately does not add a full Dashboard time-range picker, range history, reset, quick ranges, or timezone controls. The existing filter inputs remain the interim editing surface.
Saved-query Spec contract
Add to
query-spec-v1:Schema requirements:
maxItems: 1in v1;fromandtoparameter names;from !== to;timeRangesbelongs to the saved-query Spec because it describes query parameter semantics and must travel with the query between Dashboards.Authoring inference
Inference runs only when
timeRangesis absent and the saved query is being created or its SQL is explicitly updated. Dashboard viewing must never mutate saved-query metadata.Recognized parameter pairs, case-insensitively:
Do not infer
start / stop.Use the parsed parameter model, not regular expressions over raw SQL. Store the actual spelling from the query.
Inference succeeds only when:
timeRangesis absent.When zero pairs are found, leave
timeRangesabsent. When several candidates exist, do not guess; surface a diagnostic/suggestion requiring explicit Spec authoring.timeRanges: []is an explicit opt-out.The inferred Spec update must be validated and committed atomically through the normal saved-query mutation path.
Supported parameter types
Use the shared ClickHouse type parser and the existing shared date/time validation and serialization pipeline used by Workbench and Dashboard.
Accept all valid scalar and wrapper variations of:
including valid supported
Nullable(...)/LowCardinality(...)wrapper chains according to the existing parser.Reject non-date/time types such as String and integer Unix timestamps.
Do not introduce a second timestamp parser or serializer. Selection produces epoch milliseconds, then each bound is formatted and validated according to its declared parameter type through the existing pipeline.
Timezone behavior is unchanged and remains whatever the current server-timezone pipeline provides. Millisecond interaction precision is sufficient. For Date/Date32, do not silently add or subtract days or infer inclusive/exclusive SQL semantics.
Runtime group model
Resolve authored query metadata to Dashboard filters:
The key derives from resolved filter identities, not display labels.
A Dashboard may contain multiple independent groups. Different queries join the same group when their
timeRanges[0]resolves to the same two Dashboard filters.A tile joins only when its query declares both bounds and both resolve successfully. Invalid or unresolved metadata must produce diagnostics and must not partially join a group.
Tile participation
Every tile in the group reruns when the range changes, including:
Only compatible charts render crosshairs and permit brushing.
Interactive v1 chart families:
Exclude horizontal bar and pie charts from visual interaction. They still rerun when they belong to the group.
Shared hover crosshair
On pointer movement inside an interactive chart plot area:
On other compatible charts in the same group:
Leaving the active plot, destroying the chart, rerendering, or leaving the Dashboard clears the synchronized crosshair.
Implement this through a shared Chart.js plugin/interaction seam. Do not add independent Dashboard overlays with duplicated scale math.
Drag-to-select range
Plain primary-button drag inside an interactive chart plot area:
Selection starts only inside the plot area, not over title, legend, external axes, tile header, resize handles, or edit chrome.
A click or sub-threshold movement does not change filters.
Cancel without mutation on:
pointercancel;Coordinate with #332: plain drag belongs to chart selection and text selection; tile movement is Command/Ctrl-drag. Command/Ctrl-drag beginning over a chart must move the tile and must not start range selection.
Atomic filter update
Introduce or reuse a batch filter operation, for example:
Required semantics:
Range interaction is runtime filter state. It must not mutate saved-query metadata, Dashboard documents, tile placement, workspace revision, or exported layout data.
Interim UI
Until the full time-range control lands:
Accessibility and fallback
Tests
Cover:
Spec and inference
start/stopinference;Group resolution
Crosshair
Range selection
Browser coverage
Acceptance criteria
Non-goals