Files
git.stella-ops.org/docs/ui/components/findings-list.md
StellaOps Bot 17613acf57 feat: add bulk triage view component and related stories
- 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.
2025-12-26 01:01:35 +02:00

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

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;
}