Milestone 18e: Metrics + History + Exports + POAM tabs - #33
Conversation
Co-Authored-By: Bryce Anglin <brycemanglin@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| rule.history.map((h) => ({ | ||
| assetId: asset.assetId, | ||
| ruleId: rule.ruleId, | ||
| ...h, | ||
| })), |
There was a problem hiding this comment.
🟡 Object spread of ...h overwrites canonical ruleId from parent rule group
In HistoryList, each flattened entry is built as { assetId, ruleId: rule.ruleId, ...h }. Because ReviewHistoryEntry (defined at web/src/lib/api/index.ts:610-621) includes an optional ruleId?: string field, the ...h spread comes after the explicit ruleId: rule.ruleId and will overwrite it whenever the server includes ruleId in the history entry — including when it is null or a different value. This causes the Rule column (web/src/app/pages/collections/history-tab.tsx:379) to render blank or incorrect data, and corrupts the React key at line 372 which relies on r.ruleId. The fix is to place the explicit properties after the spread: { ...h, assetId: asset.assetId, ruleId: rule.ruleId }.
| rule.history.map((h) => ({ | |
| assetId: asset.assetId, | |
| ruleId: rule.ruleId, | |
| ...h, | |
| })), | |
| rule.history.map((h) => ({ | |
| ...h, | |
| assetId: asset.assetId, | |
| ruleId: rule.ruleId, | |
| })), |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Good catch — fixed by moving the spread before the explicit properties so assetId and ruleId from the parent groups always win. Pushed in the next commit.
Co-Authored-By: Bryce Anglin <brycemanglin@gmail.com>
Summary
Implements the four remaining data-visualization and extraction tabs in the Collection detail page: Metrics, History, Exports, and POAM. These replace the "Coming in milestone M18e" stubs.
Metrics tab (
collection-tab-metrics)/metrics/summary/collectionfor headline KPIs (assets, stigs, checklists, assessments, assessed, findings by severity, results by result type, statuses)./metrics/summary/assetfor a sortable per-asset table (name, STIGs count, assessed, findings H/M/L, pass, fail)./metrics/summary/stigfor a per-benchmark table (benchmarkId + title, assets, rules, findings H/M/L).History tab (
collection-tab-history)<details>).Exports tab (
collection-tab-exports)/archive/{format}, receives a Blob, and triggers a browser download via an anchor element +URL.createObjectURL.fetchBlob()inapi/index.tshandles raw binary responses, bearer injection, error extraction, and Content-Disposition filename parsing (including RFC 6266filename*UTF-8 support).POAM tab (
collection-tab-poam)/collections/{cid}/poamas a binary stream and triggers a browser download.Wiring
api/index.ts: 20+ new types and functions covering the metrics, history, blob-download, and POAM surfaces.api/hooks.ts:useMetricsCollection,useMetricsByAsset,useMetricsByStig,useReviewHistory,useReviewHistoryStats,useDeleteReviewHistory.detail.tsx: imports + conditional rendering for the four new tab components; stub filter now excludes the wired tabs.Review & Testing Checklist for Human
admin→ open a populated collection → Metrics tab → confirm KPI numbers are non-zero and match the API output from a rawcurl..cklfiles..xlsxthat opens in Excel / LibreOffice Calc.evaluator(Restricted or Read grant) → confirm the Prune card in the History tab is hidden (Manage-gated).Notes
downloadBlob()approach usesURL.createObjectURL+ an ephemeral<a>element; this is the standard SPA download pattern. The URL is revoked after 30 seconds to free memory.M18c) and Grants (M18f) remain as coming-soon cards now.Link to Devin session: https://app.devin.ai/sessions/022810763c4643c0848ba894c1512b92
Requested by: @Exonical