- Exported BulkTriageViewComponent and its related types from findings module. - Created a new accessibility test suite for score components using axe-core. - Introduced design tokens for score components to standardize styling. - Enhanced score breakdown popover for mobile responsiveness with drag handle. - Added date range selector functionality to score history chart component. - Implemented unit tests for date range selector in score history chart. - Created Storybook stories for bulk triage view and score history chart with date range selector.
5.6 KiB
5.6 KiB
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 integrated score loading and display.
Selector
<app-findings-list />
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
findings |
Finding[] |
[] |
Array of findings to display |
autoLoadScores |
boolean |
true |
Auto-fetch scores when findings change |
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 Loading
When autoLoadScores is true, scores are fetched automatically via the SCORING_API injection token.
Bucket Filtering
Filter findings by priority bucket using the chip filters:
- All (default)
- Act Now (90-100)
- Schedule Next (70-89)
- Investigate (40-69)
- Watchlist (0-39)
Flag Filtering
Filter by active score flags:
- Live Signal
- Proven Path
- Vendor N/A
- Speculative
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)"
/>
Without Auto-Loading Scores
<app-findings-list
[findings]="findings"
[autoLoadScores]="false"
/>
With Selection Handling
<app-findings-list
[findings]="findings"
[autoLoadScores]="true"
(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()"
[autoLoadScores]="true"
(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 requires a SCORING_API provider:
import { SCORING_API, ScoringApiService } from '@app/core/services/scoring.service';
@NgModule({
providers: [
{ provide: SCORING_API, useClass: ScoringApiService }
]
})
export class AppModule {}
Mock API for Testing
import { MockScoringApi } from '@app/core/services/scoring.service';
TestBed.configureTestingModule({
providers: [
{ provide: SCORING_API, useClass: MockScoringApi }
]
});
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