59 lines
2.8 KiB
TypeScript
59 lines
2.8 KiB
TypeScript
import { memo } from 'react';
|
|
import { RelatedFindingRing, useRelatedFindings } from '../../features/related-findings/RelatedFindings';
|
|
import type { FindingRecord } from '../../domain/finding';
|
|
import { normalizeSeverity } from '../../profiles/purple-knight/presentation';
|
|
import { toolIdForRecord } from '../../importers/status-normalization';
|
|
import { findingOutcomeMark, resultStatusFieldFor } from '../../features/linked-results/result-message';
|
|
import { policyDiagnosticBadges } from '../../profiles/group-policy/categories';
|
|
|
|
import { highlightText } from '../HighlightText/highlight-text';
|
|
|
|
export function findingCardStatus(record: FindingRecord, fallbackToolId?: string) {
|
|
return resultStatusFieldFor(record, fallbackToolId);
|
|
}
|
|
interface Props {
|
|
record: FindingRecord;
|
|
title: string;
|
|
bookmarked: boolean;
|
|
selected?: boolean;
|
|
/** Werkzeug-ID des Datasets; entscheidet ueber die werkzeuggebundene Statusnormalisierung. */
|
|
toolId?: string;
|
|
search?: string;
|
|
onOpen(): void;
|
|
}
|
|
|
|
function FindingCardComponent({ record, title, bookmarked, selected, toolId, search, onOpen }: Props) {
|
|
const groupMarked = useRelatedFindings()?.selection?.group.recordIds.includes(record.id);
|
|
const severityField = record.fields.find(field => /^(severity|criticality|priority)$/i.test(field.label) && field.value);
|
|
const severity = normalizeSeverity(severityField?.value);
|
|
const outcomeMark = findingOutcomeMark(record, toolId);
|
|
const resolvedToolId = toolIdForRecord(record, toolId);
|
|
const diagnostics = policyDiagnosticBadges(record, resolvedToolId);
|
|
return (
|
|
<div data-record-id={record.id} className={`record-card-with-group${groupMarked ? ' is-group-marked' : ''}`}>
|
|
<button
|
|
aria-current={selected ? 'true' : undefined}
|
|
className={`record-card severity-${severity}${bookmarked ? ' is-workspace' : ''}${selected ? ' active' : ''}`}
|
|
type="button"
|
|
onClick={onOpen}
|
|
>
|
|
<span className="record-title">{highlightText(title, search)}{bookmarked && <span className="action-pill">Workspace</span>}</span>
|
|
{(outcomeMark || diagnostics.length > 0) && <span className="record-meta-line">
|
|
{outcomeMark && <span className="record-meta outcome-mark" data-outcome={outcomeMark.outcome}>{highlightText(outcomeMark.label, search)}</span>}
|
|
{diagnostics.map(diagnostic => <span className="record-diagnostic-badge" data-policy-diagnostic={diagnostic.toLowerCase()} key={diagnostic}>{diagnostic}</span>)}
|
|
</span>}
|
|
|
|
</button>
|
|
<RelatedFindingRing record={record}/>
|
|
</div>
|
|
);
|
|
}
|
|
export const FindingCard = memo(FindingCardComponent, (previous, next) =>
|
|
previous.record === next.record &&
|
|
previous.title === next.title &&
|
|
previous.bookmarked === next.bookmarked &&
|
|
previous.selected === next.selected &&
|
|
previous.toolId === next.toolId &&
|
|
previous.search === next.search
|
|
);
|