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 type { FindingRecord } from '../../domain/finding';
|
||||||
import { normalizeSeverity } from '../../profiles/purple-knight/presentation';
|
import { normalizeSeverity } from '../../profiles/purple-knight/presentation';
|
||||||
import { toolIdForRecord } from '../../importers/status-normalization';
|
import { toolIdForRecord } from '../../importers/status-normalization';
|
||||||
|
|
@ -27,15 +27,8 @@ function FindingCardComponent({ record, title, bookmarked, selected, toolId, sea
|
||||||
const outcomeMark = findingOutcomeMark(record, toolId);
|
const outcomeMark = findingOutcomeMark(record, toolId);
|
||||||
const resolvedToolId = toolIdForRecord(record, toolId);
|
const resolvedToolId = toolIdForRecord(record, toolId);
|
||||||
const diagnostics = policyDiagnosticBadges(record, resolvedToolId);
|
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 (
|
return (
|
||||||
<button
|
<button
|
||||||
ref={cardRef}
|
|
||||||
aria-current={selected ? 'true' : undefined}
|
aria-current={selected ? 'true' : undefined}
|
||||||
className={`record-card severity-${severity}${bookmarked ? ' is-workspace' : ''}${selected ? ' active' : ''}`}
|
className={`record-card severity-${severity}${bookmarked ? ' is-workspace' : ''}${selected ? ' active' : ''}`}
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -133,12 +133,8 @@ export function DatasetViewer() {
|
||||||
left: Math.max(0, Math.min(requestedClusterPos.left, maxClusterLeft)),
|
left: Math.max(0, Math.min(requestedClusterPos.left, maxClusterLeft)),
|
||||||
top: Math.max(minClusterTop, Math.min(requestedClusterPos.top, maxClusterTop))
|
top: Math.max(minClusterTop, Math.min(requestedClusterPos.top, maxClusterTop))
|
||||||
};
|
};
|
||||||
const recordTitles = useMemo(() => {
|
const recordTitleCache = useMemo(() => {
|
||||||
if (!activeSheet) return new Map<string, string>();
|
return new WeakMap<FindingRecord, string>();
|
||||||
return new Map<string, string>(activeSheet.analysis.records.map(record => [
|
|
||||||
record.id,
|
|
||||||
recordTitleFor(record, titleColumn, activeSheet.toolId ?? dataset?.profileId)
|
|
||||||
]));
|
|
||||||
}, [activeSheet, dataset?.profileId, titleColumn]);
|
}, [activeSheet, dataset?.profileId, titleColumn]);
|
||||||
const resultColorsByRecord = useMemo(() => new Map(resultWindows.map(item => [item.recordId, item.color])), [resultWindows]);
|
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 />;
|
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 selected = records.find(record => record.id === selectedRecordId) ?? records[0];
|
||||||
const toolId = activeSheet.toolId ?? dataset.profileId;
|
const toolId = activeSheet.toolId ?? dataset.profileId;
|
||||||
const linksFor = (record: FindingRecord) => recordResultSheets(record, dataset.sheets, activeSheet.id, toolId);
|
const linksFor = (record: FindingRecord) => recordResultSheets(record, dataset.sheets, activeSheet.id, toolId);
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ export function ResultRow({ item, expanded, onToggle, onUpdated }: {
|
||||||
item: UnifiedResultProjection;
|
item: UnifiedResultProjection;
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
onToggle(): void;
|
onToggle(): void;
|
||||||
onUpdated(): Promise<void>;
|
onUpdated?(): Promise<void>;
|
||||||
}) {
|
}) {
|
||||||
const environment = item.sourceKind === 'environment-observation';
|
const environment = item.sourceKind === 'environment-observation';
|
||||||
const policyObservation = item.sourceKind === 'policy-observation';
|
const policyObservation = item.sourceKind === 'policy-observation';
|
||||||
|
|
@ -213,7 +213,7 @@ export function ResultRow({ item, expanded, onToggle, onUpdated }: {
|
||||||
setEnrichmentError(null);
|
setEnrichmentError(null);
|
||||||
try {
|
try {
|
||||||
await assessorApi.enrichCve(cveId);
|
await assessorApi.enrichCve(cveId);
|
||||||
await onUpdated();
|
await onUpdated?.();
|
||||||
} catch (reason) {
|
} catch (reason) {
|
||||||
setEnrichmentError(reason instanceof Error ? reason.message : 'CVE enrichment failed.');
|
setEnrichmentError(reason instanceof Error ? reason.message : 'CVE enrichment failed.');
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,22 @@ import { commonSeverityMappings, commonStatusMappings } from '../common-normaliz
|
||||||
export const defenderForIotDefinition: ToolProfileDefinition = {
|
export const defenderForIotDefinition: ToolProfileDefinition = {
|
||||||
schemaVersion: 1,
|
schemaVersion: 1,
|
||||||
id: 'defender-for-iot-vulnerabilities',
|
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',
|
name: 'Microsoft Defender for IoT',
|
||||||
version: '1.2.0',
|
version: '1.2.0',
|
||||||
processing: {
|
processing: {
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ export function createImportProfile(definition: ToolProfileDefinition): ImportPr
|
||||||
}
|
}
|
||||||
if (status === 'no-baseline-rule') return actualStatus === 'no baseline rule';
|
if (status === 'no-baseline-rule') return actualStatus === 'no baseline rule';
|
||||||
if (status === 'not-comparable') return actualStatus === 'not comparable';
|
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 filter = definition.presentation.statusFilters.find(item => item.value === status);
|
||||||
const statusHeaders = definition.presentation.statusHeaders;
|
const statusHeaders = definition.presentation.statusHeaders;
|
||||||
const preferredStatus = statusHeaders?.map(pattern => record.fields.find(field => field.value.trim() && matches(pattern, field.label.trim())))
|
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
|
const values = statusHeaders?.length
|
||||||
? preferredStatus ? [plainTextValue(preferredStatus.value).trim().toLowerCase()] : []
|
? preferredStatus ? [plainTextValue(preferredStatus.value).trim().toLowerCase()] : []
|
||||||
: normalizedFieldValues(record);
|
: normalizedFieldValues(record);
|
||||||
if (status === 'deviation' && !filter) {
|
|
||||||
return values.some(value => UNIVERSAL_DEVIATION_PATTERNS.some(pattern => matches(pattern, value)));
|
|
||||||
}
|
|
||||||
if (!filter) return true;
|
if (!filter) return true;
|
||||||
return values.some(value => filter.patterns.some(pattern => matches(pattern, value)));
|
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.
|
// 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([{ ...companion, detectedTool: { id: 'purple-knight', name: 'PurpleKnight' } }]).state).toBe('review');
|
||||||
expect(summarizeScanProcessingReadiness([currentPurpleKnight('run'), companion,
|
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', () => {
|
it('marks GPO failure and recovery red, including clients with healthy scanner reports', () => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue