FindingsListComponent
Comprehensive findings list with Evidence-Weighted Score (EWS) integration, filtering, and bulk selection.
Overview
The FindingsListComponent displays a sortable, filterable table of vulnerability findings with Evidence-Weighted Score display. The list sends no score request when it opens: a score is computed only through an explicit action by a session that holds vuln:operate (see Score Computation).
Selector
<app-findings-list />
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
findings | Finding[] | [] | Array of findings to display. A new array clears the computed scores and the bucket and flag filters |
serverTotal | number | null | null | Server-side total behind findings; when it exceeds the loaded rows the list states that it shows a bounded slice |
Outputs
| Output | Type | Description |
|---|---|---|
findingSelect | EventEmitter<ScoredFinding> | Emits when a finding row is clicked |
selectionChange | EventEmitter<string[]> | Emits finding IDs when selection changes |
Data Structures
Finding
interface Finding {
id: string; // Unique finding ID
advisoryId: string; // CVE/GHSA ID
packageName: string;
packageVersion: string;
severity: 'critical' | 'high' | 'medium' | 'low';
status: 'open' | 'in_progress' | 'fixed' | 'excepted';
publishedAt?: string; // ISO 8601
}
ScoredFinding
interface ScoredFinding extends Finding {
score?: EvidenceWeightedScoreResult;
scoreLoading: boolean;
}
Table Columns
| Column | Description | Sortable |
|---|---|---|
| Checkbox | Bulk selection | No |
| Score | EWS score pill with flags | Yes |
| Advisory | CVE/GHSA identifier | Yes |
| Package | Package name and version | Yes |
| Severity | CVSS severity level | Yes |
| Status | Finding status | Yes |
Features
Score Computation
The list never requests scores when it opens or when findings changes. Findings serves POST /api/findings/v1/ledger/scores as an audited computation (each uncached finding is computed, cached and recorded in the score history), and every Findings scoring route requires the vuln:operate scope, which no base role holds. The component checks the session with AUTH_SERVICE.hasScope(FINDINGS_SCORING_SCOPE) (FINDINGS_SCORING_SCOPE is vuln:operate; the Console’s scope check treats admin as satisfying it, while Findings itself authorizes vuln:operate).
- Without
vuln:operate: a note states that scores are not available to this session and why, and that an administrator can grantvuln:operatethrough a role in Setup > Identity & Access (/setup/identity-access). Each score cell reads “Not available”; no scoring request is sent. - With
vuln:operate: the list offers “Compute scores for this page”, an action markeddata-state-changing. It posts the ids of the visible page’s unscored findings (at most 50, under the 100-id batch limit). A score cell reads “Not computed”, “Computing…”, the score, or “No score returned” when Findings answers without one. A failed request stays visible as an alert, and the action stays available for a retry. The action is disabled, with the reason as its title, while a computation runs, when the page shows no findings, or when every finding on the page already has an answer.
Bucket Filtering
The bucket chips appear only once a computed score exists; while some loaded findings have no score, a line states how many of them the counts cover. Unscored findings are never counted as zero. Select a chip to filter by its priority bucket; select the active chip again to clear it:
- Act Now (90-100)
- Schedule Next (70-89)
- Investigate (40-69)
- Watchlist (0-39)
Flag Filtering
Filter by active score flags. Flags come from computed scores, so the checkboxes are disabled, with the reason as their title, until a computed score exists:
- Live Signal
- Proven Path
- Vendor N/A
- Speculative
- Anchored (DSSE/Rekor attested)
- Hard Fail (policy triggered)
- VEX Override (trusted VEX consensus cap)
Row Visual Indicators
Hard Fail Rows
Findings with hard-fail flag are highlighted with:
- Red left border (
3px solid #dc2626) - Subtle red background tint
- CSS class:
hard-fail-row
Anchored Rows
Findings with anchored flag display:
- Violet left border (
3px solid #7c3aed) - CSS class:
anchored-row
Note: If a finding is both anchored and hard-fail, hard-fail styling takes visual precedence.
Search
Text search across advisory ID and package name.
Sorting
Click column headers to sort. Click again to reverse order.
Bulk Selection
- Click checkboxes to select individual findings
- Use “Select All” to select visible findings
- Selection persists across filter changes
Usage Examples
Basic Usage
<app-findings-list
[findings]="findings"
(findingSelect)="openFinding($event)"
/>
With Selection Handling
<app-findings-list
[findings]="findings"
(selectionChange)="onSelectionChange($event)"
/>
<div class="bulk-actions" *ngIf="selectedIds.length > 0">
<button (click)="acknowledgeSelected()">
Acknowledge ({{ selectedIds.length }})
</button>
</div>
selectedIds: string[] = [];
onSelectionChange(ids: string[]): void {
this.selectedIds = ids;
}
Full Feature Example
@Component({
selector: 'app-vulnerability-dashboard',
template: `
<div class="dashboard">
<h1>Vulnerabilities</h1>
<app-findings-list
[findings]="findings()"
(findingSelect)="openFindingDetail($event)"
(selectionChange)="updateSelection($event)"
/>
@if (selectedFinding()) {
<app-finding-detail-panel
[finding]="selectedFinding()"
(close)="closeFindingDetail()"
/>
}
</div>
`
})
export class VulnerabilityDashboardComponent {
findings = signal<Finding[]>([]);
selectedFinding = signal<ScoredFinding | null>(null);
selectedIds = signal<string[]>([]);
constructor(private findingsService: FindingsService) {
this.loadFindings();
}
async loadFindings(): Promise<void> {
this.findings.set(await this.findingsService.getFindings());
}
openFindingDetail(finding: ScoredFinding): void {
this.selectedFinding.set(finding);
}
closeFindingDetail(): void {
this.selectedFinding.set(null);
}
updateSelection(ids: string[]): void {
this.selectedIds.set(ids);
}
}
Dependency Injection
The component computes scores through ScoringService, which calls the SCORING_API provider. The application registers HttpScoringApi for it in app.config.ts:
import { SCORING_API, HttpScoringApi } from '@app/core/services/scoring.service';
providers: [
HttpScoringApi,
{ provide: SCORING_API, useExisting: HttpScoringApi },
]
It reads the session’s scopes through the optional AUTH_SERVICE token. A host without an auth session holds no scope, so the list shows the scores-not-available state.
Mock API for Testing
import { FixtureScoringApi } from '@app/core/testing/scoring.testing';
TestBed.configureTestingModule({
providers: [
{ provide: SCORING_API, useClass: FixtureScoringApi }
]
});
Empty States
No Findings
<!-- Displays: "No findings to display" -->
<app-findings-list [findings]="[]" />
No Matches
When filters result in no matches:
<!-- Displays: "No findings match the current filters" -->
Accessibility
- Proper table semantics with
<table>,<thead>,<tbody> - Sortable columns use
aria-sort - Checkboxes have accessible labels
- Filter chips are keyboard navigable
- Focus management on filter/sort changes
- Screen reader announces result counts
Styling
app-findings-list {
--table-header-bg: #f9fafb;
--table-border-color: #e5e7eb;
--table-row-hover: #f3f4f6;
--table-row-selected: #eff6ff;
}
Related Components
- ScorePill - Score display in table
- ScoreBadge - Flag badges in table
- ScoreBreakdownPopover - Score details on click
- BulkTriageView - Bulk operations
