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,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