Skip to content

Give every evidence entry the same shape - #2531

Merged
dzarlax merged 2 commits into
constructorfabric:mainfrom
dzarlax:feat/evidence-single-entry-shape
Aug 14, 2026
Merged

Give every evidence entry the same shape#2531
dzarlax merged 2 commits into
constructorfabric:mainfrom
dzarlax:feat/evidence-single-entry-shape

Conversation

@dzarlax

@dzarlax dzarlax commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #2518.

The problem

The evidence dialog had two shapes. Opening it from a chart block passed every
metric in the block, so the dialog showed a metric picker in its header.
Opening the same dialog from a summary card, a distribution, a peer story, an
activity strip or a roster cell passed one metric, so it showed a plain title.

Same dialog, same table, same export — but it looked like two different
features, and from a card there was no way to reach a neighbouring metric
without closing the dialog and hunting for another control.

What changed

A surface now declares the metrics it holds, through a scope context:

  • a metric section declares its blocks' metrics
  • a roster row declares its columns, for that member

Every entry opens that set with the metric the reader clicked already
selected. openEvidenceTargets takes the active metric as an option instead
of always opening the first target, and falls back to the first when handed a
key that is not in the set.

The caller's own selection wins over the scope's copy of it (withOwnTarget),
so a card that drills with its breakdown dimensions, or a chart point that
drills with its bucket period and group filter, keeps exactly what it built —
the scope only supplies the neighbours.

Where there is no scope — a card rendered outside a section — the dialog opens
with one metric, as before.

Verification

  • pnpm typecheck, eslint, vitest run --project unit (1311 passing)
  • The two central behaviours were mutation-checked: making the card ignore the
    scope, and making the provider ignore the requested active metric, each
    fails the test written for it.
  • Exercised against a running instance, both scope sources:
    • from a distribution card in a metric section, the dialog opens with a
      picker listing that section's drillable metrics and the card's own metric
      selected; switching to another metric reloads the table under the new
      heading.
    • from a roster cell, the picker lists that grid's metric columns with the
      clicked column selected.

Opening supporting data from a chart block offered a metric picker; opening
the same data from a card, an activity strip or a roster cell did not. Two
doors into one dialog, and from the card there was no way to reach a
neighbouring metric without closing it and finding another control.

A surface now declares the metrics it holds, and every entry opens that set
with the metric the reader clicked already selected. The caller's own
selection wins over the surface's copy of it, so a card that drills with its
breakdown dimensions keeps them.

The provider takes the active metric as an option rather than always opening
the first target, and falls back to the first when asked for one that is not
in the set.

Signed-off-by: Alexey Panfilov <alexey.panfilov@constructor.tech>
@dzarlax
dzarlax requested a review from a team as a code owner August 14, 2026 07:55
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@dzarlax, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d850f95-e328-4c1a-aaa0-01c1ba8930ca

📥 Commits

Reviewing files that changed from the base of the PR and between 58a7087 and 2760520.

📒 Files selected for processing (16)
  • src/frontend/src/components/metric-evidence-context.test.ts
  • src/frontend/src/components/metric-evidence-context.ts
  • src/frontend/src/components/metric-evidence-dialog-provider.test.tsx
  • src/frontend/src/components/metric-evidence-dialog-provider.tsx
  • src/frontend/src/components/widgets/dashboard/members-grid.tsx
  • src/frontend/src/components/widgets/metric-views/collection-drilldown.tsx
  • src/frontend/src/components/widgets/metric-views/metric-activity.tsx
  • src/frontend/src/components/widgets/metric-views/metric-breakdown.test.tsx
  • src/frontend/src/components/widgets/metric-views/metric-card-actions.test.tsx
  • src/frontend/src/components/widgets/metric-views/metric-card-actions.tsx
  • src/frontend/src/components/widgets/metric-views/metric-histogram.test.tsx
  • src/frontend/src/components/widgets/metric-views/metric-timeseries-view.test.tsx
  • src/frontend/src/components/widgets/metric-views/metric-timeseries-view.tsx
  • src/frontend/src/components/widgets/metric-views/peer-story.test.tsx
  • src/frontend/src/lib/metrics/evidence-targets.test.ts
  • src/frontend/src/lib/metrics/evidence-targets.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@dzarlax
dzarlax enabled auto-merge August 14, 2026 08:37
@dzarlax
dzarlax added this pull request to the merge queue Aug 14, 2026
Merged via the queue into constructorfabric:main with commit 734350a Aug 14, 2026
54 checks passed
@dzarlax
dzarlax deleted the feat/evidence-single-entry-shape branch August 14, 2026 09:38
ktursunov pushed a commit that referenced this pull request Aug 14, 2026
Journey 7 asserted that a cell's dialog offers nothing to switch to. #2531
made every entry open the surface's metric set with the clicked one selected,
and for a roster cell that set is the member's own columns — so the assertion
described the shape the feature replaced, and the journey failed on it as soon
as the dialog was named again and the run got that far.

Assert the current contract instead: the picker is there, opened on the column
that was clicked. Then switch to another column and check the request still
asks about that member — the scope is built from the row, and one built from
the wrong axis would answer 200 with somebody else's rows, which is the very
thing this journey exists to catch.

Signed-off-by: Konstantin Tursunov <Konstantin.Tursunov@constructor.tech>
ktursunov pushed a commit that referenced this pull request Aug 14, 2026
Journey 7 asserted that a cell's dialog offers nothing to switch to. #2531
made every entry open the surface's metric set with the clicked one selected,
and for a roster cell that set is the member's own columns — so the assertion
described the shape the feature replaced, and the journey failed on it as soon
as the dialog was named again and the run got that far.

Assert the current contract instead: the picker is there, opened on the column
that was clicked. Then switch to another column and check the request still
asks about that member — the scope is built from the row, and one built from
the wrong axis would answer 200 with somebody else's rows, which is the very
thing this journey exists to catch.

Two things the switch has to respect. The dialog is named for the metric on
show, so it renames itself and the handle it opened under stops resolving.
And a neighbouring column the member has nothing recorded for answers honestly
with no records, so the person is what the assertion holds, not rows.

The cell's own label is read BEFORE the dialog opens: a modal hides the grid
behind it from the accessibility tree, and a role-based locator then resolves
to nothing.

Signed-off-by: Konstantin Tursunov <Konstantin.Tursunov@constructor.tech>
dzarlax pushed a commit to dzarlax/insight-back that referenced this pull request Aug 14, 2026
…ic#2540)

* ci(test-stand): build whichever tree the ref changes

The stand pins the frontend and the four backend services to their charts'
appVersions, which name what main released. A ref that changes one of those
trees and still pins it has the lane reporting on code the run never executed.

`changes` asked that question for src/backend/ only, so a diff touching just
the SPA ran against main's frontend image. dev-compose.sh carries a guard for
exactly this case, but it needs origin/main and the stand jobs clone at depth
1, so it returned early and said nothing.

Make the two trees symmetric on both sides of the interface. `test-stand up`
grows --build-backend and --build-frontend as independent axes, with --build
kept as the both-axes alias; `changes` emits one output per tree and the lanes
map each onto its own flag. A frontend diff now costs a pnpm build rather than
the 26-minute Rust compile --build would have pulled in.

The guard now says on stderr when it cannot resolve origin/main, so a shallow
CI log no longer reads as if it had vouched for the pinning.

Signed-off-by: Konstantin Tursunov <Konstantin.Tursunov@constructor.tech>

* fix(frontend): name the evidence dialog for the metric it shows

Giving every entry the metric picker (constructorfabric#2531) also gave every entry more than
one target, and the multi-target branch names the dialog from a title the
caller supplies — which only the timeseries block does. Opening from a card, a
chart point or a roster cell therefore fell back to the placeholder "Metric
evidence", where it had been the metric's own name.

A screen reader now hears the metric again, and the three deployed-stand
journeys that locate the dialog by that name pass. The joined title the
timeseries block passes still wins where it is given.

Signed-off-by: Alexey Panfilov <alexey.panfilov@constructor.tech>
(cherry picked from commit d0da982)

* docs(test-stand): trim the comments, correct the measured cost

The prose around the two build axes said in ten lines what fits in three.

The numbers it quoted were also stale. Measured across the last 60 completed
runs of this workflow (62 lane jobs, 21 of them on the build path): the
bring-up step takes 2.5-4.0 min pinned and 23.8-32.5 min building, and whole
jobs take 3-6 min pinned against 25-34 min building. So the compile adds ~28
min rather than the ~26 quoted, pull-mode jobs are nowhere near the 8 min the
timeout rationale assumed, and the 75-minute ceiling keeps about 2x over the
worst build observed.

Signed-off-by: Konstantin Tursunov <Konstantin.Tursunov@constructor.tech>

* test(stand): the roster cell dialog offers its row's metrics

Journey 7 asserted that a cell's dialog offers nothing to switch to. constructorfabric#2531
made every entry open the surface's metric set with the clicked one selected,
and for a roster cell that set is the member's own columns — so the assertion
described the shape the feature replaced, and the journey failed on it as soon
as the dialog was named again and the run got that far.

Assert the current contract instead: the picker is there, opened on the column
that was clicked. Then switch to another column and check the request still
asks about that member — the scope is built from the row, and one built from
the wrong axis would answer 200 with somebody else's rows, which is the very
thing this journey exists to catch.

Two things the switch has to respect. The dialog is named for the metric on
show, so it renames itself and the handle it opened under stops resolving.
And a neighbouring column the member has nothing recorded for answers honestly
with no records, so the person is what the assertion holds, not rows.

The cell's own label is read BEFORE the dialog opens: a modal hides the grid
behind it from the accessibility tree, and a role-based locator then resolves
to nothing.

Signed-off-by: Konstantin Tursunov <Konstantin.Tursunov@constructor.tech>

---------

Signed-off-by: Konstantin Tursunov <Konstantin.Tursunov@constructor.tech>
Signed-off-by: Alexey Panfilov <alexey.panfilov@constructor.tech>
Co-authored-by: Konstantin Tursunov <Konstantin.Tursunov@constructor.tech>
Co-authored-by: Alexey Panfilov <alexey.panfilov@constructor.tech>
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.

2 participants