Skip to content

Web charts: show the queries, blocking or deadlocks at the clicked time (#5230) - #5286

Merged
erikdarlingdata merged 8 commits into
devfrom
feat/5230-chart-at-this-time
Oct 5, 2026
Merged

erikdarlingdata merged 8 commits into
devfrom
feat/5230-chart-at-this-time

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Fixes #5230.

Why

The desktop viewer's Performance Trends charts have a right-click item. It jumps from a point on a chart to what was running then. The item is "Show Active Queries at This Time" on most charts, "Show Blocking at This Time" on the blocking charts and "Show Deadlocks at This Time" on the deadlock chart.

The web viewer's chart menu had Copy Image, Save Image As, Reset zoom, Export Data to CSV and Show Data Source. It had nothing that goes from a spike to the queries, blocking or deadlocks around it.

What changes

  • Each chart gets one item, as on the desktop. A right-click on the plot of a server-tab line chart adds the "at This Time" item that matches what the chart plots, after the usual items:

    • Show Blocking at This Time on Blocking Events (Overview and Blocking tabs), Lock Waits and Blocking Severity.
    • Show Deadlocks at This Time on Deadlocks (Overview and Blocking tabs) and Deadlock Severity.
    • Show Active Queries at This Time on every other server-tab line chart.

    renderLineChart takes atTime: { server, item }. line() and fanout() in pages/server-tabs.js hand the server over and read the item from atTimeItem. panels.js passes atTime through to the line chart. The nine charts that call zoomableLineChart directly pass the server and take the Active Queries default.

  • The item uses the time of the drawn point nearest the click. A click a pixel beside a one-bucket spike on a 7-day chart therefore still opens the hour that holds the spike. The hover tooltip names the same point, because both now use one nearestPointIdx. A click past either edge of the plot snaps to the first or last point.

  • The item sets a custom range of one hour: the clicked time plus or minus 30 minutes. This is the shortest range that the range picker allows. A range cannot end in the future. Near now, the item therefore shifts the hour back to end at the current time. The hour still holds the clicked time. The chart's status line shows a range that the page refuses, and the tab does not change.

  • The page rebuilds through the router. applyCustomRange gains { redraw: false }. The item applies the range without a panel redraw. Then it sets the hash (#/server/<name>/queries or /blocking, with the server name URL-encoded), or raises hashchange itself when that tab is already open. A full render makes the range picker in the page head show the new range. A panel redraw does not.

  • The item's own grid scrolls into view. Once the router builds the tab, a once-only hashchange listener runs. It scrolls the last panel with the item's title (Active Queries, Blocking or Deadlocks) to the top of the view. The Blocking tab has a Deadlocks chart and, below it, a Deadlocks grid. Show Deadlocks at This Time lands on the grid. The item adds the listener only after the page takes the range, so a refused range leaves none behind.

  • The item appears only on a right-click on the plot. A menu opened any other way has no clicked time and offers no item. The other ways include the chart menu button, Shift+F10, and a right-click on the legend, the status line or the open menu. A menu held open across the 60-second page refresh keeps its clicked time.

  • Some charts get no item. The PostgreSQL Aurora CPU chart passes atTime: false, because the PostgreSQL page has no Queries or Blocking tab. The FinOps and Custom Views charts are unchanged.

  • WebChartAtTimeBehaviourTests and its Node harness web-chart-at-time-harness.mjs are new.

  • This change touches web JavaScript and tests only. It has no server code, read or MCP tool changes.

Test plan

  • WebChartAtTimeBehaviourTests: 26 cases, 0 failed. The cases run the shipped charts.js under Node. The harness copies every module under js/ and js/pages/, so a new page module needs no list edit. The cases also scan server-tabs.js, panels.js and charts.js for the hand-offs. They cover:
    • One item per chart: Active Queries by default (and for an item name the page does not know), Blocking, Deadlocks.
    • Each item sets exactly the clicked time plus or minus 30 minutes. It moves the hash to its tab without a panel redraw. It raises an event only when the tab is already open.
    • The chart shows a refused range, and the tab stays.
    • The grid scroll lands on the Active Queries panel, the Blocking grid and the Deadlocks grid. It does not land on the Blocking Events chart or on the Deadlocks chart above the grid. The listener runs once and is not added for a refused range. The panels that the scroll looks for exist in their tabs, with the Deadlocks grid below its chart.
    • The nearest drawn point, the clamp at the plot edges, the near-now shift and the URL-encoded server name.
    • No item appears on a chart without atTime, from the chart menu button, or from the legend, status line, button or open menu. A held-open menu keeps its time.
    • The nine direct chart calls and the line() and fanout() hand-offs (server and item).
    • The item named by each blocking and deadlock chart and by no other, and every PostgreSQL line() opting out.
  • The cases that guard the item choice, the scroll and the hand-offs each fail when their change is undone. This was checked by undoing each change. The changes were:
    • Every chart offers all three items again (6 cases fail).
    • Lock Waits is left without its item.
    • The scroll takes the first panel instead of the last (Show Deadlocks lands on the chart).
    • The listener has no once, or is added before the range check.
    • The Deadlocks grid is renamed.
    • The line() hand-off, the fanout() hand-off or the Aurora opt-out is removed. Before these cases, removing any of these three failed nothing.
  • BaselineDiscontinuityRenderCensusTests (Lite, scans server-tabs.js): 24 cases, 0 failed.
  • Related web classes: 144 cases, 0 failed. The classes are chart, chart zoom, chart window domain, integer ticks and module load. The other classes are render settle, server page tabs and tab pins, Blocking columns, deadlock process rows and viewer context menu.
  • Darling.Tests and Lite.Tests build with 0 warnings after merging dev.
  • The full suite and CI were not run locally, because this change touches web JavaScript and its harness only.
  • Try the items on a live server page in a browser. The scroll runs after the router builds the tab, while its panels are still loading. Because of this, where the grid ends up depends on layout. The Node harness cannot measure layout.

…er, so the range picker shows the new range (#5230)

Review finding 1 on PR #5286. The item set the custom range and then redrew only the panel grid. The range picker
lives in the page head, which only a full render rebuilds, so on the tab already open the picker kept the old preset
and the Custom form stayed hidden. From another tab, the panel redraw started a whole batch of reads under the new
range, and the hashchange render that followed aborted that batch.

applyCustomRange takes { redraw: false } for a caller that rebuilds the page itself. goToTime passes it, then always
renders through the router: it sets the hash, or raises hashchange itself when the hash already holds the target
(setting a hash to its current value fires none).

The harness stands in window.dispatchEvent and records the redraw option the item passes. The new case pins redraw
false for all three items, no raised event on a move to another tab, and exactly one on the tab already open.
…drawn point, the one the hover tooltip names (#5230)

Review finding 2 on PR #5286. The menu mapped the raw pointer x to a time, but the hover tooltip and the desktop's
chart drill-down both snap to the nearest drawn point. On a 7-day chart one pixel is about 19 minutes, so a right-click
a couple of pixels beside a one-bucket deadlock spike opened an hour about 40 minutes away that did not hold it, and
the Deadlocks grid came up empty.

The tooltip's nearest-point loop moves into one helper, nearestPointIdx, which the tooltip and the menu's timeAt now
both call, so there is a single loop and the two cannot disagree. A click past either edge snaps to the first or last
point, the same clamp values as before. A plot with no point gives no time, so no items.

The new case draws two points, at 00:00 and 00:10, and right-clicks near each (expecting that point's time, where the
raw pointer time would be 00:05.85 at the right one) and at the exact middle (either point, never 00:05).
…ide the plot, not on the legend, status line, button or menu (#5230)

Review finding 3 on PR #5286. The contextmenu listener sits on the whole chart box, which also holds the legend, the
annotation legend, the status line, the source box, the menu button and the open menu. timeAt mapped the pointer x of
any of them through the plot's x scale and clamped it, so a right-click on the legend under a 24-hour chart offered
"Show Active Queries at This Time" for a point on the axis the user never pointed at, and Shift+F10 on the focused
button did the same with the right-edge time.

The handler now names a time only when the event target is inside the plot's SVG (root.contains(e.target)). Anywhere
else, and from the keyboard, the menu holds just Copy Image, Save Image As and Export Data to CSV.

The harness gives FakeNode a contains method and lets a right-click name its target (the plot's SVG unless a case
says otherwise). The new case right-clicks the legend, the status line, the button and an item of the open menu, and
expects only the three usual items each time.
…s it, as the desktop does (#5230)

A right-click on a server-tab line chart used to offer all three items. The desktop's chart drill-downs give each chart
one: Show Blocking at This Time on the blocking charts, Show Deadlocks at This Time on the deadlock charts, and Show
Active Queries at This Time on the rest. atTime now carries an item, and line() and fanout() read it from atTimeItem
(Blocking Events, Lock Waits and Blocking Severity name "blocking"; Deadlocks and Deadlock Severity name "deadlocks").
… Show Deadlocks reaches the Deadlocks grid (#5230)

The desktop's Deadlocks item opens its Deadlocks sub-tab. On the web both the Blocking and the Deadlocks item opened
the top of the Blocking tab, with the Deadlocks grid below the fold. Each item now names the panel it brings into
view (Active Queries, Blocking, Deadlocks), and once the router has built the tab a once-only hashchange listener scrolls
the last panel with that title to the top: the Deadlocks chart and the Deadlocks grid share a title, and the grid is the
later one. The listener is added after the range is taken, so a refused range leaves none behind.
…ostgreSQL opt-out, is removed (#5230)

Most server-tab charts reach the chart menu through line() and fanout(), and the one PostgreSQL line chart opts out
with atTime: false, but the scan counted only the nine direct zoomableLineChart calls. Deleting any of the three left
every case passing. Two cases now pin the two hand-offs (server and item) and the opt-out of every PostgreSQL line().
@erikdarlingdata erikdarlingdata added the merge-train In a merge train: its CI ran on the train PR label Oct 5, 2026
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review October 5, 2026 22:53
@erikdarlingdata
erikdarlingdata merged commit 4e32d90 into dev Oct 5, 2026
17 of 21 checks passed
@erikdarlingdata
erikdarlingdata deleted the feat/5230-chart-at-this-time branch October 5, 2026 22:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-train In a merge train: its CI ran on the train PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant