Files
git.stella-ops.org/docs/modules/ui/components/findings-list.md
2026-01-06 19:07:48 +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;
}