Skip to content

Add custom time-range selection for graphs and the event timeline - #388

Open
dpage wants to merge 9 commits into
mainfrom
fix/issue-345-custom-time-range
Open

Add custom time-range selection for graphs and the event timeline#388
dpage wants to merge 9 commits into
mainfrom
fix/issue-345-custom-time-range

Conversation

@dpage

@dpage dpage commented Jul 29, 2026

Copy link
Copy Markdown
Member

Summary

Users can now select an arbitrary start and end time for dashboard
graphs and the event timeline, alongside the existing 1h, 6h,
24h, 7d and 30d presets, which continue to behave exactly as
before.

The issue described this as primarily front-end wiring, on the grounds
that the metrics query layer already buckets over an arbitrary
timeStart/timeEnd pair. That was true of the query layer but not of
the HTTP boundary, so there is server work here as well:
/api/v1/metrics/query now accepts time_range=custom together with
RFC 3339 time_start and time_end, resolved by a single new
metrics.ResolveTimeWindow, and QueryTimeSeries takes an
already-validated TimeWindow rather than resolving a preset string
itself.

The central design decision on the client is that the window's bounds
do not travel through MetricQueryParams. useMetrics already
consumed DashboardContext for refreshTrigger, so it reads
customStart/customEnd from there directly; consequently none of the
roughly twenty consumer components changed, and they keep passing the
range they already passed.

Auto-refresh is suspended whilst a custom window is active, since
re-polling a fixed historical window returns identical data. The
suspension is derived rather than stored, so the user's own
auto-refresh preference is untouched and resumes on returning to a
preset. The window is held in memory only: it is neither persisted nor
reflected in the URL, matching how the preset selection already
behaves.

Validation rejects a start at or after the present, a non-positive
span, and spans beyond 366 days (the bucket-width heuristic derives
from the span, so an unbounded window is a resource-exhaustion
surface); an end in the future is clamped to now rather than rejected,
because a picker set to the current day routinely overshoots by
minutes.

The event timeline keeps its own independent range state but gains the
same picker. Its display-side bounds logic and the calculation in
useTimelineEvents were two diverged copies of the same thing, only
one of which understood custom ranges; both now call a single shared
helper in client/src/utils/timelineRange.ts.

@mui/x-date-pickers is pinned to ^7 because v8 requires MUI 6+ and
this project is on @mui/material 5.18.

Scope

Deliberately out of scope, tracked in #387:

  • The query leaderboards and QueryDetail's headline statistics. The
    top-queries endpoint has no time dimension at all; its SQL pins
    collected_at to the latest snapshot, and giving it a window needs
    delta aggregation over cumulative pg_stat_statements counters,
    which changes preset behaviour too. QueryDetail is therefore split
    today: its two charts follow the selected range, its headline
    statistics report the latest sample. The documentation says so
    explicitly rather than glossing over it.

  • The performance-summary and database-summary tiles, which hardcode
    time_range=24h and ignore the selector entirely.

Test plan

  • gofmt -l and go vet ./... clean across the server.
  • make test-all: everything passes bar two pre-existing failures
    in internal/tools
    (TestStoreMemoryGeneratesEmbeddingIntegration and
    TestRecallMemoriesGeneratesQueryEmbeddingIntegration), which
    reproduce identically on main with expected 3 dimensions, not 4000 from a stale vector(3) fixture and touch no code in this
    change.
  • Client suite: 176 files, 3563 tests, all passing.
  • Coverage on every touched file verified from lcov.info: eleven
    of twelve at 100% line coverage, EventTimeline/utils.ts at
    97.26% (the two uncovered lines are pre-existing
    formatEventTime branches). Server side, ResolveTimeWindow is
    at 100% and handleMetricsQuery rose from 73.4% to 96.9%.
  • go test ./internal/api/ -run OpenAPI -v passes, including a new
    test asserting the parameters landed on /metrics/query and
    not on the performance-summary path.
  • openapi.json regenerated via make openapi; mkdocs build --strict clean.
  • Browser validation with playwright-cli is still outstanding: it
    needs the dev server and web client running, which are started
    manually, and they serve the main checkout rather than this
    worktree.

Closes #345

Summary by CodeRabbit

  • New Features
    • Added “Custom” time-range selection to the dashboard and event timeline, with validated “From/To” pickers and a 366-day maximum span.
    • Auto-refresh is now paused while a custom window is active, with a visible paused indicator.
    • Metrics querying now supports time_range=custom using RFC3339 time_start/time_end.
  • Bug Fixes
    • Metrics time windows are resolved consistently (including clamping future ends to “now”).
    • Timeline requests enforce the 366-day span limit.
  • Documentation
    • Updated user guide and API/OpenAPI docs to describe custom window behavior, constraints, and what parts of the UI honor the selector.
  • Tests
    • Added/expanded UI, hook, and server validation tests covering custom range, auto-refresh pause/resume, and time-window rules.

Added during review

One substantive change came out of the review round and is worth
calling out, because it is a fix to something this PR itself exposed.
/api/v1/timeline/events only ever enforced end_time > start_time;
before this change the timeline UI could send nothing but presets, the
largest being 30 days, so an unbounded span was unreachable in
practice. Giving the timeline a custom-range picker put a decade-long
window one click away, so the endpoint now rejects spans beyond
metrics.MaxCustomTimeSpan, importing the same constant rather than
redeclaring the number.

The other review fixes were an accessibility correction to the paused
indicator (MUI v5's SvgIcon renders aria-hidden="true" unless given
titleAccess, so although the tooltip did inject an aria-label, the
element was pruned from the accessibility tree and never announced),
markdown line lengths on the rows this PR changed, and wording in the
docs and help panel that implied the event timeline follows the
dashboard selector when the two are genuinely independent.

Two pre-existing issues found along the way are recorded on #387
rather than fixed here: ValidateTimeRange accepts end_time equal to
start_time despite its error text, and the undocumented index_name
parameter.

dpage added 4 commits July 29, 2026 11:16
The dashboard time selector offers only the five rolling presets, and
`/api/v1/metrics/query` enforced that list inline, so an arbitrary
incident window could not be requested even though the query layer has
always bucketed over an absolute start and end pair. This adds the
server half of custom time-range support.

`metrics.TimeWindow` and `metrics.ResolveTimeWindow` become the single
source of truth for what constitutes a valid window. A `time_range`
other than `custom` still delegates to the unchanged `ParseTimeRange`,
whilst `custom` requires `time_start` and `time_end` as RFC 3339
timestamps and rejects a missing or unparsable timestamp, an end that
is not after the start, a start at or after the present moment, and a
span longer than 366 days; the span cap matters because the bucket
width derives from the span, so an unbounded window would be a
resource-exhaustion surface rather than merely a slow query. An end in
the future is clamped to now instead of rejected, since a picker set to
the current day routinely overshoots by a few minutes.

`QueryTimeSeries` now takes the resolved `TimeWindow` rather than a
time-range string, so resolution happens once at the HTTP boundary; the
`timeSeriesQueryFunc` seam and its test fakes follow. The handler maps
any resolution error to a 400 carrying the resolver's own message, and
keeps the historical `1h` default when `time_range` is absent. The
performance-summary and database-summary handlers keep their own preset
checks, which are out of scope here.

The OpenAPI specification documents `time_start` and `time_end` on
`/metrics/query` and advertises `custom` on `time_range`, and the
static `openapi.json` is regenerated to match.
Issue #345 asked for an arbitrary start and end time on the dashboard,
so that a user can line the graphs up with a known incident window
rather than a rolling window ending now. This is the client half of
that work: the picker, the context plumbing, and the selector control.

The TimeRange union gains a 'custom' member, and setCustomTimeRange now
switches the range to 'custom' rather than preserving the previous
preset; the old behaviour left the query layer with no way to tell that
an arbitrary window was in force. The existing context test that
asserted the preserved range has been updated to match, because this is
a deliberate change to tested behaviour.

Auto-refresh is suspended whilst a custom range is active, since
re-fetching a fixed historical window returns identical data on every
poll. The suspension is derived rather than stored, exposed as
autoRefreshSuspended, so the user's own autoRefresh.enabled preference
survives untouched and resumes as soon as they return to a preset; the
selector surfaces the pause with a tooltipped indicator, as there is no
other UI control for the dashboard's auto-refresh today.

CustomTimeRangePopover reads no context and takes the current window
plus an onApply callback, so the event timeline can reuse it in a
follow-on task despite keeping its range state elsewhere. Apply stays
disabled until both bounds parse and the end is after the start.

@mui/x-date-pickers and dayjs are new runtime dependencies, with
LocalizationProvider and AdapterDayjs installed at the application
root.
This is the second half of the client work for issue #345, wiring the
window that the picker now produces through to the data layers so that
the dashboard graphs and the event timeline actually honour it.

useMetrics reads customStart and customEnd from DashboardContext itself
and hands them to buildMetricsUrl, which emits time_start and time_end
only for the 'custom' range; the bounds deliberately stay out of
MetricQueryParams, so none of the twenty-odd consumer components change
and they carry on passing timeRange.range as before. Both values join
the fetch effect's dependencies and the initialLoadDoneRef reset list,
because without the first a newly applied window would never refetch
and without the second the loading state would not show whilst it did.
A custom range whose bounds have somehow gone missing is a transient
state the server answers with a 400, so the hook skips the request
altogether and leaves whatever data and error state is already present.

The timeline keeps its own range state, and its selector gains a Custom
toggle that opens the very same popover; an applied window is described
in the toggle's title attribute rather than in its label, because the
timeline toolbar is considerably tighter on space than the dashboard
selector. Custom windows are not written to localStorage, so the stored
preset survives untouched and a reload returns to it rather than to a
stale historical window.

The bounds calculation that EventTimeline/utils.ts and useTimelineEvents
each carried a diverging copy of now lives once, in utils/timelineRange,
which both call; the range types move there with it and are re-exported
from the hook for existing callers. Rather than teaching two switch
statements about arbitrary windows, there is now one function that knows
about them, and the bounds the canvas draws can no longer drift from the
bounds the API is queried with.
The three preceding commits added custom time-range support to the
server and the web client, so this documents it across the API
reference, the dashboards user guide, the changelog, and the in-app
help panel.

The API reference gains a Metric Time Windows section covering
time_range=custom together with the RFC 3339 time_start and time_end
parameters, the five validation rules the server enforces, the
reasoning behind the future-end clamp and the 366-day span cap, and a
curl example. The section is explicit that only /metrics/query
supports a custom window, because performance-summary and
database-summaries still accept the presets alone and top-queries has
no time dimension at all.

The dashboards guide describes the picker, the auto-refresh pause and
why it exists, the fact that the window is neither persisted nor
reflected in the URL, and the event timeline's independent range
control. It also states plainly which views honour the selector: the
charts and the timeline do, including the two charts on the query
detail overlay, whilst the query leaderboards and the summary tiles
do not. That distinction is the one thing a reader is most likely to
get wrong, and issue #387 tracks closing the gap.

The help panel's Time Range Selector, Auto-Refresh, and Event
Timeline entries were all stale in the same way, listing the five
presets and promising unconditional refreshes, so each is corrected
and a Custom Time Range entry is added alongside them.

docs/admin-guide/api/openapi.json needed no change; regenerating it
with `make openapi` produced no diff, confirming the artefact
committed in 2e2d420 is current.
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The pull request adds custom absolute time-window selection to dashboard charts and the event timeline, propagates bounds through metrics requests, resolves and validates windows at the server boundary, suspends dashboard auto-refresh for custom ranges, and updates API and user documentation.

Changes

Custom time-window selection

Layer / File(s) Summary
Time-window models and resolution
client/src/components/Dashboard/types.ts, client/src/utils/timelineRange.ts, server/src/internal/metrics/query.go
Preset and custom ranges are represented and resolved into absolute bounds with validation, UTC normalization, future-end clamping, and a 366-day span limit.
Dashboard picker and metrics integration
client/src/components/Dashboard/*, client/src/contexts/DashboardContext.tsx, client/src/hooks/useMetrics.ts, client/src/App.tsx
A Dayjs-backed picker updates dashboard context, displays the active window, suspends auto-refresh, and sends custom bounds with metrics requests.
Event timeline range control
client/src/components/EventTimeline/*, client/src/hooks/useTimelineEvents.ts
The timeline gains an independent Custom control, applies absolute bounds to event queries, and avoids persisting custom windows.
Metrics API and timeline validation
server/src/internal/api/*, server/src/internal/metrics/*
The metrics handler resolves windows once and forwards metrics.TimeWindow to the query layer; timeline requests enforce the shared span cap and add integration coverage.
Documentation and validation coverage
docs/*, client/src/components/HelpPanel/pages/MonitoringPage.tsx, .claude/golang-expert/metrics-queries.md
Documentation and tests describe picker validation, auto-refresh behavior, supported views, endpoint limitations, and server-side resolution.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

  • #387 — The PR establishes custom time-window support, while this issue proposes extending the behavior to additional query views and summary tiles.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds arbitrary custom time ranges, wires them through metrics queries and dashboard views, and keeps preset ranges working.
Out of Scope Changes check ✅ Passed No clear out-of-scope code changes are present; the added docs, tests, and wiring all support the custom time-range feature.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding custom time-range selection for graphs and the event timeline.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-345-custom-time-range

Comment @coderabbitai help to get the list of available commands.

@codacy-production

codacy-production Bot commented Jul 29, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 1 medium

Results:
1 new issue

Category Results
Complexity 1 medium

View in Codacy

🟢 Metrics 273 complexity · 33 duplication

Metric Results
Complexity 273
Duplication 33

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
client/src/components/EventTimeline/__tests__/config.test.ts (1)

21-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

vi.clearAllMocks() does not drop the throwing getItem implementation.

clearAllMocks only clears calls/results, so the mockImplementation that throws at Line 49 survives into any test added after it. Prefer resetting implementations in the hook.

♻️ Suggested change
     beforeEach(() => {
-        vi.clearAllMocks();
+        vi.resetAllMocks();
     });

Note resetAllMocks also clears return values, so each test must set its own mockReturnValue (they already do, except the options-only test which never reads storage).

Also applies to: 48-54

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/src/components/EventTimeline/__tests__/config.test.ts` around lines 21
- 23, Update the test setup around beforeEach and the throwing getItem mock to
reset mock implementations between tests instead of only clearing calls. Use
resetAllMocks, ensuring each test re-establishes any required mockReturnValue
while leaving the options-only test independent of storage.
client/src/components/Dashboard/CustomTimeRangePopover.tsx (1)

55-63: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider mirroring the server's max-span check here for immediate feedback.

isWindowValid only checks presence and end > start. Per the PR objectives, the server resolver rejects spans over 366 days; surfacing that same limit here would let users see the problem before submitting, instead of relying on a later API failure.

♻️ Suggested addition
 const isWindowValid = (start: Dayjs | null, end: Dayjs | null): boolean => {
     if (start === null || end === null) {
         return false;
     }
     if (!start.isValid() || !end.isValid()) {
         return false;
     }
-    return end.isAfter(start);
+    if (!end.isAfter(start)) {
+        return false;
+    }
+    return end.diff(start, 'day') <= 366;
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/src/components/Dashboard/CustomTimeRangePopover.tsx` around lines 55 -
63, Update isWindowValid to enforce the same maximum 366-day span as the server
after validating both dates and their ordering. Return false when the interval
exceeds that limit while preserving the existing null, invalid-date, and
valid-range behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@client/src/components/Dashboard/TimeRangeSelector.tsx`:
- Around line 149-155: Make the auto-refresh pause indicator keyboard-focusable
in the autoRefreshSuspended rendering block by assigning the
PauseCircleOutlineIcon a suitable tabIndex so its Tooltip can be triggered
without pointer input. Preserve the existing tooltip text and styling.

In `@client/src/components/EventTimeline/TimelineHeader.tsx`:
- Around line 185-192: Cap custom timeline ranges before they reach the event
query flow, using the same maximum-span constant or validation behavior as the
metrics endpoint. Update the custom range handling around TimelineHeader and its
apply/query path, preserving valid ranges while rejecting or constraining spans
exceeding the configured limit.

In `@docs/admin-guide/api/reference.md`:
- Line 215: Shorten the descriptions in the Markdown table rows for GET
/api/v1/metrics/query and the related rows around the custom-window section so
every changed row is no more than 79 characters. Preserve the endpoint meaning
and keep each table row on a single line without splitting Markdown syntax.

In `@docs/user-guide/dashboards/index.md`:
- Around line 80-85: The dashboard documentation must distinguish the range
selectors: update the “Views That Honour the Selector” text in
docs/user-guide/dashboards/index.md lines 80-85 to state that time-series charts
use the dashboard range while the event timeline uses its own independent range;
also update client/src/components/HelpPanel/pages/MonitoringPage.tsx lines
184-185 to describe the timeline’s “own selected time range.”

---

Nitpick comments:
In `@client/src/components/Dashboard/CustomTimeRangePopover.tsx`:
- Around line 55-63: Update isWindowValid to enforce the same maximum 366-day
span as the server after validating both dates and their ordering. Return false
when the interval exceeds that limit while preserving the existing null,
invalid-date, and valid-range behavior.

In `@client/src/components/EventTimeline/__tests__/config.test.ts`:
- Around line 21-23: Update the test setup around beforeEach and the throwing
getItem mock to reset mock implementations between tests instead of only
clearing calls. Use resetAllMocks, ensuring each test re-establishes any
required mockReturnValue while leaving the options-only test independent of
storage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 67ec2b5f-0b5c-4784-be2c-d662de33d51f

📥 Commits

Reviewing files that changed from the base of the PR and between 19c645d and e4fb378.

⛔ Files ignored due to path filters (1)
  • client/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (36)
  • .claude/golang-expert/metrics-queries.md
  • client/package.json
  • client/src/App.tsx
  • client/src/__tests__/App.test.tsx
  • client/src/components/Dashboard/CustomTimeRangePopover.tsx
  • client/src/components/Dashboard/TimeRangeSelector.tsx
  • client/src/components/Dashboard/__tests__/CustomTimeRangePopover.test.tsx
  • client/src/components/Dashboard/__tests__/TimeRangeSelector.test.tsx
  • client/src/components/Dashboard/index.ts
  • client/src/components/Dashboard/styles.ts
  • client/src/components/Dashboard/types.ts
  • client/src/components/EventTimeline/TimelineHeader.tsx
  • client/src/components/EventTimeline/__tests__/TimelineCustomRange.test.tsx
  • client/src/components/EventTimeline/__tests__/config.test.ts
  • client/src/components/EventTimeline/index.tsx
  • client/src/components/EventTimeline/utils.ts
  • client/src/components/HelpPanel/pages/MonitoringPage.tsx
  • client/src/components/__tests__/EventTimeline.test.tsx
  • client/src/contexts/DashboardContext.tsx
  • client/src/contexts/__tests__/DashboardContext.test.tsx
  • client/src/hooks/__tests__/useMetrics.test.ts
  • client/src/hooks/useMetrics.ts
  • client/src/hooks/useTimelineEvents.ts
  • client/src/utils/__tests__/timelineRange.test.ts
  • client/src/utils/timelineRange.ts
  • docs/admin-guide/api/openapi.json
  • docs/admin-guide/api/reference.md
  • docs/changelog.md
  • docs/user-guide/dashboards/index.md
  • server/src/internal/api/metrics_handlers.go
  • server/src/internal/api/metrics_handlers_test.go
  • server/src/internal/api/openapi.go
  • server/src/internal/api/openapi_test.go
  • server/src/internal/metrics/query.go
  • server/src/internal/metrics/query_timeseries_db_test.go
  • server/src/internal/metrics/timewindow_test.go

Comment thread client/src/components/Dashboard/TimeRangeSelector.tsx
Comment thread client/src/components/EventTimeline/TimelineHeader.tsx
Comment thread docs/admin-guide/api/reference.md Outdated
Comment thread docs/user-guide/dashboards/index.md Outdated
dpage added 3 commits July 29, 2026 12:35
CodeRabbit raised two valid points against e4fb378 on PR #388, and
this addresses both.

First, the Markdown rows I added or changed in the API reference ran
past the project's 79-character limit. Table rows cannot be wrapped
without breaking the syntax, so the wording is shortened instead: the
endpoint summary now reads "Query metrics for preset or custom
windows", and the parameter table drops to short cells. The list of
preset values that the `time_range` cell used to carry has moved up
into the prose above the table, which wraps freely, so nothing is
lost. The neighbouring rows I did not touch keep their existing
lengths.

Second, both the dashboards guide and the help panel could be read as
saying the event timeline follows the dashboard time range selector.
The timeline holds its own range state, so applying a custom window on
the dashboard leaves the timeline untouched and vice versa; both
descriptions now say so explicitly. The same misreading was available
in two further places, so those are corrected as well: the Time Range
Selector introduction claimed the selector "controls the time window
for all charts", and the changelog entry lumped the charts and the
timeline together as honouring one window.
The pause indicator added for the custom time-range selector was
unreachable by keyboard and, because MUI's SvgIcon marks itself
aria-hidden unless given titleAccess, its accessible name existed in
the markup but was never exposed to assistive technology. The Tooltip
title was therefore the only explanation available, and only to users
with a pointer.

The indicator now hangs off a focusable span carrying role="img" and an
aria-label with the same wording as the tooltip, so screen readers
announce the paused state without the tooltip being involved at all,
whilst the span's tabIndex lets a sighted keyboard user focus it and
read the tooltip too. Anchoring on a span rather than the icon avoids
the inconsistent browser handling of tabindex on SVG elements, and
inline-flex keeps the rendered layout byte-for-byte identical.

Addresses review feedback on #388; refs #345.
`/api/v1/timeline/events` only ever checked that `end_time` was after
`start_time`, which was harmless whilst the event timeline could send
nothing but the five rolling presets, the largest of which was 30 days.
The custom-range picker added earlier in this pull request now sends
arbitrary absolute timestamps, so the endpoint can be handed a
decade-long window and will happily union-scan every history table for
it; that is a resource-exhaustion surface this pull request itself
opens.

The handler therefore rejects a span longer than
`metrics.MaxCustomTimeSpan` with a 400 carrying the same message the
metrics resolver uses, `invalid time range: span must not exceed 366
days`. The constant is imported rather than duplicated, which costs
nothing because the `api` package already depends on `metrics` for the
query layer, so there is no new dependency edge and no cycle. Nothing
else in the handler moves, and the metrics endpoint is untouched.

The new table-driven test covers the boundary in both directions: a
span exactly at the cap is accepted whilst one second beyond it is
rejected. Bringing the file to the 90% floor also needed the
handler's post-validation paths, which no test reached before, so a
Postgres-backed integration suite now drives the visibility filter
(superuser bypass, an unfiltered request restricted to the visible
set, a visible and an invisible `connection_id`, an intersected
`connection_ids` list, an empty visible set), both 500 paths, and the
configured branch of `RegisterRoutes`. `timeline_handlers.go` moves
from 55.7% to 100% line coverage. The OpenAPI parameter descriptions
record the limit and the static specification is regenerated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/src/internal/api/timeline_handlers_integration_test.go`:
- Around line 104-161: Register a test cleanup immediately after pgxpool.New
succeeds in newTimelineTestEnv, using t.Cleanup to close the pool on every
subsequent failure path, including insertTimelineConnection and
insertTimelineClearedAlert. Keep the existing explicit cleanup behavior
consistent and avoid double-closing the pool during normal teardown.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8fa59bff-6c81-452c-858e-1cd3460b6184

📥 Commits

Reviewing files that changed from the base of the PR and between e4fb378 and 4484044.

📒 Files selected for processing (12)
  • .claude/golang-expert/metrics-queries.md
  • client/src/components/Dashboard/TimeRangeSelector.tsx
  • client/src/components/Dashboard/__tests__/TimeRangeSelector.test.tsx
  • client/src/components/HelpPanel/pages/MonitoringPage.tsx
  • docs/admin-guide/api/openapi.json
  • docs/admin-guide/api/reference.md
  • docs/changelog.md
  • docs/user-guide/dashboards/index.md
  • server/src/internal/api/openapi.go
  • server/src/internal/api/timeline_handlers.go
  • server/src/internal/api/timeline_handlers_integration_test.go
  • server/src/internal/api/timeline_handlers_test.go
🚧 Files skipped from review as they are similar to previous changes (8)
  • docs/admin-guide/api/openapi.json
  • client/src/components/HelpPanel/pages/MonitoringPage.tsx
  • client/src/components/Dashboard/TimeRangeSelector.tsx
  • client/src/components/Dashboard/tests/TimeRangeSelector.test.tsx
  • .claude/golang-expert/metrics-queries.md
  • docs/user-guide/dashboards/index.md
  • docs/admin-guide/api/reference.md
  • docs/changelog.md

Comment thread server/src/internal/api/timeline_handlers_integration_test.go Outdated
dpage added 2 commits July 29, 2026 12:55
The Go linter enforces US spelling, so the subtest name 'visible
connection_id is honoured' failed the misspell check on CI whilst
passing locally. Reworded to 'respected', which sidesteps the
en-GB/en-US split rather than writing a spelling that sits oddly
against the rest of the project's prose.
CodeRabbit spotted on PR #388 that newTimelineTestEnv leaked its pgx
pool whenever one of the seeding helpers failed, because those helpers
call t.Fatalf, which runs runtime.Goexit and so prevents the helper
from ever returning the cleanup closure that the caller was expected
to defer. The teardown, and with it pool.Close, therefore never ran.

I have moved the whole teardown onto t.Cleanup, registering the pool
close as soon as the pool exists so that every later exit path is
covered, and dropping the returned closure along with the now-redundant
explicit pool.Close calls on the Ping and schema paths; each resource
is thus released exactly once. The callers lose their defer lines but
the assertions are untouched.

Refs #345
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add custom time-range selection for graphs and query views

1 participant