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.
This commit is contained in:
StellaOps Bot
2025-12-26 01:01:35 +02:00
parent ed3079543c
commit 17613acf57
45 changed files with 9418 additions and 64 deletions

View File

@@ -0,0 +1,113 @@
# UI Components
This directory contains documentation for the StellaOps Angular UI components.
## Evidence-Weighted Score (EWS) Components
The EWS component suite provides visual representations of vulnerability scores based on evidence-weighted analysis.
### Core Components
| Component | Purpose | Location |
|-----------|---------|----------|
| [ScorePill](./score-pill.md) | Compact score display with bucket coloring | `shared/components/score/` |
| [ScoreBreakdownPopover](./score-breakdown-popover.md) | Detailed score breakdown with dimensions | `shared/components/score/` |
| [ScoreBadge](./score-badge.md) | Evidence flag badges (live-signal, proven-path, etc.) | `shared/components/score/` |
| [ScoreHistoryChart](./score-history-chart.md) | Timeline visualization of score changes | `shared/components/score/` |
### Feature Components
| Component | Purpose | Location |
|-----------|---------|----------|
| [FindingsList](./findings-list.md) | Findings table with EWS integration | `features/findings/` |
| [BulkTriageView](./bulk-triage-view.md) | Bulk triage interface with bucket summaries | `features/findings/` |
## Score Buckets
All EWS components use a consistent bucket system:
| Bucket | Score Range | Color | Priority |
|--------|-------------|-------|----------|
| Act Now | 90-100 | Red (`#DC2626`) | Critical - Immediate action required |
| Schedule Next | 70-89 | Amber (`#D97706`) | High - Schedule for next sprint |
| Investigate | 40-69 | Blue (`#2563EB`) | Medium - Investigate when possible |
| Watchlist | 0-39 | Gray (`#6B7280`) | Low - Monitor for changes |
## Evidence Flags
Findings can have special flags indicating evidence quality:
| Flag | Icon | Color | Description |
|------|------|-------|-------------|
| `live-signal` | Signal wave | Green | Active runtime signals detected |
| `proven-path` | Checkmark | Blue | Verified reachability path confirmed |
| `vendor-na` | Strikethrough | Gray | Vendor marked as not affected |
| `speculative` | Question mark | Orange | Evidence is speculative/unconfirmed |
## Quick Start
### Import Components
```typescript
// Score components
import {
ScorePillComponent,
ScoreBreakdownPopoverComponent,
ScoreBadgeComponent,
ScoreHistoryChartComponent,
} from '@app/shared/components/score';
// Findings components
import {
FindingsListComponent,
BulkTriageViewComponent,
} from '@app/features/findings';
```
### Basic Usage
```html
<!-- Display a score pill -->
<stella-score-pill [score]="78" size="md" />
<!-- Display score badges -->
<stella-score-badge type="live-signal" />
<stella-score-badge type="proven-path" />
<!-- Full findings list with scoring -->
<app-findings-list
[findings]="findings"
[autoLoadScores]="true"
(findingSelect)="onFindingSelect($event)"
/>
```
## Storybook
Interactive examples and documentation are available in Storybook:
```bash
cd src/Web/StellaOps.Web
npm run storybook
```
Navigate to:
- `Score/ScorePill` - Score pill variants
- `Score/ScoreBreakdownPopover` - Breakdown popover examples
- `Score/ScoreBadge` - Evidence flag badges
- `Score/ScoreHistoryChart` - History chart variants
- `Findings/FindingsList` - Findings list with scoring
- `Findings/BulkTriageView` - Bulk triage interface
## Design Tokens
Score colors are defined as CSS custom properties. See [design-tokens.md](./design-tokens.md) for the full token reference.
## Accessibility
All components follow WCAG 2.1 AA guidelines:
- Proper ARIA labels and roles
- Keyboard navigation support
- Focus management
- Color contrast ratios meet AA standards
- Screen reader announcements for dynamic content

View File

@@ -0,0 +1,246 @@
# BulkTriageViewComponent
Streamlined interface for triaging multiple findings at once with bucket-based organization.
## Overview
The `BulkTriageViewComponent` provides a triage-focused view with bucket summary cards, one-click bucket selection, and bulk actions.
## Selector
```html
<app-bulk-triage-view />
```
## Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `findings` | `ScoredFinding[]` | `[]` | Array of scored findings |
| `selectedIds` | `Set<string>` | `new Set()` | Currently selected finding IDs |
| `processing` | `boolean` | `false` | Whether an action is in progress |
## Outputs
| Output | Type | Description |
|--------|------|-------------|
| `selectionChange` | `EventEmitter<string[]>` | Emits when selection changes |
| `actionRequest` | `EventEmitter<BulkActionRequest>` | Emits when an action is triggered |
| `actionComplete` | `EventEmitter<BulkActionResult>` | Emits when an action completes |
## Action Types
```typescript
type BulkActionType = 'acknowledge' | 'suppress' | 'assign' | 'escalate';
interface BulkActionRequest {
action: BulkActionType;
findingIds: string[];
assignee?: string; // For 'assign' action
reason?: string; // For 'suppress' action
}
interface BulkActionResult {
action: BulkActionType;
findingIds: string[];
success: boolean;
error?: string;
}
```
## UI Sections
### Bucket Summary Cards
Four cards showing findings grouped by priority bucket:
- **Act Now** (red): 90-100 score
- **Schedule Next** (amber): 70-89 score
- **Investigate** (blue): 40-69 score
- **Watchlist** (gray): 0-39 score
Each card displays:
- Bucket name and color
- Finding count
- "Select All" button
- Selection indicator
### Action Bar
Appears when findings are selected:
- Selection count
- Clear selection button
- Action buttons: Acknowledge, Suppress, Assign, Escalate
- Undo button (when history exists)
### Progress Overlay
Shown during bulk operations:
- Action name
- Progress bar
- Percentage complete
- Items processed count
### Modals
- **Assign Modal**: Email input for assignee
- **Suppress Modal**: Text area for suppression reason
## Usage Examples
### Basic Usage
```html
<app-bulk-triage-view
[findings]="scoredFindings"
[selectedIds]="selectedIds"
(selectionChange)="onSelectionChange($event)"
(actionRequest)="onActionRequest($event)"
/>
```
### Full Implementation
```typescript
@Component({
selector: 'app-triage-page',
template: `
<app-bulk-triage-view
[findings]="findings()"
[selectedIds]="selectedIds()"
[processing]="processing()"
(selectionChange)="updateSelection($event)"
(actionRequest)="handleAction($event)"
(actionComplete)="onActionComplete($event)"
/>
`
})
export class TriagePageComponent {
findings = signal<ScoredFinding[]>([]);
selectedIds = signal<Set<string>>(new Set());
processing = signal(false);
private triageService = inject(TriageService);
updateSelection(ids: string[]): void {
this.selectedIds.set(new Set(ids));
}
async handleAction(request: BulkActionRequest): Promise<void> {
this.processing.set(true);
try {
switch (request.action) {
case 'acknowledge':
await this.triageService.acknowledge(request.findingIds);
break;
case 'suppress':
await this.triageService.suppress(request.findingIds, request.reason!);
break;
case 'assign':
await this.triageService.assign(request.findingIds, request.assignee!);
break;
case 'escalate':
await this.triageService.escalate(request.findingIds);
break;
}
this.selectedIds.set(new Set());
await this.refreshFindings();
} finally {
this.processing.set(false);
}
}
}
```
### With Toast Notifications
```html
<app-bulk-triage-view
[findings]="findings"
[selectedIds]="selectedIds"
(actionComplete)="showToast($event)"
/>
```
```typescript
showToast(result: BulkActionResult): void {
if (result.success) {
this.toast.success(
`${result.action} completed for ${result.findingIds.length} findings`
);
} else {
this.toast.error(`Action failed: ${result.error}`);
}
}
```
## Bucket Selection
### Select All in Bucket
Click "Select All" on a bucket card to select all findings in that bucket.
### Toggle Bucket
Clicking "Select All" when all bucket items are selected will deselect them.
### Partial Selection
When some items in a bucket are selected, the button shows a partial indicator.
## Action Descriptions
| Action | Icon | Description |
|--------|------|-------------|
| Acknowledge | Checkmark | Mark findings as reviewed |
| Suppress | Eye-off | Suppress with reason (opens modal) |
| Assign | User | Assign to team member (opens modal) |
| Escalate | Alert | Mark for urgent attention |
## Undo Capability
The component maintains an undo stack for recent actions:
- Up to 5 operations stored
- Undo restores previous selection
- Toast shows "Undo" button after action
## Accessibility
- Bucket summary has `aria-label="Findings by priority"`
- Select All buttons have `aria-pressed` state
- Action bar has `role="toolbar"`
- Progress overlay announces to screen readers
- Modals trap focus and support Escape to close
- Action buttons have descriptive labels
## Keyboard Navigation
| Key | Action |
|-----|--------|
| Tab | Navigate between elements |
| Enter/Space | Activate buttons |
| Escape | Close modals |
## Styling
```css
app-bulk-triage-view {
--bucket-card-padding: 16px;
--bucket-card-radius: 8px;
--action-bar-bg: #f9fafb;
--modal-max-width: 400px;
}
/* Bucket colors */
.bucket-card.act-now { --bucket-color: #DC2626; }
.bucket-card.schedule-next { --bucket-color: #D97706; }
.bucket-card.investigate { --bucket-color: #2563EB; }
.bucket-card.watchlist { --bucket-color: #6B7280; }
```
## Responsive Behavior
| Breakpoint | Layout |
|------------|--------|
| > 640px | 4 bucket cards in row |
| <= 640px | 2x2 grid, action labels hidden |
## Related Components
- [FindingsList](./findings-list.md) - Findings table view
- [ScorePill](./score-pill.md) - Score display
- [ScoreBadge](./score-badge.md) - Evidence flags

View File

@@ -0,0 +1,334 @@
# Design Tokens
CSS custom properties (design tokens) for the Evidence-Weighted Score component suite.
## Score Colors
### Bucket Colors
```css
:root {
/* Act Now (90-100) - Critical priority */
--score-bucket-act-now: #DC2626;
--score-bucket-act-now-light: #FEE2E2;
--score-bucket-act-now-dark: #991B1B;
/* Schedule Next (70-89) - High priority */
--score-bucket-schedule-next: #D97706;
--score-bucket-schedule-next-light: #FEF3C7;
--score-bucket-schedule-next-dark: #92400E;
/* Investigate (40-69) - Medium priority */
--score-bucket-investigate: #2563EB;
--score-bucket-investigate-light: #DBEAFE;
--score-bucket-investigate-dark: #1E40AF;
/* Watchlist (0-39) - Low priority */
--score-bucket-watchlist: #6B7280;
--score-bucket-watchlist-light: #F3F4F6;
--score-bucket-watchlist-dark: #374151;
}
```
### Flag Colors
```css
:root {
/* Live Signal - Active runtime signals */
--score-flag-live-signal: #16A34A;
--score-flag-live-signal-light: #DCFCE7;
--score-flag-live-signal-dark: #166534;
/* Proven Path - Verified reachability */
--score-flag-proven-path: #2563EB;
--score-flag-proven-path-light: #DBEAFE;
--score-flag-proven-path-dark: #1E40AF;
/* Vendor N/A - Vendor not affected */
--score-flag-vendor-na: #6B7280;
--score-flag-vendor-na-light: #F3F4F6;
--score-flag-vendor-na-dark: #374151;
/* Speculative - Unconfirmed evidence */
--score-flag-speculative: #D97706;
--score-flag-speculative-light: #FEF3C7;
--score-flag-speculative-dark: #92400E;
}
```
## Component Tokens
### ScorePill
```css
:root {
--score-pill-font-family: system-ui, -apple-system, sans-serif;
--score-pill-font-weight: 600;
--score-pill-border-radius: 4px;
/* Size: Small */
--score-pill-sm-height: 20px;
--score-pill-sm-min-width: 24px;
--score-pill-sm-padding: 0 4px;
--score-pill-sm-font-size: 12px;
/* Size: Medium */
--score-pill-md-height: 24px;
--score-pill-md-min-width: 32px;
--score-pill-md-padding: 0 6px;
--score-pill-md-font-size: 14px;
/* Size: Large */
--score-pill-lg-height: 28px;
--score-pill-lg-min-width: 40px;
--score-pill-lg-padding: 0 8px;
--score-pill-lg-font-size: 16px;
/* Interactive states */
--score-pill-hover-scale: 1.05;
--score-pill-focus-ring: 2px solid var(--color-focus);
--score-pill-focus-offset: 2px;
}
```
### ScoreBadge
```css
:root {
--score-badge-font-family: system-ui, -apple-system, sans-serif;
--score-badge-font-weight: 500;
--score-badge-border-radius: 4px;
/* Size: Small */
--score-badge-sm-height: 20px;
--score-badge-sm-padding: 2px 6px;
--score-badge-sm-font-size: 11px;
--score-badge-sm-icon-size: 12px;
/* Size: Medium */
--score-badge-md-height: 24px;
--score-badge-md-padding: 4px 8px;
--score-badge-md-font-size: 12px;
--score-badge-md-icon-size: 14px;
/* Icon-only mode */
--score-badge-icon-only-size: 20px;
--score-badge-icon-only-padding: 4px;
}
```
### ScoreBreakdownPopover
```css
:root {
--score-popover-max-width: 360px;
--score-popover-padding: 16px;
--score-popover-border-radius: 8px;
--score-popover-background: #FFFFFF;
--score-popover-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
/* Dimension bars */
--score-dimension-bar-height: 8px;
--score-dimension-bar-radius: 4px;
--score-dimension-bar-bg: #E5E7EB;
/* Header */
--score-popover-header-font-size: 24px;
--score-popover-header-font-weight: 700;
/* Labels */
--score-popover-label-font-size: 12px;
--score-popover-label-color: #6B7280;
/* Explanations */
--score-popover-explanation-font-size: 13px;
--score-popover-explanation-color: #374151;
}
```
### ScoreHistoryChart
```css
:root {
--score-chart-line-color: #3B82F6;
--score-chart-line-width: 2px;
--score-chart-area-fill: rgba(59, 130, 246, 0.1);
--score-chart-area-gradient-start: rgba(59, 130, 246, 0.2);
--score-chart-area-gradient-end: rgba(59, 130, 246, 0);
/* Data points */
--score-chart-point-size: 6px;
--score-chart-point-hover-size: 8px;
--score-chart-point-border-width: 2px;
--score-chart-point-border-color: #FFFFFF;
/* Grid */
--score-chart-grid-color: #E5E7EB;
--score-chart-grid-width: 1px;
/* Bands */
--score-chart-band-opacity: 0.1;
/* Axis */
--score-chart-axis-color: #9CA3AF;
--score-chart-axis-font-size: 11px;
/* Tooltip */
--score-chart-tooltip-bg: #1F2937;
--score-chart-tooltip-color: #FFFFFF;
--score-chart-tooltip-padding: 8px 12px;
--score-chart-tooltip-radius: 6px;
}
```
## Dark Mode
```css
@media (prefers-color-scheme: dark) {
:root {
/* Backgrounds */
--score-popover-background: #1F2937;
--score-chart-tooltip-bg: #374151;
/* Text */
--score-popover-label-color: #D1D5DB;
--score-popover-explanation-color: #F9FAFB;
/* Borders */
--score-dimension-bar-bg: #374151;
--score-chart-grid-color: #374151;
/* Adjust bucket light colors for dark mode */
--score-bucket-act-now-light: rgba(220, 38, 38, 0.2);
--score-bucket-schedule-next-light: rgba(217, 119, 6, 0.2);
--score-bucket-investigate-light: rgba(37, 99, 235, 0.2);
--score-bucket-watchlist-light: rgba(107, 114, 128, 0.2);
}
}
```
## Semantic Tokens
```css
:root {
/* Focus states */
--color-focus: #3B82F6;
--color-focus-ring: rgba(59, 130, 246, 0.3);
/* Interactive */
--color-interactive: #3B82F6;
--color-interactive-hover: #2563EB;
--color-interactive-active: #1D4ED8;
/* Status */
--color-success: #16A34A;
--color-warning: #D97706;
--color-error: #DC2626;
--color-info: #2563EB;
/* Neutral */
--color-text-primary: #111827;
--color-text-secondary: #6B7280;
--color-text-disabled: #9CA3AF;
--color-border: #E5E7EB;
--color-background: #FFFFFF;
--color-surface: #F9FAFB;
}
```
## Usage in Components
### SCSS Import
```scss
// styles/_tokens.scss
@use 'sass:map';
$score-buckets: (
'act-now': (
color: var(--score-bucket-act-now),
light: var(--score-bucket-act-now-light),
dark: var(--score-bucket-act-now-dark),
),
'schedule-next': (
color: var(--score-bucket-schedule-next),
light: var(--score-bucket-schedule-next-light),
dark: var(--score-bucket-schedule-next-dark),
),
'investigate': (
color: var(--score-bucket-investigate),
light: var(--score-bucket-investigate-light),
dark: var(--score-bucket-investigate-dark),
),
'watchlist': (
color: var(--score-bucket-watchlist),
light: var(--score-bucket-watchlist-light),
dark: var(--score-bucket-watchlist-dark),
),
);
@mixin bucket-colors($bucket) {
$colors: map.get($score-buckets, $bucket);
background-color: map.get($colors, color);
color: white;
&.light {
background-color: map.get($colors, light);
color: map.get($colors, dark);
}
}
```
### TypeScript Constants
```typescript
// score-colors.ts
export const SCORE_BUCKET_COLORS = {
ActNow: {
bg: '#DC2626',
light: '#FEE2E2',
dark: '#991B1B',
},
ScheduleNext: {
bg: '#D97706',
light: '#FEF3C7',
dark: '#92400E',
},
Investigate: {
bg: '#2563EB',
light: '#DBEAFE',
dark: '#1E40AF',
},
Watchlist: {
bg: '#6B7280',
light: '#F3F4F6',
dark: '#374151',
},
} as const;
export const SCORE_FLAG_COLORS = {
'live-signal': { bg: '#16A34A', light: '#DCFCE7' },
'proven-path': { bg: '#2563EB', light: '#DBEAFE' },
'vendor-na': { bg: '#6B7280', light: '#F3F4F6' },
'speculative': { bg: '#D97706', light: '#FEF3C7' },
} as const;
export function getBucketColor(score: number): string {
if (score >= 90) return SCORE_BUCKET_COLORS.ActNow.bg;
if (score >= 70) return SCORE_BUCKET_COLORS.ScheduleNext.bg;
if (score >= 40) return SCORE_BUCKET_COLORS.Investigate.bg;
return SCORE_BUCKET_COLORS.Watchlist.bg;
}
```
## Accessibility Notes
- All color combinations meet WCAG 2.1 AA contrast requirements (4.5:1 for text)
- Bucket colors use both color and position/labels for identification
- Flag badges use icons in addition to colors
- Focus states use high-contrast ring colors
## Related Files
- `src/Web/StellaOps.Web/src/styles/_tokens.scss` - SCSS token definitions
- `src/Web/StellaOps.Web/src/app/core/constants/score-colors.ts` - TypeScript constants

View File

@@ -0,0 +1,260 @@
# 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
```html
<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
```typescript
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
```typescript
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
```html
<app-findings-list
[findings]="findings"
(findingSelect)="openFinding($event)"
/>
```
### Without Auto-Loading Scores
```html
<app-findings-list
[findings]="findings"
[autoLoadScores]="false"
/>
```
### With Selection Handling
```html
<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>
```
```typescript
selectedIds: string[] = [];
onSelectionChange(ids: string[]): void {
this.selectedIds = ids;
}
```
### Full Feature Example
```typescript
@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:
```typescript
import { SCORING_API, ScoringApiService } from '@app/core/services/scoring.service';
@NgModule({
providers: [
{ provide: SCORING_API, useClass: ScoringApiService }
]
})
export class AppModule {}
```
### Mock API for Testing
```typescript
import { MockScoringApi } from '@app/core/services/scoring.service';
TestBed.configureTestingModule({
providers: [
{ provide: SCORING_API, useClass: MockScoringApi }
]
});
```
## Empty States
### No Findings
```html
<!-- Displays: "No findings to display" -->
<app-findings-list [findings]="[]" />
```
### No Matches
When filters result in no matches:
```html
<!-- 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
```css
app-findings-list {
--table-header-bg: #f9fafb;
--table-border-color: #e5e7eb;
--table-row-hover: #f3f4f6;
--table-row-selected: #eff6ff;
}
```
## Related Components
- [ScorePill](./score-pill.md) - Score display in table
- [ScoreBadge](./score-badge.md) - Flag badges in table
- [ScoreBreakdownPopover](./score-breakdown-popover.md) - Score details on click
- [BulkTriageView](./bulk-triage-view.md) - Bulk operations

View File

@@ -0,0 +1,166 @@
# ScoreBadgeComponent
Score badge component displaying evidence flags with icons and labels.
## Overview
The `ScoreBadgeComponent` displays evidence quality flags that provide context about score reliability and data sources.
## Selector
```html
<stella-score-badge />
```
## Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `type` | `ScoreFlag` | required | The flag type to display |
| `size` | `'sm' \| 'md'` | `'md'` | Size variant |
| `showTooltip` | `boolean` | `true` | Show description on hover |
| `showLabel` | `boolean` | `true` | Show label text (false = icon only) |
## Flag Types
| Type | Icon | Color | Description |
|------|------|-------|-------------|
| `live-signal` | Signal wave | Green (`#16A34A`) | Active runtime signals detected from deployed environments |
| `proven-path` | Checkmark | Blue (`#2563EB`) | Verified reachability path to vulnerable code |
| `vendor-na` | Strikethrough | Gray (`#6B7280`) | Vendor has marked as not affected |
| `speculative` | Question mark | Orange (`#D97706`) | Evidence is speculative or unconfirmed |
## Usage Examples
### Basic Usage
```html
<stella-score-badge type="live-signal" />
```
### All Flag Types
```html
<stella-score-badge type="live-signal" />
<stella-score-badge type="proven-path" />
<stella-score-badge type="vendor-na" />
<stella-score-badge type="speculative" />
```
### Size Variants
```html
<stella-score-badge type="proven-path" size="sm" />
<stella-score-badge type="proven-path" size="md" />
```
### Icon-Only Mode
```html
<stella-score-badge type="live-signal" [showLabel]="false" />
```
### With Score Pill
```html
<div class="score-display">
<stella-score-pill [score]="92" />
<div class="flags">
<stella-score-badge type="live-signal" size="sm" />
<stella-score-badge type="proven-path" size="sm" />
</div>
</div>
```
### Rendering from Flags Array
```html
@for (flag of scoreResult.flags; track flag) {
<stella-score-badge [type]="flag" size="sm" />
}
```
### In a Table
```html
<td class="flags-column">
@for (flag of finding.score?.flags; track flag) {
<stella-score-badge
[type]="flag"
size="sm"
[showLabel]="false"
/>
}
</td>
```
## Flag Significance
### live-signal (Green - High Confidence)
Indicates the vulnerability affects code that is actively being executed in production. This is the highest confidence indicator and typically elevates priority.
**Sources:**
- Runtime telemetry from deployed containers
- Function call traces
- Code coverage data from production
### proven-path (Blue - Confirmed)
A verified call path from application entry points to the vulnerable function has been confirmed through static or dynamic analysis.
**Sources:**
- Static reachability analysis
- Dynamic call graph analysis
- Fuzzing results
### vendor-na (Gray - Vendor Override)
The software vendor has issued a VEX statement indicating this vulnerability does not affect their product configuration or version.
**Sources:**
- Vendor VEX documents
- CSAF advisories
- Distro security teams
### speculative (Orange - Unconfirmed)
The evidence for this vulnerability is speculative or based on incomplete analysis. Score caps are typically applied.
**Sources:**
- Incomplete static analysis
- Heuristic-based detection
- Unverified reports
## Accessibility
- Uses `role="img"` with descriptive `aria-label`
- Tooltip shown on focus for keyboard users
- Icon colors meet WCAG AA contrast requirements
- Screen reader announces full flag description
## Styling
```css
stella-score-badge {
--badge-font-size: 12px;
--badge-padding: 4px 8px;
--badge-border-radius: 4px;
}
```
### Live Signal Animation
The live-signal badge features a subtle pulse animation to draw attention:
```css
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.live-signal-badge {
animation: pulse 2s ease-in-out infinite;
}
```
## Related Components
- [ScorePill](./score-pill.md) - Compact score display
- [ScoreBreakdownPopover](./score-breakdown-popover.md) - Full breakdown with flags section

View File

@@ -0,0 +1,172 @@
# ScoreBreakdownPopoverComponent
Detailed score breakdown popover showing all evidence dimensions, flags, and explanations.
## Overview
The `ScoreBreakdownPopoverComponent` displays a comprehensive breakdown of an evidence-weighted score, including dimension bars, active flags, guardrails, and human-readable explanations.
## Selector
```html
<stella-score-breakdown-popover />
```
## Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `scoreResult` | `EvidenceWeightedScoreResult` | required | Full score result object |
| `position` | `PopoverPosition` | `'bottom'` | Popover placement |
## Position Options
```typescript
type PopoverPosition = 'top' | 'bottom' | 'left' | 'right';
```
## Score Result Structure
```typescript
interface EvidenceWeightedScoreResult {
findingId: string;
score: number; // 0-100
bucket: ScoreBucket; // 'ActNow' | 'ScheduleNext' | 'Investigate' | 'Watchlist'
inputs: {
rch: number; // Reachability (0-1)
rts: number; // Runtime signals (0-1)
bkp: number; // Backport availability (0-1)
xpl: number; // Exploitability (0-1)
src: number; // Source trust (0-1)
mit: number; // Mitigations (0-1)
};
weights: {
rch: number;
rts: number;
bkp: number;
xpl: number;
src: number;
mit: number;
};
flags: ScoreFlag[];
explanations: string[];
caps: {
speculativeCap: boolean;
notAffectedCap: boolean;
runtimeFloor: boolean;
};
policyDigest: string;
calculatedAt: string; // ISO 8601
}
```
## Dimension Labels
| Key | Label | Description |
|-----|-------|-------------|
| `rch` | Reachability | Path to vulnerable code exists |
| `rts` | Runtime Signals | Active usage detected in production |
| `bkp` | Backport | Fix backported to current version |
| `xpl` | Exploitability | EPSS probability, known exploits |
| `src` | Source Trust | Advisory source reliability |
| `mit` | Mitigations | Active mitigations reduce risk |
## Usage Examples
### Basic Usage
```html
<stella-score-breakdown-popover [scoreResult]="scoreResult" />
```
### With Position
```html
<stella-score-breakdown-popover
[scoreResult]="scoreResult"
position="right"
/>
```
### Triggered from Score Pill
```html
<div class="score-container">
<stella-score-pill
[score]="score"
(scoreClick)="showPopover = true"
/>
@if (showPopover) {
<stella-score-breakdown-popover
[scoreResult]="scoreResult"
(close)="showPopover = false"
/>
}
</div>
```
### In a Dialog
```typescript
@Component({
template: `
<div class="dialog-content">
<h2>Score Details</h2>
<stella-score-breakdown-popover
[scoreResult]="scoreResult"
/>
</div>
`
})
export class ScoreDialogComponent {
scoreResult = input.required<EvidenceWeightedScoreResult>();
}
```
## Popover Sections
### 1. Header
Displays the overall score with bucket label and color.
### 2. Dimensions Chart
Horizontal bar chart showing all six dimensions with their normalized values (0-100%).
### 3. Flags Section
Active flags displayed as badges. See [ScoreBadge](./score-badge.md) for flag types.
### 4. Guardrails Section
Applied caps and floors:
- **Speculative Cap**: Score limited due to unconfirmed evidence
- **Not Affected Cap**: Score reduced due to vendor VEX
- **Runtime Floor**: Score elevated due to active runtime signals
### 5. Explanations
Human-readable explanations of factors affecting the score.
### 6. Footer
- Policy digest (truncated SHA-256)
- Calculation timestamp
## Accessibility
- Uses `role="dialog"` with `aria-labelledby`
- Focus trapped within popover when open
- Escape key closes popover
- Click outside closes popover
- Screen reader announces dimension values
## Styling
```css
stella-score-breakdown-popover {
--popover-max-width: 360px;
--popover-background: white;
--popover-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
```
## Related Components
- [ScorePill](./score-pill.md) - Compact score display
- [ScoreBadge](./score-badge.md) - Evidence flag badges

View File

@@ -0,0 +1,217 @@
# ScoreHistoryChartComponent
Timeline visualization showing how a finding's evidence-weighted score has changed over time.
## Overview
The `ScoreHistoryChartComponent` renders an SVG line chart displaying score history with interactive data points and bucket-colored bands.
## Selector
```html
<stella-score-history-chart />
```
## Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `history` | `ScoreHistoryEntry[]` | `[]` | Array of historical score entries |
| `width` | `number \| 'auto'` | `'auto'` | Chart width in pixels |
| `height` | `number` | `200` | Chart height in pixels |
| `showBands` | `boolean` | `true` | Show bucket background bands |
| `showGrid` | `boolean` | `true` | Show horizontal grid lines |
| `showRangeSelector` | `boolean` | `false` | Show date range filter controls |
## Outputs
| Output | Type | Description |
|--------|------|-------------|
| `pointHover` | `EventEmitter<ScoreHistoryEntry>` | Emits when hovering over a data point |
| `pointClick` | `EventEmitter<ScoreHistoryEntry>` | Emits when clicking a data point |
## History Entry Structure
```typescript
interface ScoreHistoryEntry {
score: number; // 0-100
bucket: ScoreBucket;
policyDigest: string; // SHA-256 of active policy
calculatedAt: string; // ISO 8601 timestamp
trigger: ScoreChangeTrigger;
changedFactors: string[]; // ['rch', 'rts', ...]
}
type ScoreChangeTrigger =
| 'evidence_update' // New evidence received
| 'policy_change' // Scoring policy modified
| 'scheduled'; // Periodic recalculation
```
## Chart Features
### Bucket Bands
Colored background regions showing score thresholds:
- Act Now (90-100): Light red
- Schedule Next (70-89): Light amber
- Investigate (40-69): Light blue
- Watchlist (0-39): Light gray
### Data Points
Interactive markers with trigger-type indicators:
- Circle: `evidence_update`
- Diamond: `policy_change`
- Square: `scheduled`
### Tooltips
Hover over any point to see:
- Score and bucket
- Timestamp
- Trigger type
- Changed factors (if any)
### Date Range Selector
When enabled, provides filtering options:
- Preset ranges: 7d, 30d, 90d, 1y, All
- Custom date range picker
## Usage Examples
### Basic Usage
```html
<stella-score-history-chart [history]="scoreHistory" />
```
### Custom Dimensions
```html
<stella-score-history-chart
[history]="scoreHistory"
[width]="600"
[height]="250"
/>
```
### Minimal Chart
```html
<stella-score-history-chart
[history]="scoreHistory"
[showBands]="false"
[showGrid]="false"
[height]="150"
/>
```
### With Date Range Selector
```html
<stella-score-history-chart
[history]="extendedHistory"
[showRangeSelector]="true"
/>
```
### Responsive Width
```html
<div class="chart-container">
<stella-score-history-chart
[history]="scoreHistory"
width="auto"
/>
</div>
```
```css
.chart-container {
width: 100%;
min-width: 300px;
}
```
### With Event Handlers
```html
<stella-score-history-chart
[history]="scoreHistory"
(pointClick)="showEntryDetails($event)"
(pointHover)="updateTooltip($event)"
/>
```
```typescript
showEntryDetails(entry: ScoreHistoryEntry): void {
console.log('Clicked entry:', entry);
this.selectedEntry = entry;
}
updateTooltip(entry: ScoreHistoryEntry | null): void {
this.hoveredEntry = entry;
}
```
## Date Range Presets
| Preset | Label | Filter |
|--------|-------|--------|
| `7d` | Last 7 days | Entries from past week |
| `30d` | Last 30 days | Entries from past month |
| `90d` | Last 90 days | Entries from past quarter |
| `1y` | Last year | Entries from past 12 months |
| `all` | All time | No filtering |
| `custom` | Custom range | User-selected dates |
## Visualization Details
### Line Chart
- Smooth curve interpolation
- Area fill under the line with gradient opacity
- Score range always 0-100 on Y-axis
### Grid Lines
- Horizontal lines at bucket boundaries (40, 70, 90)
- Vertical lines at regular time intervals
### Time Axis
- Auto-formats based on date range
- Labels: Jan 15, Feb 1, etc.
## Loading State
When `history` is empty or loading:
```html
<stella-score-history-chart [history]="[]" />
<!-- Displays: "No history available" -->
```
## Accessibility
- SVG includes `role="img"` with descriptive `aria-label`
- Data points are keyboard focusable
- Tooltip content read by screen readers
- Color not the only indicator (shape markers)
## Styling
```css
stella-score-history-chart {
--chart-line-color: #3b82f6;
--chart-area-fill: rgba(59, 130, 246, 0.1);
--chart-point-size: 6px;
--chart-font-family: system-ui, sans-serif;
}
```
## Performance
- Uses requestAnimationFrame for smooth animations
- Virtualizes data points when > 100 entries
- Debounces resize observer
## Related Components
- [ScorePill](./score-pill.md) - Current score display
- [ScoreBreakdownPopover](./score-breakdown-popover.md) - Current score breakdown

View File

@@ -0,0 +1,116 @@
# ScorePillComponent
Compact score display component with bucket-based color coding.
## Overview
The `ScorePillComponent` displays a 0-100 evidence-weighted score with automatic color coding based on priority bucket thresholds.
## Selector
```html
<stella-score-pill />
```
## Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `score` | `number` | `0` | Evidence-weighted score (0-100) |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size variant |
| `showTooltip` | `boolean` | `true` | Show bucket tooltip on hover |
| `interactive` | `boolean` | `true` | Enable hover/click interactions |
## Outputs
| Output | Type | Description |
|--------|------|-------------|
| `scoreClick` | `EventEmitter<number>` | Emits when the pill is clicked |
## Size Variants
| Size | Dimensions | Font Size | Use Case |
|------|------------|-----------|----------|
| `sm` | 24x20px | 12px | Tables, compact lists |
| `md` | 32x24px | 14px | Standard UI elements |
| `lg` | 40x28px | 16px | Dashboard emphasis |
## Color Mapping
The pill automatically applies colors based on score:
```typescript
// Score -> Bucket -> Color
score >= 90 // ActNow -> #DC2626 (red)
score >= 70 // ScheduleNext -> #D97706 (amber)
score >= 40 // Investigate -> #2563EB (blue)
score < 40 // Watchlist -> #6B7280 (gray)
```
## Usage Examples
### Basic Usage
```html
<stella-score-pill [score]="78" />
```
### In a Table
```html
<td>
<stella-score-pill [score]="finding.score" size="sm" />
</td>
```
### All Sizes
```html
<stella-score-pill [score]="78" size="sm" />
<stella-score-pill [score]="78" size="md" />
<stella-score-pill [score]="78" size="lg" />
```
### Non-Interactive
```html
<stella-score-pill [score]="78" [interactive]="false" />
```
### With Click Handler
```html
<stella-score-pill
[score]="78"
(scoreClick)="openScoreDetails($event)"
/>
```
```typescript
openScoreDetails(score: number): void {
// Handle click - typically opens breakdown popover
}
```
## Accessibility
- Uses `role="status"` for screen reader announcements
- `aria-label` includes bucket name: "Score 78: Schedule Next"
- Focusable when interactive
- Supports keyboard activation (Enter/Space)
## Styling
The component uses Shadow DOM encapsulation. Override styles using CSS custom properties:
```css
stella-score-pill {
--score-pill-border-radius: 4px;
--score-pill-font-weight: 600;
}
```
## Related Components
- [ScoreBreakdownPopover](./score-breakdown-popover.md) - Detailed breakdown on click
- [ScoreBadge](./score-badge.md) - Evidence flag badges