Skip to content

Add reports merge and reports rows commands - #102

Merged
callumreid merged 8 commits into
mainfrom
feat/reports-merge-command
Aug 6, 2026
Merged

Add reports merge and reports rows commands#102
callumreid merged 8 commits into
mainfrom
feat/reports-merge-command

Conversation

@callumreid

Copy link
Copy Markdown
Contributor

No Linear issue yet. This machine has no LINEAR_API_KEY and the Linear MCP needs interactive OAuth, so the issue could not be created. Someone needs to file it and rename this PR to [COVAL-XXXX] ... before merge.

Blocked on backend. reports merge sends compare_by: custom + custom_dimensions, which the public API rejects until coval-ai/backend#6427 ships. Land and deploy that first.

Problem

The app can merge several reports into one grouped comparison (frontend/src/modules/reports/routes/useMergeReports.ts) — one group per source report, in a single shareable report. There was no way to do it from the CLI or the public API.

Change

coval reports merge

coval reports merge \
  --name "Q3 Scorecard" \
  --report-ids 01HAAAA…,01HBBBB… \
  [--dimension-name Report] [--permissions public|private]

Same algorithm as the app's action:

  1. GET /v1/reports/{id} per source, for its display name and run IDs.
  2. Page GET /v1/reports/{id}/rows per source, collecting simulation IDs.
  3. First-seen attribution — a simulation present in several selected reports lands in the first one's group only, so overlapping reports do not double-count.
  4. POST /v1/reports with the union of run IDs (order-preserving), compare_by: custom, view_mode: grouped, and one dimension holding a group per source report.

Guards: at least two report IDs, IDs must be distinct, and the merge fails loudly rather than silently truncating if a source exceeds the page ceiling (500 rows × 200 pages). If every source report is run-less the command explains that instead of letting the API reject an empty run_ids.

coval reports rows

coval reports rows <report_id> [--cursor …] [--limit …] [--metric-ids …] [--simulation-ids …]

GET /reports/{report_id}/rows is the endpoint merge pages through, and it was a recorded coverage gap. Exposing it makes merge a composition of first-class commands rather than a command reaching past the published surface. Human output prints Next cursor: … after the table; --format json and --agent emit the whole response including next_page_token, matching traces search.

reports create --view-mode

rows | grouped, matching the field the backend PR adds. --input-json can also carry custom_dimensions / custom_dimension_id now that the request model has them, with the same compare-by pairing guard the existing metadata_key check uses.

Coverage manifest

api-coverage.toml drops the GET /reports/{report_id}/rows known-gap entry (it now reaches a command) and bumps cli_supported_operations 124 → 125.

Two things I deliberately did not touch:

  • api-coverage-report.md is unchanged. It is generated from the live catalog, and regenerating it here would pull unrelated drift into this PR — see below.
  • The live audit already fails on main. I ran scripts/audit_api_coverage.py against a clean origin/main worktree: FAIL: 117/174, because the published catalog has renamed monitors-v1alerts-v1 while the CLI still ships coval monitors. That surfaces as 7 new_gaps (/alerts/*) and 7 unexpected_cli_operations (/monitors/*). With this PR the same run reports 118/174 with those same 14 entries and no stale_gaps — i.e. this PR's only effect on the audit is the intended +1. The monitorsalerts rename is pre-existing drift for the weekly chore/weekly-api-parity PR, not for this one. Note that the audit step is continue-on-error on ordinary PR runs.

Tests

Six new integration tests in tests/cli_tests.rs, all wiremock-backed:

  • merge builds one group per source, pages a multi-page source via next_page_token, unions run IDs in order, and drops a simulation shared between two reports from the second group (first-seen attribution).
  • merge rejects a single report ID and rejects duplicate IDs.
  • rows forwards cursor, limit, metric IDs and simulation IDs as query parameters.
  • create rejects --compare-by custom without dimensions, and dimensions without --compare-by custom.

Added a QueryParamAbsent matcher so the first page can assert no cursor is sent.

Observed locally (full CI command set):

  • cargo test --all-targets --all-features — 114 passed, 0 failed (3 + 114 across both targets)
  • cargo clippy --all-targets --all-features -- -D warnings — clean
  • cargo fmt --check — clean
  • python -m unittest discover --start-directory scripts --pattern 'test_*.py' — 31 passed
  • ruff check scripts / ruff format --check scripts — clean (with the pinned ruff==0.15.9 from scripts/requirements-audit.txt; a newer ruff reports pre-existing findings on untouched files)
  • python scripts/release_version.pyv0.6.0

Out of scope

reports update still exposes only the legacy top-level --compare-by / --metadata-key flags; the backend's typed view_config patch (which also gained the custom-dimension fields, and needs If-Match) has no CLI surface yet. Regrouping an existing report by a custom dimension therefore still needs a raw API call. Worth a follow-up.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 52 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: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 5eadc502-1f9f-4020-bb69-ed5648a2fb86

📥 Commits

Reviewing files that changed from the base of the PR and between 4d400a2 and fbc7d91.

📒 Files selected for processing (3)
  • src/agent_discovery.rs
  • src/commands/reports.rs
  • tests/cli_tests.rs

Walkthrough

The reports rows command retrieves paginated report rows with optional metric and simulation filters. Report models support custom dimensions, view modes, row responses, and tabular output. The reports merge command combines source reports into a grouped custom report with validation and first-seen simulation attribution. Discovery metadata, documentation, API coverage data, and CLI integration tests were updated.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the two primary commands added by the pull request.
Description check ✅ Passed The description directly explains the merge and rows commands, validation, tests, coverage updates, and backend dependency.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@callumreid
callumreid marked this pull request as ready for review August 4, 2026 18:26
@greptile-apps

greptile-apps Bot commented Aug 4, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds first-class report-row retrieval and report merging, extends report creation with custom dimensions and view modes, and updates command discovery and API coverage.

  • Adds coval reports rows with paging and filtering.
  • Adds coval reports merge with ordered run union, first-seen simulation attribution, and a pagination ceiling.
  • Extends report creation models and validation for custom grouping.

Confidence Score: 4/5

The null custom-dimensions case should be rejected locally before merge because it currently sends a request that violates the command’s custom-comparison contract.

The new presence-only validator accepts custom_dimensions: null, after which request deserialization removes the dimensions while retaining compare_by: custom.

Files Needing Attention: src/commands/reports.rs

Reviews (1): Last reviewed commit: "Add reports merge and reports rows comma..." | Re-trigger Greptile

Comment thread src/commands/reports.rs Outdated

@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.

🧹 Nitpick comments (4)
src/commands/reports.rs (2)

274-345: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Duplicate detection uses ids before the count check; both are correct here.

Paging control is sound. The loop bails when the final allowed page still returns a token, so a truncated merge cannot silently produce partial groups.

One note: the source reports are fetched and paged strictly in sequence. For many source reports this makes merge slow. This is acceptable for a CLI, so no change is required now.

🤖 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 `@src/commands/reports.rs` around lines 274 - 345, No change is required:
merge_reports correctly validates distinct IDs, enforces the minimum count, and
prevents truncated pagination; sequential report fetching is acceptable for this
CLI.

21-22: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider a larger merge page size.

RowsArgs documents the API limit as 1-2000. MERGE_ROWS_PAGE_SIZE is 500, so merge issues four times more requests than needed and reaches the page cap at 100,000 rows. A page size of 2000 reduces request count and raises the effective ceiling with the same cap.

The merge tests assert query_param("limit", "500"), so update mount_merge_source in tests/cli_tests.rs if you change this value.

🤖 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 `@src/commands/reports.rs` around lines 21 - 22, Increase MERGE_ROWS_PAGE_SIZE
from 500 to the documented API maximum of 2000 so merge requests fetch larger
batches. Update mount_merge_source in tests/cli_tests.rs and its merge test
expectations from limit=500 to limit=2000, preserving the existing page-cap
behavior.
src/client/models/report.rs (1)

69-74: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a serde default for hide_unassigned.

hide_unassigned is a required bool on deserialize. Users can supply custom_dimensions through --input-json on reports create, and that JSON is deserialized into CreateReportRequest. If the user omits hide_unassigned, deserialization fails instead of using the intended default. groups has the same shape concern.

♻️ Proposed change
 pub struct ReportCustomDimension {
     pub id: String,
     pub name: String,
+    #[serde(default)]
     pub groups: Vec<ReportCustomDimensionGroup>,
+    #[serde(default)]
     pub hide_unassigned: bool,
 }
🤖 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 `@src/client/models/report.rs` around lines 69 - 74, Update
ReportCustomDimension deserialization so omitted hide_unassigned and groups
fields receive their intended defaults via serde, while preserving explicitly
provided values. Use the existing default conventions in the surrounding report
models rather than introducing unrelated changes.
tests/cli_tests.rs (1)

3897-4030: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Good coverage of first-seen attribution and paging.

Two behaviors from merge_reports remain untested: the page-cap bail when a report never drains, and the bail when the source reports contribute no runs. Both produce user-facing errors, so a test for each would lock in the messages.

🤖 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/cli_tests.rs` around lines 3897 - 4030, Add tests alongside
test_reports_merge_builds_one_group_per_source_report covering merge_reports
aborting when a source report exceeds the page limit without draining and when
all source reports contribute no runs. Mock the required report/row requests,
assert both commands fail, and verify each user-facing error contains the
expected page-cap or no-runs message.
🤖 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.

Nitpick comments:
In `@src/client/models/report.rs`:
- Around line 69-74: Update ReportCustomDimension deserialization so omitted
hide_unassigned and groups fields receive their intended defaults via serde,
while preserving explicitly provided values. Use the existing default
conventions in the surrounding report models rather than introducing unrelated
changes.

In `@src/commands/reports.rs`:
- Around line 274-345: No change is required: merge_reports correctly validates
distinct IDs, enforces the minimum count, and prevents truncated pagination;
sequential report fetching is acceptable for this CLI.
- Around line 21-22: Increase MERGE_ROWS_PAGE_SIZE from 500 to the documented
API maximum of 2000 so merge requests fetch larger batches. Update
mount_merge_source in tests/cli_tests.rs and its merge test expectations from
limit=500 to limit=2000, preserving the existing page-cap behavior.

In `@tests/cli_tests.rs`:
- Around line 3897-4030: Add tests alongside
test_reports_merge_builds_one_group_per_source_report covering merge_reports
aborting when a source report exceeds the page limit without draining and when
all source reports contribute no runs. Mock the required report/row requests,
assert both commands fail, and verify each user-facing error contains the
expected page-cap or no-runs message.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f9a0dda-6586-4969-8bdf-b5d0cf40ab39

📥 Commits

Reviewing files that changed from the base of the PR and between a6a0de4 and 8e513db.

📒 Files selected for processing (7)
  • README.md
  • api-coverage.toml
  • src/agent_discovery.rs
  • src/client/mod.rs
  • src/client/models/report.rs
  • src/commands/reports.rs
  • tests/cli_tests.rs

`coval reports merge` combines two or more reports into one grouped report,
mirroring the app's "Merge reports" action: one group per source report, each
simulation attributed to the first selected report that contains it.

Also adds `coval reports rows` (the endpoint merge pages through, previously a
recorded coverage gap) and `--view-mode` on `reports create`.

Depends on the backend change that accepts custom dimensions on POST /v1/reports.
@callumreid
callumreid force-pushed the feat/reports-merge-command branch from f55c51e to ac52039 Compare August 5, 2026 16:54

@kdmelon kdmelon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

wee notes

Comment thread src/commands/reports.rs
Comment thread src/client/models/report.rs
Comment thread src/commands/reports.rs Outdated
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@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.

🧹 Nitpick comments (1)
tests/cli_tests.rs (1)

4124-4142: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Two negative validation tests depend on an outbound request failing. Each test supplies a payload that validation accepts, then asserts .failure() with a .not() predicate. Neither command sets --api-url, so the CLI reaches the default base URL and the exit status depends on the test environment rather than on CLI logic.

  • tests/cli_tests.rs#L4124-L4142: point the command at a MockServer that answers POST /v1/reports, then assert the absence of "custom_dimensions can only be set" against that deterministic run.
  • tests/cli_tests.rs#L4362-L4380: apply the same MockServer change, then assert the absence of "custom_dimension_id can only be set".
🤖 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/cli_tests.rs` around lines 4124 - 4142, The negative validation tests
at tests/cli_tests.rs:4124-4142 and tests/cli_tests.rs:4362-4380 currently
depend on the default network endpoint. Update both CLI test commands to use a
MockServer configured to answer POST /v1/reports, then retain the failure
assertions while checking absence of “custom_dimensions can only be set” at
4124-4142 and “custom_dimension_id can only be set” at 4362-4380.
🤖 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.

Nitpick comments:
In `@tests/cli_tests.rs`:
- Around line 4124-4142: The negative validation tests at
tests/cli_tests.rs:4124-4142 and tests/cli_tests.rs:4362-4380 currently depend
on the default network endpoint. Update both CLI test commands to use a
MockServer configured to answer POST /v1/reports, then retain the failure
assertions while checking absence of “custom_dimensions can only be set” at
4124-4142 and “custom_dimension_id can only be set” at 4362-4380.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 29036c91-4bad-4447-9c45-c17e1dc3dc2f

📥 Commits

Reviewing files that changed from the base of the PR and between 22937d6 and 4d400a2.

📒 Files selected for processing (8)
  • README.md
  • api-coverage-report.md
  • api-coverage.toml
  • src/agent_discovery.rs
  • src/client/mod.rs
  • src/client/models/report.rs
  • src/commands/reports.rs
  • tests/cli_tests.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • README.md
  • src/client/mod.rs
  • api-coverage.toml
  • src/agent_discovery.rs
  • src/client/models/report.rs

@callumreid
callumreid merged commit 428b8c4 into main Aug 6, 2026
7 checks passed
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.

5 participants