Skip to content

test(stand): cover metric drilldown for every metric - #2255

Merged
aleksdotbar merged 3 commits into
mainfrom
test/drilldown-metric-coverage
Aug 6, 2026
Merged

test(stand): cover metric drilldown for every metric#2255
aleksdotbar merged 3 commits into
mainfrom
test/drilldown-metric-coverage

Conversation

@aleksdotbar

@aleksdotbar aleksdotbar commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • reconcile drilldown evidence against the served metric value for all 59 catalogue metrics
  • pin the expectation per metric in drilldown_matrix.py, and fail when a metric is added without one
  • refuse-or-answer cross-check: a metric the catalogue calls incapable must return EVIDENCE_UNAVAILABLE
  • export parity for one metric per evidence presentation, including the capable-but-empty case
  • browser journeys for the evidence affordances no API test can reach: card overflow menu, team heatmap cell, multi-metric block selector, table Total row
  • guard that every registry evidence_ref parses as an evidence relation

Notes

Capability is declared per source, not per metric, so an evidence relation drifting from the observations derived from it moves every metric of that family at once — a single-metric test cannot see it. Reconciliation is tiered because an evidence row means a different thing per granularity and computation: exact row count, summed values, median identity, scaled ratio with the definition's affine and clamp applied, distinct dates, and an inequality where day flags collapse across a person's accounts.

No value is typed into a test — both sides come from the service, so the seed can change underneath it.

Not covered, deliberately: clicking a chart mark. Recharts marks carry no accessible handle, and its click path is the one the table cell already exercises.

Validation

  • ruff format --check, ruff check, mypy
  • cargo test -p analytics --bin analytics metric_definitions::builtin — 13 passed
  • offline collection: 266 tests

Refs #1603

Summary by CodeRabbit

  • New Features

    • Added coverage for viewing supporting data from collaboration cards, team heatmap cells, metric cards, timeseries blocks, and table totals.
    • Supporting-data dialogs now validate metric selection, evidence details, empty states, and CSV export behavior.
  • Bug Fixes

    • Expanded analytics validation to reconcile dashboard values with drilldown evidence across metric types and sources.
    • Added checks for pagination, response formats, derived values, and unsupported evidence scenarios.
  • Documentation

    • Updated testing guidance to describe generalized browser journeys, evidence validation, and CI check execution.

Drilldown capability is declared per source, not per metric, so an
evidence relation drifting from the observations derived from it moves
every metric of that family at once. Sweep the catalogue and require
each metric's evidence to add back up to its own served value.

Refs #1603

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
@aleksdotbar
aleksdotbar requested a review from a team as a code owner August 6, 2026 04:13
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 40 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: 6b83cb38-7f50-4a10-9dca-7aeff9be563a

📥 Commits

Reviewing files that changed from the base of the PR and between 0958842 and f0d8a41.

📒 Files selected for processing (7)
  • tests/stand/README.md
  • tests/stand/api/analytics/drilldown_matrix.py
  • tests/stand/api/analytics/test_drilldown.py
  • tests/stand/ui/evidence_requests.py
  • tests/stand/ui/pages/group_dialog.py
  • tests/stand/ui/test_team_grid_cell_evidence.py
  • tests/stand/ui/test_timeseries_block_evidence.py
📝 Walkthrough

Walkthrough

This change adds catalogue-wide metric drilldown reconciliation, evidence-reference registry validation, shared browser page objects, and Playwright coverage for card, cell, timeseries, and export evidence flows. Testing documentation now describes generalized browser journeys and non-required CI checks.

Changes

Metric Evidence Validation

Layer / File(s) Summary
Evidence contract and metric matrix
tests/stand/api/analytics/drilldown_matrix.py, src/backend/services/analytics/src/domain/metric_definitions/builtin.rs
Defines reconciliation tiers, transforms, metric expectations, export shapes, and validates builtin evidence references.
Catalogue-wide API reconciliation
tests/stand/api/analytics/test_drilldown.py, tests/stand/README.md
Generalizes pagination, metric lookup, capability checks, evidence reconciliation, and CSV/XLSX export validation across the metric catalogue.
Shared evidence-dialog page objects
tests/stand/ui/pages/group_dialog.py, tests/stand/ui/pages/person_view.py, tests/stand/ui/pages/team_view.py
Adds shared workflows for group dialogs, metric evidence dialogs, domain cards, heatmap cells, timeseries blocks, and total-row evidence.
Browser evidence journeys
tests/stand/ui/test_collaboration_card_evidence.py, tests/stand/ui/test_team_grid_cell_evidence.py, tests/stand/ui/test_timeseries_block_evidence.py, tests/stand/README.md, docs/TESTING.md
Adds browser coverage for collaboration cards, team cells, metric selectors, single-metric dialogs, total cells, and generalized compose-stand test execution.

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

Possibly related PRs

Suggested labels: stack:metric-drilldown

Suggested reviewers: cyberantonz, ktursunov

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.95% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the catalogue-wide metric drilldown test coverage added by the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 test/drilldown-metric-coverage

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.

@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: 8

🧹 Nitpick comments (6)
tests/stand/api/analytics/test_drilldown.py (5)

161-209: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Bound the walk when page_budget is None.

The loop terminates only on next_cursor is None or on a repeated cursor. page_budget=None removes the page cap, and cursors grows once per page. test_git_commit_drilldown_pages_and_reconciles and test_git_commit_drilldown_exports_all_rows call _walk with limit=1 and no budget. If the seed gains commits, those two tests issue one request per row. A seed change then produces a slow run rather than a readable failure.

Consider defaulting page_budget to _PAGE_BUDGET and letting callers raise it, so every walk carries a bound.

🤖 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 `@tests/stand/api/analytics/test_drilldown.py` around lines 161 - 209, The
_walk helper currently permits unbounded pagination when page_budget is None,
causing limit=1 callers to request indefinitely as data grows. Default
page_budget to _PAGE_BUDGET and preserve the existing override behavior so
callers can explicitly raise the bound while every walk remains capped.

557-564: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The git selection is now declared in two incompatible ways.

_seeded_request encodes the filter and display dimension at Lines 156-157. Both tests re-declare the same pair inline for _walk, and test_git_commit_drilldown_exports_all_rows then exports through _seeded_request. The walk and the export therefore describe one selection in two places. If either drifts, Line 604 compares an export of one selection against a walk of another, and the count mismatch reads as a serializer defect rather than a test defect.

Give _walk the same filter and dimension arguments from one shared constant, or let _walk accept the request _seeded_request builds.

Also applies to: 588-596

🤖 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 `@tests/stand/api/analytics/test_drilldown.py` around lines 557 - 564,
Centralize the git selection used by _seeded_request and both _walk calls so the
filter for source=github and the repository display dimension are defined only
once. Update the tests around _walk and
test_git_commit_drilldown_exports_all_rows to reuse that shared selection,
preserving identical parameters for walking and exporting.

540-550: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

The export tests compare counts, not cell values.

The CSV check compares the header labels and the row count. The XLSX check compares only the row count. A serializer that emitted the correct number of rows with wrong, shifted, or empty cells passes both. The test name states that the export carries every row, and the docstring states that comparing against the page is what catches a broken serializer.

Compare at least the first data row's cells against the matching row in walk.rows, projected through walk.column_keys.

🤖 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 `@tests/stand/api/analytics/test_drilldown.py` around lines 540 - 550, The
export assertions in the drilldown test only validate headers and row counts,
not serialized cell values. Update the CSV and XLSX checks around `_export` and
`_xlsx_rows` to compare at least the first data row with the corresponding
`walk.rows` entry, projecting values through `walk.column_keys`; preserve the
existing format, status, content type, and row-count assertions.

369-375: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

The bounded branch drops expectation.transform.

The unbounded path at Line 377 applies transform before comparing. The bounded path compares the raw evidence value. No COLLAPSE_BOUNDED_RATIO entry in MATRIX sets a transform today, so this is currently unreachable. A future bounded ratio with a clamp would be compared against an untransformed value and could pass or fail for the wrong reason.

Either apply the transform here as well, or assert expectation.transform is None in this branch so the omission is explicit.

🤖 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 `@tests/stand/api/analytics/test_drilldown.py` around lines 369 - 375, The
bounded branch in the drilldown assertion omits expectation.transform before
comparing evidence. Update the bounded path to apply expectation.transform
consistently with the unbounded path, or explicitly assert that
expectation.transform is None before the comparison; preserve the existing
bounded ratio validation behavior.

436-439: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert EVIDENCE_UNAVAILABLE on a structured error field, not the raw body.

response.parse(ProblemDocument) already exposes the parsed error. Use the error-class-specific field for the precondition code instead of matching the whole response text, so assertions fail when the named precondition changes.

🤖 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 `@tests/stand/api/analytics/test_drilldown.py` around lines 436 - 439, Update
the assertion in the drilldown test to check that “EVIDENCE_UNAVAILABLE” appears
in the appropriate structured error field from the parsed ProblemDocument
returned by response.parse, rather than in response.text. Preserve the existing
status assertion and diagnostic context for failures.
tests/stand/api/analytics/drilldown_matrix.py (1)

100-101: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused BY_KEY from tests/stand/api/analytics/drilldown_matrix.py.

BY_KEY is exported but no repository code reads it. Keep it only if a consumer is added; otherwise remove the unused export.

🤖 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 `@tests/stand/api/analytics/drilldown_matrix.py` around lines 100 - 101, Remove
the unused BY_KEY export from the drilldown_matrix definitions, including any
associated declaration or export wiring. Leave metric_key and source unchanged,
and do not retain BY_KEY unless repository usage is introduced.
🤖 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 `@tests/stand/api/analytics/drilldown_matrix.py`:
- Around line 113-188: Add an import-time duplicate-key validation adjacent to
BY_KEY, before or while constructing the lookup, so repeated metric_key values
in MATRIX raise an explicit failure instead of being silently overwritten.
Preserve the existing BY_KEY mapping for unique keys and make the guard identify
the duplicated key.

In `@tests/stand/api/analytics/test_drilldown.py`:
- Around line 387-422: Both Tier match statements silently accept future
unhandled variants. In tests/stand/api/analytics/test_drilldown.py lines
387-422, add a final unhandled arm to _reconcile that raises AssertionError
including metric_key and the tier; likewise add the same failing arm to the
match in _assert_shape at lines 296-321, ensuring any new Tier variant fails
explicitly.
- Around line 503-509: Update the incomplete-page branch in the drilldown
comparison to fail the test via pytest.fail instead of only issuing
warnings.warn, ensuring exceeding _PAGE_BUDGET cannot leave the comparison
passing while unreconciled. Preserve the existing diagnostic message and early
return behavior as appropriate.
- Around line 74-77: Update _request_for() to use None—not truthiness—as the
omission sentinel for entity_id, so an explicitly passed _EMPTY_ENTITY_ID is
preserved instead of replaced with dev_lead. Adjust the rejection test to pass
the empty identifier directly, while allowing callers to pass None when they
want the default entity ID.

In `@tests/stand/README.md`:
- Line 23: Update the introductory description in the README to remove or revise
the stale “four browser journeys” count so it no longer contradicts the
generalized journey descriptions and newly added browser evidence modules. Keep
the rest of the introduction unchanged.

In `@tests/stand/ui/test_team_grid_cell_evidence.py`:
- Around line 64-70: Update the cell evidence test around the evidence drilldown
assertions to verify that the opened evidence belongs to member, using the
request payload or returned evidence owner data exposed by the test flow. Keep
the existing table visibility, date header, row-count, and metric-selector
assertions unchanged.

In `@tests/stand/ui/test_timeseries_block_evidence.py`:
- Around line 61-70: Update the metric-selection test around selector.click()
and evidence.table() to verify that selecting other triggers a new drilldown
request or displays evidence content specific to other. Do not rely solely on
table visibility, since the existing table may belong to opened_with; preserve
the selector value assertion while adding a metric-specific replacement check.
- Around line 121-128: Strengthen the assertions around
open_total_row_evidence(TASKS_CLOSED) to verify the total-row request selects no
individual time bucket, or that the returned evidence covers the entire
displayed block period. Keep the existing visibility, column, and non-empty
table assertions while adding this full-period validation.

---

Nitpick comments:
In `@tests/stand/api/analytics/drilldown_matrix.py`:
- Around line 100-101: Remove the unused BY_KEY export from the drilldown_matrix
definitions, including any associated declaration or export wiring. Leave
metric_key and source unchanged, and do not retain BY_KEY unless repository
usage is introduced.

In `@tests/stand/api/analytics/test_drilldown.py`:
- Around line 161-209: The _walk helper currently permits unbounded pagination
when page_budget is None, causing limit=1 callers to request indefinitely as
data grows. Default page_budget to _PAGE_BUDGET and preserve the existing
override behavior so callers can explicitly raise the bound while every walk
remains capped.
- Around line 557-564: Centralize the git selection used by _seeded_request and
both _walk calls so the filter for source=github and the repository display
dimension are defined only once. Update the tests around _walk and
test_git_commit_drilldown_exports_all_rows to reuse that shared selection,
preserving identical parameters for walking and exporting.
- Around line 540-550: The export assertions in the drilldown test only validate
headers and row counts, not serialized cell values. Update the CSV and XLSX
checks around `_export` and `_xlsx_rows` to compare at least the first data row
with the corresponding `walk.rows` entry, projecting values through
`walk.column_keys`; preserve the existing format, status, content type, and
row-count assertions.
- Around line 369-375: The bounded branch in the drilldown assertion omits
expectation.transform before comparing evidence. Update the bounded path to
apply expectation.transform consistently with the unbounded path, or explicitly
assert that expectation.transform is None before the comparison; preserve the
existing bounded ratio validation behavior.
- Around line 436-439: Update the assertion in the drilldown test to check that
“EVIDENCE_UNAVAILABLE” appears in the appropriate structured error field from
the parsed ProblemDocument returned by response.parse, rather than in
response.text. Preserve the existing status assertion and diagnostic context for
failures.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cee4b90a-a142-4031-851a-d6365f495d9c

📥 Commits

Reviewing files that changed from the base of the PR and between 74728a4 and 0958842.

📒 Files selected for processing (11)
  • docs/TESTING.md
  • src/backend/services/analytics/src/domain/metric_definitions/builtin.rs
  • tests/stand/README.md
  • tests/stand/api/analytics/drilldown_matrix.py
  • tests/stand/api/analytics/test_drilldown.py
  • tests/stand/ui/pages/group_dialog.py
  • tests/stand/ui/pages/person_view.py
  • tests/stand/ui/pages/team_view.py
  • tests/stand/ui/test_collaboration_card_evidence.py
  • tests/stand/ui/test_team_grid_cell_evidence.py
  • tests/stand/ui/test_timeseries_block_evidence.py

Comment thread tests/stand/api/analytics/drilldown_matrix.py Outdated
Comment thread tests/stand/api/analytics/test_drilldown.py
Comment thread tests/stand/api/analytics/test_drilldown.py
Comment thread tests/stand/api/analytics/test_drilldown.py
Comment thread tests/stand/README.md
Comment thread tests/stand/ui/test_team_grid_cell_evidence.py
Comment thread tests/stand/ui/test_timeseries_block_evidence.py
Comment thread tests/stand/ui/test_timeseries_block_evidence.py Outdated
The catalogue's capability query also requires the definition's schema to
be checked, while the drilldown endpoint requires only healthy evidence,
so the catalogue withholds the capability for metrics whose evidence it
serves. Assert the direction that holds — an advertised metric must
answer — and pin the other as a strict xfail.

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
A dialog with rows in it proves the request succeeded, not that it carried
the person whose cell was clicked, the metric that was chosen, or the
block's period rather than one bucket. Read the selection off the wire.

Also make both `Tier` matches exhaustive, so a tier added without a rule
fails to type-check instead of asserting nothing, and drop an unused
lookup table.

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
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