Repository navigation
DESKTOP: the Blocked Process Reports, Deadlocks, System Events, Default Trace, Long Queries, Plan Corrections, Query Heatmap and Query Store Regressions grids say where their data starts (#4966) - #4998
Merged
Conversation
…cked Process Reports and Deadlocks grids, with their tests (#4966) The probes read where the collector's coverage starts through the shared floor. An event grid names the earlier of that and the earliest event it shows, so a server's stored history never makes a notice name a later time than a row on screen. The tab is wired in the next commit; the read-path and banner pins fail until then.
…here their data starts (#4966) Each grid gets a banner in the Queries tab's existing style, raised at every read of the grid: the range load and the slicer drag. The notice names the earlier of the collector's coverage start and the earliest event the grid shows.
…s at its oldest row (#4966) A read that keeps the newest N rows reaches back no further than the oldest it returned, whatever the store covers. The Blocked Process Reports (200) and Deadlocks (50) grids now name that row in the Showing since banner once their read returns a full page. The notice comes from the rows, so it does not wait on the coverage probe; a read under its cap keeps its result.
…race grids, with their tests (#4966) The eight system_health grids share one probe over system_health_events, and Default Trace has its own over default_trace_events. Both read the collector's UTC collection_time, so the coverage needs no server clock; the Default Trace events are compared in UTC after the read's own per-row conversion, so a clock behind UTC or a day that crosses a daylight-saving change names the right instant. The tab is wired in the next commit; the read-path, banner and chart pins fail until then.
…heir data starts (#4966) One banner above the sub-tab control, in the Queries tab's existing style, covers the nine grids that read system_health_events and default_trace_events. Each grid starts its probe beside its read and names the earlier of the collector's coverage start and the earliest event it shows. The Corruption and Contention chart sub-tabs take the banner down.
…ce and capped Blocking grids' data-start notices (#4966) System Events and Default Trace, against a store: rows that start inside the range give a notice at the coverage start, a quiet start gives none, history from before the coverage gives a notice at the history's start (Default Trace on a clock 5 hours behind UTC names it in UTC), and history that reaches the range start gives none. The two Blocking caps: a 200-row Blocked Process Reports read and a 50-row Deadlocks read give a notice at the oldest returned row, and a read one row under the cap keeps its result.
…e tests that pin the tab to them (#4966) GetLongQueriesDataStartAsync asks the shared probe over long_query_completions, and LongQueriesRowCap is the LIMIT of the read. The tests pin the tab's probe beside its read, the banner call from the event time of each row the grid shows, the cap rule, and the banner declared above the grid; they also run Long Queries against a store. The tab and the banner are wired in the next commit.
LoadLongQueriesAsync starts the coverage probe beside its read and raises a Showing since banner above the grid, in the style the Blocking grids use. The banner names the earlier of the coverage start and the earliest event the grid shows (the read windows on collection time but the grid shows event time), and the oldest event when the read returned a full page of its newest 200.
…e over plan_correction (#4966)
…robe and its rows, up to its cap (#4966)
…ry_stats and the regressions' baseline window (#4966)
…nce banner, the Regressions keyed on its baseline window (#4966)
…cked tests for the Showing since notice (#4966) Plan Corrections: rows that start inside the range name the coverage start, a quiet start (monitored for months, first row 5 hours in) names nothing, a read that fills the 200-row cap names its oldest row over a range the store covers (200 and 220 seeded), and one row under the cap names nothing. Query Heatmap: the table has no schedule edge, so its coverage is its oldest row. Rows that start inside the range name the first 5-minute column drawn (a first row 3 minutes past a boundary names the boundary), and an older row before the range names nothing. Query Store Regressions: the notice keys on the baseline window, 7 days before the range, through the shared baseline-start helper. A server added two days before a one-day range names where that window's data starts, where a notice keyed on the range stays silent, and rows reaching back before the baseline name nothing.
…an the slack starts no coverage probe (#4966) The 90-minute slack belongs to the coverage probe alone, where it absorbs a first collection that lands a little after the window starts. A grid that filled its row cap dropped rows for certain, so its banner now shows whenever the oldest row shown is later than the window's start; before, the slack hid it, and on a range of an hour or less no row could sit past it. A window no longer than the slack can never get a coverage note (the probe's answer is at or before the window's end, and a note needs a start more than the slack after the window's start), so the shared server probe returns at once for such a window and starts no query. The capped path does not need the probe.
…he store-backed tests run the tab's own banner step (#4966) The banner's time was printed in whole minutes. A capped read has no slack, so a grid whose oldest row came 42 seconds into the first minute of its range showed a banner that read "10:15" for a range from 10:15:00, as if the grid started at the range's start. The one formatter every desktop "Showing since" note goes through now prints yyyy-MM-dd HH:mm:ss, as Lite's banner does. The Query Store Regressions banner step moves out of the load method into ViewerServerTab.ShowQueryStoreRegressionsDataStartAsync, which compares the probe's answer with the baseline window's start. The load method and the tests both call it. The nine store-backed tests for Plan Corrections, the Query Heatmap and Query Store Regressions no longer copy the tab's decision. Each runs the tab's own step (ShowEventDataStartAsync, or the Regressions step) on a real banner control and reads the banner text, so a change to the rule fails a test. The two Regressions cases also run on ranges of 90 and 60 minutes: the probe asks about the baseline window, which is longer than the shared probe's slack whatever the range, so it still runs there, and a probe keyed on the range's own start would be skipped and answer nothing.
…re the first collection, the DMV collector, and a capped DMV read (#4966) A range that ends before the server's first collection finds no coverage, yet the grid lists the history that collection stored. The event surfaces now name the earliest event shown when the probe found no coverage (a probe that threw still names nothing), so the grid no longer starts late with no note. The probe's answer keeps "no coverage" apart from "failed" (DataStartAnswerAsync). The Blocked Process Reports coverage is the earlier of the XE collector's and the DMV snapshot collector's, so a server with XE collection off reads as covered from the DMV collector's first collection, and a quiet DMV start is not mistaken for a cut. Each source read now reports whether it filled its own 200-row cap. The merge drops rows an XE report already covers and rows that repeat a pair within a minute, so the merged list can hold fewer than 200 rows while the DMV read left older reports out; the notice names the oldest row that read returned, with no slack.
… time to the second (#4966)
erikdarlingdata
marked this pull request as ready for review
October 3, 2026 08:52
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #4966.
DESKTOP slice, three batches: the two event grids of the Blocking tab, then the System Events and Default Trace grids, then the Long Queries grid. The second batch also makes a grid whose read hit its row cap name its oldest row, which changes the two Blocking grids too. The third batch adds store-backed tests for the System Events, Default Trace and capped Blocking cases. A fourth batch adds the Plan Corrections grid, the Query Heatmap and the Query Store Regressions grid. The surfaces that remain are listed under Handoff.
Why
A range that starts before a server's coverage (a newly added server, or a custom range past the retention horizon) drew the shorter list with no notice, and the missing span read as a quiet period. The Blocked Process Reports and Deadlocks grids filter on the event's own time, and a server's first collection stores the server's event history, so a row can carry an event time from before the server was added. A notice that named only the collector's coverage start would name a time later than a row on screen.
What changes
GetBlockedProcessReportsDataStartAsyncandGetDeadlocksDataStartAsyncask the shared probe (DataWindowFloor.GetForServerAsync) where the collector's coverage starts: the later of the registry'screated_dateand the purge edge, moved earlier by a row in the range.ViewerEventDataStart.Ofapplies the event-time rule: the notice names the earlier of the coverage start and the earliest event the grid shows. A probe that failed names nothing, so a failed probe never lets the grid's own first row stand in for coverage. A probe that found no coverage in the range is not a failure: the range ends before the server's first collection. The grid can still list stored history from before that collection, so the notice names the earliest event shown, under the usual slack.ViewerServerTab.ShowEventDataStartAsyncawaits the probe through the existingDataStartOrNullAsynccatch and raises the Queries tab's "Showing since" banner (UpdateTruncationBanner, the 90-minute slack).Each grid gets a banner in the existing style (
BlockedProcessReportsTruncationBanner,DeadlocksTruncationBanner), above the grid and beside the separately-monitored note.The probe runs at every read of each grid. Blocked Process Reports: the range load (
LoadBlockedProcessReportsAsync) and the slicer drag (OnBlockingSlicerChanged). Deadlocks: the range load (LoadDeadlocksAsync, which the Daily Summary and the chart drill-downs also call) and the slicer drag (OnDeadlockSlicerChanged).A grid whose read hit its row cap names its oldest row (
ViewerEventDataStart.OfwithreadHitCap,ReadHitCap, and an optionalrowCaponShowEventDataStartAsync). The two Blocking grids pass their caps (BlockedProcessReportsRowCap, the merge's 200, andDeadlocksRowCap, 50), each pinned to the LIMIT of its read.System Events: one probe over
system_health_events(GetSystemHealthEventsDataStartAsync) serves the eight grids that read it, andGetDefaultTraceDataStartAsyncserves Default Trace. One banner (SystemEventsTruncationBanner) sits above the sub-tab control, in a new two-row grid around it, in the existing style. The Corruption and Contention chart sub-tabs take it down (LoadSystemEventsAsync).Each of the eight grids starts its probe beside its read and raises the banner from the rows it shows after its significance filter (
EventTime, naive UTC). Default Trace does the same overEventTimeUtc.Long Queries:
GetLongQueriesDataStartAsyncasks the shared probe overlong_query_completions(oncollection_time, the column the read windows on), andLongQueriesRowCap(200) is pinned to the LIMIT of the read.LoadLongQueriesAsyncstarts the probe beside its read and raisesLongQueriesTruncationBanner, above the grid in the existing style (one new row in the tab's grid), from the event time of each row shown (a row with no event time is ignored). A full page of the newest 200 names its oldest event.Plan Corrections:
GetPlanCorrectionsDataStartAsyncasks the shared probe overplan_correction. A test proves the catalog's index for that table leads with(server_id, collection_time), so the probe accepts it (the table is also in the probe's acceptance theory).PlanCorrectionsRowCap(200) is pinned to the LIMIT ofPlanCorrectionsSql, which is newest-first on the column the grid shows.LoadPlanCorrectionsAsyncstarts the probe beside its read and raisesPlanCorrectionsTruncationBannerthroughShowEventDataStartAsyncfrom the collection time of each row shown, with the cap, so a full page names its oldest row. The banner is a new row of the tab's grid (now three rows), in the existing style.Query Heatmap:
GetQueryHeatmapDataStartAsyncasks the shared probe overquery_stats. The read is the raw table only (no rollup route, no LIMIT; both pinned for all five metrics), so the probe is the raw table's; a rollup route added later must move it to that rollup's floor. The chart draws one column per 5-minute bin that holds a row, so a stretch with no row has no column and the chart cannot tell a server that did not exist yet from a quiet one: it needs the notice. The read now sits in one method (ReadAndDrawQueryHeatmapAsync) behind both entry points, the range load and the metric change, with the probe beside it; the banner (QueryHeatmapTruncationBanner, a new row above the chart) names the earlier of the coverage start and the first column drawn.Query Store Regressions: the read compares the range with a baseline of the 7 days before it, so the notice keys on that earlier window.
QueryStoreRegressionsBaselineStartis the one place the baseline's start is computed (the read's baseline arm and the probe both take it), andGetQueryStoreRegressionsDataStartAsyncasks the shared probe overquery_store_statsfrom that start.LoadQueryStoreRegressionsAsyncraisesQueryStoreRegressionsTruncationBanner(a new row above the grid) against the baseline start, so a server added two days before the range, which covers the range whole but holds two days of baseline, gets a notice that a range-keyed one would not give.A capped verdict gets no slack. The 90-minute slack belongs to the coverage probe alone, where it absorbs a first collection that lands a little after the window starts. A grid that filled its row cap dropped rows for certain, so
ShowEventDataStartAsynccompares the oldest row shown with the bare window start and raises the banner whenever that row is later than the start; before, the slack hid it, and on a range of an hour or less no row could sit past it.A window no longer than the slack starts no coverage probe. The probe's answer is at or before the window's end, and a note needs it more than the slack after the window's start, so
DataWindowFloor.GetForServerAsync(the form every server-tab probe goes through, except the three Queries-tab probes of query stats, Query Store and procedure stats, which shareRawWindowFloor.GetAsyncwith the MCP tools) returns null at once for a window of 90 minutes or less. The capped path does not need the probe and keeps working on a short window.Every desktop "Showing since" time prints to the second (
yyyy-MM-dd HH:mm:ssin the display zone), as Lite's banner does. A capped note then never names the range's own start minute for a row some seconds into it.The Query Store Regressions banner step is one shared function (
ShowQueryStoreRegressionsDataStartAsync) that the tab and its store-backed tests both call. It still compares the probe's answer with the start of the 7-day baseline window.Design choices:
Blocked Process Reports takes its coverage from both collectors the grid merges: the earlier of the XE collector's (
blocked_process_reports) and the DMV snapshots' (dmv_blocking_snapshots). With XE collection off, the DMV rows alone fill the grid. A range that starts before the DMV collector's coverage then gets the notice, and a range it covers gets none.The grids are capped (200 reports, 50 deadlocks, 200 long queries, newest first). A read that returns a full page names its oldest row in the notice. The grid reaches back no further than that row, even where the store covers the whole range. That notice comes from the rows, so it does not wait on the coverage probe. A read under its cap keeps the rule it had: the earlier of the coverage start and the earliest row shown. A read that returns exactly its cap counts as capped, since the read cannot tell whether older rows were left out. Blocked Process Reports merges two reads, each capped at 200, so the cap is judged per read. When either read fills its own cap, the notice names that read's oldest row, or the later of the two when both fill. That holds even when the merged list has fewer than 200 rows once duplicates are dropped.
system_health_events.event_timeis UTC: it is the XE@timestamp, which the collector compares with its own UTC clock, and the grids bind naive-UTC bounds to it. So the eight grids need no clock conversion. Default Trace stores the server's local clock, so its read converts each row with the server's clock; the probe readscollection_time(UTC) on both tables, so the coverage needs no conversion, and the earliest event is measured on the rows the read already converted per row (the offset at that row's own date) and trimmed to the window in UTC.The System Events and Default Trace reads have no row cap (no LIMIT), so the cap rule does not apply to them. A test pins that, so a LIMIT added later has to be wired to the rule.
Long Queries windows on
collection_timebut showsevent_time. The session's first collection stores the events its ring buffer still held, so rows collected inside the range can carry earlier event times: the notice names the earlier of the coverage start and the earliest event shown, and history that reaches back past the range start gives no notice. The read sorts onevent_time, which the collector uses as its watermark, so a row without one is not expected; a row that had none would sort first and take a page slot, but could not name a start.Plan Corrections goes through
ShowEventDataStartAsync, as Blocking and Long Queries do, so the banner call inViewerServerTab.Queries.csis not the plainUpdateTruncationBanner(..., await DataStartOrNullAsync(floorTask, ...), ...)shape. The census of that shape inViewerDataStartBannerTestsstill moves from 6 to 7, because the Regressions load adds one such call (it has no slicer, so one); the Plan Corrections call has its own pin inViewerPlanCorrectionsDataStartTests.Query Heatmap and Query Store Regressions read
query_statsandquery_store_stats, which the probe answers from the oldest row the server holds at or before the range's end (the schedule gives these tables no edge, and they are dense), not from the registry's created date. Plan Corrections has a schedule edge.Query Store Regressions ranks its top 50 by added duration, not by time, so its rows say nothing about how far back the comparison reached and the cap rule does not apply (pinned). Its rows carry no earlier start either, so its notice is the coverage of the baseline window alone.
The heatmap's first column is a 5-minute bin stamped at its start, so it can start up to 4 minutes before the first row the probe found; the notice names the column, never a time later than one on screen.
Surfaces checked
event_time). Coverage from both the XE and DMV collectors. Cap rule applied to each source read (200 each). Read paths: range load, slicer drag.deadlock_time); cap rule applied (50). Read paths: range load, slicer drag; the Daily Summary and drill-down paths reuse the range load.system_health_events, one banner above the sub-tab control. Event-time rule applied (the grids filter onevent_time, UTC). Read paths: one per grid, each with its own probe. No row cap.default_trace_events, on the same banner. Event-time rule applied; the events are stored on the server's clock and compared in UTC after the read's per-row conversion. Read path: the one range load. No row cap.collection_time, the grid showsevent_time); cap rule applied (200). Read path: the one range load (LoadLongQueriesAsync); the grid has no slicer.plan_correction; cap rule applied (200, newest first oncollection_time, the column the grid shows). Read path: the one range load (LoadPlanCorrectionsAsync); the grid has no slicer.query_stats(the read has no rollup route); no cap (no LIMIT, pinned). Read paths: the range load and the metric change, through one method.query_store_statsfrom the baseline start. No cap rule: the top 50 are ranked by added duration, not by time. Read path: the one range load; no slicer.Every other surface of the issue's desktop list is under Handoff, not claimed here.
Test plan
origin/dev: 0 warnings, 0 errors.origin/dev:ViewerLongQueriesDataStartTests(11),ViewerEventDataStartTests,ViewerSystemEventsDataStartTests,ViewerDataStartBannerTests,DataWindowFloorTests,RawWindowFloor*,LongQueryBannerSessionScopeTests,ViewerGridPayloadColumnOrderPinTests,ViewerGridTimeColumnSortMemberTests,ViewerSystemEventsTests,ViewerDefaultTraceServerClockTests,RepoFileAdoptionTests,LivePostgresCollectionHygieneTests,LiveCleanupConversionRatchetTests. Total 373 run, failed 0, skipped 4 (the liveRawWindowFloor*tests, which need a store).origin/dev:ViewerEventDataStartLiveTests(8),ViewerSystemEventsDataStartLiveTests(12),ViewerEventCapDataStartLiveTests(6),RawWindowFloorQuietStartLiveTests(3),ComposeDataFloorLiveTests(14). Total 43, failed 0, skipped 0.ViewerSystemEventsDataStartLiveTests). Default Trace runs on a clock 5 hours behind UTC with the events stored on that clock, and the history case names the history's start in UTC (5 days before the end of the range), not shifted by 5 hours. Long Queries stores the history collected at the first collection with event times from before it.ViewerEventCapDataStartLiveTests): a server monitored 120 days with minute-spaced events. A Blocked Process Reports read of 200 rows (and of 260 seeded, which returns 200) and a Deadlocks read of 50 (and of 70, which returns 50) give a notice at the oldest returned row, which is inside the range; the same rows with the cap rule left out give none, so the cap is what raises it. A read one row under the cap (199 and 49) keeps its result: no notice.ViewerEventDataStartTests), plus a probe that throws and a census that every tab read of the two Blocking grids is paired with its probe.collection_time; the reads carry no cap. The four notice cases run through the real Default Trace read (ReadDefaultTraceEventsAsync) with a clock 5 hours behind UTC. Two more cross a daylight-saving change (the spring and the fall day) and name the instant with that date's offset, not the snapshot's.Ofdisabled, 2 of the 20 tests ofViewerEventDataStartTestsfailed (the rule test and the banner test); restored, 20 of 20 pass.ViewerLongQueriesDataStartTests, 11): the cap is the LIMIT of the read; the probe goes through the shared floor overlong_query_completionsoncollection_time; the tab pins its probe beside its read and its banner call fromrows.Select(r => r.EventTime)with the cap; a census over every tab file pairs the one read with the one probe and the one banner call; the banner is declared above the grid in the existing style. The banner cases: rows start inside the range, a quiet start, history before the coverage, history that reaches the range start, a row with no event time, and a full page against one row fewer.ViewerPlanCorrectionsDataStartTests(9),ViewerQueryHeatmapDataStartTests(13),ViewerQueryStoreRegressionsDataStartTests(9),ViewerDataStartBannerTests,ViewerEventDataStartTests,ViewerLongQueriesDataStartTests,ViewerSystemEventsDataStartTests,DataWindowFloorTests,RawWindowFloor*,ViewerQueriesRestTests,ScopedLoadOrderingTests: 180 run, failed 0, skipped 4 (the live floor tests, which need a store). The pin classes (Capability*,RepoFileAdoptionTests,LivePostgresCollectionHygieneTests,LiveCleanupConversionRatchetTests,ViewerGridPayloadColumnOrderPinTests,ViewerGridTimeColumnSortMemberTests): 279 run, failed 0, skipped 4.ViewerEventCapDataStartLiveTests,ViewerSystemEventsDataStartLiveTests,ViewerEventDataStartLiveTests,RawWindowFloorQuietStartLiveTests,ComposeDataFloorLiveTests,DataWindowFloorTests: 82 run, failed 0, skipped 0.ViewerQueryGridsDataStartLiveTests.cs, 9 tests, each class on its own scratch database with the registry row and the collector's runs fromEventGridSeed.AddServerAsync).ViewerPlanCorrectionsDataStartLiveTests(5): rows that start inside the range (a server added 2 days ago, its first row a day later) name the coverage start; a quiet start (120 days monitored, first row 5 hours into the range) names none; a read that fills the 200-row cap (200 seeded, and 220, which returns 200) names its oldest row, 199 minutes before the range's end and inside the range, where the same rows with the cap rule left out give none; one row under the cap (199) keeps its result.ViewerQueryHeatmapDataStartLiveTests(2): rows that start inside the range (first row 3 minutes past a 5-minute boundary) name the boundary, the first column drawn; an older row before the range (10 days back, then a gap) names none. A quiet start does not apply to the heatmap:query_statshas no schedule edge, so a server's coverage is its oldest row, and a server with nothing older reads as one added late.ViewerQueryStoreRegressionsDataStartLiveTests(2): a server added 2 days before a one-day range names where the baseline window's data starts (the day it was added), though the same coverage compared with the range's own start is silent; rows reaching back 10 days, before the baseline window starts, name none. Three runs in a row: 9 run, failed 0, skipped 0 each time.*DataStartTests,*DataStartLiveTestsandViewerDataStartBannerTests,RawWindowFloor*,DataWindowFloorTests,RepoFileAdoptionTests,LivePostgresCollectionHygieneTests,LiveCleanupConversionRatchetTestsandDocCommentHygieneTests. Without a store: 313 run, failed 0, skipped 39 (the live classes). Against a store: 313 run, failed 0, skipped 0. Build: 0 warnings, 0 errors. The hygiene census flagged the shared scratch helper until it carried the own-store note; it does now.ViewerShortWindowDataStartTests, pure, through the real banner control): a capped read on a one-hour range whose oldest row came 20 minutes in shows its banner, with and without a coverage answer; one whose oldest row is at the window's start shows none; the slack stays on the coverage probe (a first collection 85 minutes in is absorbed, 95 minutes in is named), and a page one row under the cap is not capped. RED: with the old tab, the capped case fails (line 59).ViewerEventDataStartTests). RED: the notice was null.ViewerBlockedProcessReportsCoverageLiveTests, 4 tests). With XE collection off and the first collection two days before the range's end, the notice names the DMV collector's coverage (RED: null). A quiet DMV start over 40 days gets no notice (RED: the probe found no coverage). 300 DMV rows, 3 a minute, that merge to fewer than 200 name the DMV read's oldest row (RED: null).ViewerEventDataStartTestscover a failed probe against one that found no coverage, the per-read cap and the no-coverage range. Changed on purpose: the dead-probe pin passesprobeFailed: true, the tab's call-site pins take the new argument, and the census countsReadRecentBlockedProcessReportsAsync.ShowEventDataStartAsyncignoring its rows, 2 of 5 Plan Corrections and 1 of 2 Heatmap tests fail.*DataStart*class and the censuses pass: 0 failed without a store (the live tests skipped), and 0 failed in 317 live tests.Not in this PR
Not done in this PR, in the order I would take them:
RawWindowFloor.GetAsync(query stats, Query Store, procedure stats) still start for a window of 90 minutes or less: the same skip belongs in those three viewer methods. The MCP tools share the function, so it is not put in the shared one.ViewerEventDataStartLiveTests.cs,ViewerEventGridsDataStartLiveTests.cs) still restate the tab's rule instead of running the tab's step. They move to the tab's step in the next desktop PR, asViewerQueryGridsDataStartLiveTests.csnow does.ViewerServerTab.BlockChain.cs): not yet checked.CHANGELOG
No entry in this PR; the release owner adds it under [Unreleased].
Proposed entry: