fix: preserve contracts after Michael rebase
This commit is contained in:
parent
6600198312
commit
dbe244b32f
|
|
@ -1,4 +1,4 @@
|
|||
import { memo, useEffect, useRef } from 'react';
|
||||
import { memo } from 'react';
|
||||
import type { FindingRecord } from '../../domain/finding';
|
||||
import { normalizeSeverity } from '../../profiles/purple-knight/presentation';
|
||||
import { toolIdForRecord } from '../../importers/status-normalization';
|
||||
|
|
@ -27,15 +27,8 @@ function FindingCardComponent({ record, title, bookmarked, selected, toolId, sea
|
|||
const outcomeMark = findingOutcomeMark(record, toolId);
|
||||
const resolvedToolId = toolIdForRecord(record, toolId);
|
||||
const diagnostics = policyDiagnosticBadges(record, resolvedToolId);
|
||||
const cardRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selected) return;
|
||||
cardRef.current?.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' });
|
||||
}, [selected]);
|
||||
return (
|
||||
<button
|
||||
ref={cardRef}
|
||||
aria-current={selected ? 'true' : undefined}
|
||||
className={`record-card severity-${severity}${bookmarked ? ' is-workspace' : ''}${selected ? ' active' : ''}`}
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -133,12 +133,8 @@ export function DatasetViewer() {
|
|||
left: Math.max(0, Math.min(requestedClusterPos.left, maxClusterLeft)),
|
||||
top: Math.max(minClusterTop, Math.min(requestedClusterPos.top, maxClusterTop))
|
||||
};
|
||||
const recordTitles = useMemo(() => {
|
||||
if (!activeSheet) return new Map<string, string>();
|
||||
return new Map<string, string>(activeSheet.analysis.records.map(record => [
|
||||
record.id,
|
||||
recordTitleFor(record, titleColumn, activeSheet.toolId ?? dataset?.profileId)
|
||||
]));
|
||||
const recordTitleCache = useMemo(() => {
|
||||
return new WeakMap<FindingRecord, string>();
|
||||
}, [activeSheet, dataset?.profileId, titleColumn]);
|
||||
const resultColorsByRecord = useMemo(() => new Map(resultWindows.map(item => [item.recordId, item.color])), [resultWindows]);
|
||||
|
||||
|
|
@ -236,7 +232,13 @@ export function DatasetViewer() {
|
|||
|
||||
if (!dataset || !activeSheet) return <EmptyWorkspace />;
|
||||
|
||||
const recordTitle = (record: FindingRecord) => recordTitles.get(record.id) ?? record.title;
|
||||
const recordTitle = (record: FindingRecord) => {
|
||||
const cached = recordTitleCache.get(record);
|
||||
if (cached !== undefined) return cached;
|
||||
const title = recordTitleFor(record, titleColumn, activeSheet.toolId ?? dataset.profileId);
|
||||
recordTitleCache.set(record, title);
|
||||
return title;
|
||||
};
|
||||
const selected = records.find(record => record.id === selectedRecordId) ?? records[0];
|
||||
const toolId = activeSheet.toolId ?? dataset.profileId;
|
||||
const linksFor = (record: FindingRecord) => recordResultSheets(record, dataset.sheets, activeSheet.id, toolId);
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ export function ResultRow({ item, expanded, onToggle, onUpdated }: {
|
|||
item: UnifiedResultProjection;
|
||||
expanded: boolean;
|
||||
onToggle(): void;
|
||||
onUpdated(): Promise<void>;
|
||||
onUpdated?(): Promise<void>;
|
||||
}) {
|
||||
const environment = item.sourceKind === 'environment-observation';
|
||||
const policyObservation = item.sourceKind === 'policy-observation';
|
||||
|
|
@ -213,7 +213,7 @@ export function ResultRow({ item, expanded, onToggle, onUpdated }: {
|
|||
setEnrichmentError(null);
|
||||
try {
|
||||
await assessorApi.enrichCve(cveId);
|
||||
await onUpdated();
|
||||
await onUpdated?.();
|
||||
} catch (reason) {
|
||||
setEnrichmentError(reason instanceof Error ? reason.message : 'CVE enrichment failed.');
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,22 @@ import { commonSeverityMappings, commonStatusMappings } from '../common-normaliz
|
|||
export const defenderForIotDefinition: ToolProfileDefinition = {
|
||||
schemaVersion: 1,
|
||||
id: 'defender-for-iot-vulnerabilities',
|
||||
securityScoring: {
|
||||
version: '1.0.0',
|
||||
native: { kind: 'unavailable', reason: 'Affected-device counts describe vulnerability reach, not a native security score.' },
|
||||
targets: [], resultPopulation: 'findings-only'
|
||||
},
|
||||
priorityBasis: {
|
||||
version: '1.0.0',
|
||||
basis: { kind: 'severity', properties: ['vulnerability.severity'] },
|
||||
excludedProperties: [
|
||||
{ property: 'affected-device-count', reason: 'Vulnerability reach is not a criticality.' }
|
||||
]
|
||||
},
|
||||
timeslotComparison: {
|
||||
version: '1.0.0', kind: 'unavailable',
|
||||
reason: 'The aggregated CVE export does not establish complete negative evidence for vulnerability remediation.'
|
||||
},
|
||||
name: 'Microsoft Defender for IoT',
|
||||
version: '1.2.0',
|
||||
processing: {
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ export function createImportProfile(definition: ToolProfileDefinition): ImportPr
|
|||
}
|
||||
if (status === 'no-baseline-rule') return actualStatus === 'no baseline rule';
|
||||
if (status === 'not-comparable') return actualStatus === 'not comparable';
|
||||
if (status === 'deviation') return isDeviationRecord(record, definition.id);
|
||||
const filter = definition.presentation.statusFilters.find(item => item.value === status);
|
||||
const statusHeaders = definition.presentation.statusHeaders;
|
||||
const preferredStatus = statusHeaders?.map(pattern => record.fields.find(field => field.value.trim() && matches(pattern, field.label.trim())))
|
||||
|
|
@ -114,9 +115,6 @@ export function createImportProfile(definition: ToolProfileDefinition): ImportPr
|
|||
const values = statusHeaders?.length
|
||||
? preferredStatus ? [plainTextValue(preferredStatus.value).trim().toLowerCase()] : []
|
||||
: normalizedFieldValues(record);
|
||||
if (status === 'deviation' && !filter) {
|
||||
return values.some(value => UNIVERSAL_DEVIATION_PATTERNS.some(pattern => matches(pattern, value)));
|
||||
}
|
||||
if (!filter) return true;
|
||||
return values.some(value => filter.patterns.some(pattern => matches(pattern, value)));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ describe('Datahub client processing readiness', () => {
|
|||
// A display tool label cannot turn a generic processor into a native one.
|
||||
expect(summarizeScanProcessingReadiness([{ ...companion, detectedTool: { id: 'purple-knight', name: 'PurpleKnight' } }]).state).toBe('review');
|
||||
expect(summarizeScanProcessingReadiness([currentPurpleKnight('run'), companion,
|
||||
file('run', 'pingcastle', '1.5.0', 'pingcastle-canonical-v1.1')]).state).toBe('review');
|
||||
file('run', 'pingcastle', '1.6.0', 'pingcastle-canonical-v1.1')]).state).toBe('review');
|
||||
});
|
||||
|
||||
it('marks GPO failure and recovery red, including clients with healthy scanner reports', () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue