Skip to content

Commit 5df8881

Browse files
authored
Link Security warnings to dashboard outcome views (#384)
1 parent 892a3cb commit 5df8881

3 files changed

Lines changed: 31 additions & 16 deletions

File tree

pages/dashboard/src/overview-data.js

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function deriveOverviewSources(sources) {
2424
const disabledWorkflows = workflows.filter((row) => String(row['workflow-active']) === 'false').length;
2525
const overviewMetadata = createOverviewMetadata(sources);
2626
const packageUsage = summarizePackageAicUsage(workflows, usage);
27-
const securitySignals = buildSecuritySignals({ workflows, runs, findings });
27+
const securitySignals = buildSecuritySignals({ workflows, runs, findings, outcomes });
2828
const valueSignals = buildValueSignals({ sources, graderObservations, operationalValues, outcomes });
2929
const costSignals = buildCostSignals(sources.usage);
3030

@@ -612,10 +612,11 @@ function buildSecuritySummary(input) {
612612
}
613613

614614
/**
615-
* @param {{ workflows: Array<Record<string, unknown>>, runs: Array<Record<string, unknown>>, findings: Array<Record<string, unknown>> }} input
615+
* @param {{ workflows: Array<Record<string, unknown>>, runs: Array<Record<string, unknown>>, findings: Array<Record<string, unknown>>, outcomes: Array<Record<string, unknown>> }} input
616616
*/
617617
function buildSecuritySignals(input) {
618618
const workflowNames = new Map(input.workflows.map((row) => [String(row.workflow ?? ''), String(row['workflow-name'] ?? row.workflow ?? 'Unknown workflow')]));
619+
const outcomeIds = new Set(input.outcomes.map((row) => String(row['safe-output'] ?? '')).filter(Boolean));
619620
const signals = [
620621
...groupRows(input.runs.filter((row) => String(row['run-conclusion']) === 'action-required'), (row) => String(row.workflow ?? ''))
621622
.map(([workflow, rows]) => ({
@@ -644,18 +645,24 @@ function buildSecuritySignals(input) {
644645
'navigation-page': 'packages'
645646
})),
646647
...groupRows(input.findings.filter(isAuthoredWarning), findingWorkflowKey)
647-
.map(([workflow, rows]) => ({
648-
priority: 3,
649-
count: rows.length,
650-
tone: 'warning',
651-
icon: 'issue',
652-
kind: 'Authored warning',
653-
title: workflowNames.get(workflow) ?? String(rows[0]?.['finding-summary'] ?? workflow),
654-
detail: `${formatNumber(rows.length)} retained output${rows.length === 1 ? ' contains' : 's contain'} an explicit warning block`,
655-
evidence: 'Output content',
656-
action: 'View evidence',
657-
'external-link': latestRow(rows)?.['external-link']
658-
}))
648+
.map(([workflow, rows]) => {
649+
const latest = latestRow(rows);
650+
const outcomeId = String(latest?.finding ?? '');
651+
return {
652+
priority: 3,
653+
count: rows.length,
654+
tone: 'warning',
655+
icon: 'issue',
656+
kind: 'Authored warning',
657+
title: workflowNames.get(workflow) ?? String(rows[0]?.['finding-summary'] ?? workflow),
658+
detail: `${formatNumber(rows.length)} retained output${rows.length === 1 ? ' contains' : 's contain'} an explicit warning block`,
659+
evidence: 'Output content',
660+
action: 'View evidence',
661+
...(outcomeIds.has(outcomeId)
662+
? { 'navigation-href': `#page-outcome-detail?outcome=${encodeURIComponent(outcomeId)}` }
663+
: { 'external-link': latest?.['external-link'] })
664+
};
665+
})
659666
];
660667
return signals.sort((left, right) => left.priority - right.priority || right.count - left.count || left.title.localeCompare(right.title));
661668
}

pages/dashboard/src/specification.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ export const SOURCE_FIELDS = {
287287
'operational-values': ['organization', 'repository', 'workflow', 'run', 'experiment', 'operational-case', 'evaluator-digest', 'rollout-mode', 'operational-value', 'operational-value-definition', 'requested-evidence-at', 'evidence-cutoff', 'maturity-at', 'maturity-status', 'baseline-value', 'delta-from-baseline', 'observed-at', 'evidence-link', 'organization-link', 'repository-link', 'workflow-link', 'run-link'],
288288
'overview-attention-domains': ['domain', 'state', 'tone', 'icon', 'value', 'detail', 'href', 'priority', 'order'],
289289
'security-summary': ['label', 'value'],
290-
'security-signals': ['priority', 'count', 'tone', 'icon', 'kind', 'title', 'detail', 'evidence', 'action', 'navigation-page', 'run-link', 'external-link'],
290+
'security-signals': ['priority', 'count', 'tone', 'icon', 'kind', 'title', 'detail', 'evidence', 'action', 'navigation-page', 'navigation-href', 'run-link', 'external-link'],
291291
'value-summary': ['label', 'value'],
292292
'value-signals': ['priority', 'count', 'tone', 'icon', 'kind', 'title', 'detail', 'evidence', 'action', 'navigation-page', 'run-link', 'external-link'],
293293
'value-workflows': ['organization', 'repository', 'workflow', 'run', 'operational-value-definition', 'opportunities', 'mature-observations', 'mean-operational-value', 'mean-baseline', 'observed-at', 'evidence-link', 'run-link', 'organization-link', 'repository-link', 'workflow-link'],

pages/dashboard/test/unit/presenter.test.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,14 @@ describe('presenter built-in and custom pages', () => {
406406
'external-link': { relation: 'external', href: 'https://github.com/githubnext/central-agentic-ops/issues/1', label: 'View warning output' }
407407
}],
408408
metadata
409+
},
410+
outcomes: {
411+
source: 'outcomes',
412+
rows: [{
413+
'safe-output': 'warning-1',
414+
'outcome-title': 'Release warning'
415+
}],
416+
metadata
409417
}
410418
}
411419
});
@@ -429,7 +437,7 @@ describe('presenter built-in and custom pages', () => {
429437
expect(signals[0]?.querySelector('a')?.getAttribute('href')).toContain('/actions/runs/102');
430438
expect(signals[1]?.querySelector('a')?.getAttribute('href')).toBe('#page-packages');
431439
expect(signals[1]?.textContent).toContain('View package');
432-
expect(signals[2]?.querySelector('a')?.getAttribute('href')).toContain('/issues/1');
440+
expect(signals[2]?.querySelector('a')?.getAttribute('href')).toBe('#page-outcome-detail?outcome=warning-1');
433441
expect(page?.textContent).toContain('No vulnerability feed is retained.');
434442
});
435443

0 commit comments

Comments
 (0)