89 lines
4.2 KiB
TypeScript
89 lines
4.2 KiB
TypeScript
import type { ImportFileRecord } from '../../domain/canonical-model';
|
|
import type { GpoBackupStatusRecord } from '../../api/assessor-api-contract';
|
|
import { currentProcessingManifest, processingReadiness, type ProcessingReadinessStatus } from '../../domain/processing-compatibility';
|
|
|
|
export type DatahubClientProcessingState = 'ready' | 'processing' | 'update' | 'error' | 'no-data';
|
|
|
|
export interface DatahubClientProcessingSummary {
|
|
state: DatahubClientProcessingState;
|
|
label: string;
|
|
}
|
|
|
|
const UPDATE_STATES = new Set(['reimport-recommended']);
|
|
const ERROR_STATES = new Set(['incompatible', 'unknown']);
|
|
const ATTENTION_STATES = new Set([...UPDATE_STATES, ...ERROR_STATES]);
|
|
|
|
export interface DatahubScanProcessingSummary {
|
|
state: 'ready' | 'update' | 'error';
|
|
statuses: ProcessingReadinessStatus[];
|
|
explanation: string;
|
|
}
|
|
|
|
export function summarizeScanProcessingReadiness(files: ImportFileRecord[]): DatahubScanProcessingSummary {
|
|
const statuses = [...new Set(files.map(file => processingReadiness(
|
|
file.detectedTool.id || file.processedWith.profileId,
|
|
file.processedWith
|
|
)))];
|
|
if (!statuses.some(status => ATTENTION_STATES.has(status))) {
|
|
return { state: 'ready', statuses, explanation: 'The scan uses a current or compatible ASSESSOR processing profile.' };
|
|
}
|
|
|
|
const hasError = statuses.some(status => ERROR_STATES.has(status));
|
|
const relevantStates = hasError ? ERROR_STATES : UPDATE_STATES;
|
|
const representative = files.find(file => relevantStates.has(processingReadiness(
|
|
file.detectedTool.id || file.processedWith.profileId,
|
|
file.processedWith
|
|
))) ?? files[0];
|
|
const current = representative ? currentProcessingManifest(representative.detectedTool.id || representative.processedWith.profileId) : undefined;
|
|
if (statuses.includes('unknown')) {
|
|
return { state: 'error', statuses, explanation: 'The stored ASSESSOR processing profile is unknown and cannot be assessed safely.' };
|
|
}
|
|
if (statuses.includes('incompatible')) {
|
|
return { state: 'error', statuses, explanation: `The stored ASSESSOR processing profile is incompatible with the current ASSESSOR profile ${current?.profileVersion ?? 'unknown'}.` };
|
|
}
|
|
const contextCapture = current?.affectedDimensions.includes('environment-context')
|
|
? ' The newer ASSESSOR profile captures additional environment context for the assessment.'
|
|
: '';
|
|
return {
|
|
state: 'update',
|
|
statuses,
|
|
explanation: `ASSESSOR profile update: ${representative?.processedWith.profileVersion ?? 'unknown'} → ${current?.profileVersion ?? 'unknown'}. Reimporting applies the newer ASSESSOR processing profile; the ${representative?.detectedTool.name ?? 'scanner'} report itself has not changed.${contextCapture}`
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Summarises all active scanner runs represented by the client card. Every
|
|
* timeslot must use a comparable ASSESSOR processing contract before the
|
|
* client can be considered fully ready for lifecycle comparison.
|
|
*/
|
|
export function summarizeClientProcessingReadiness(
|
|
files: ImportFileRecord[],
|
|
activeScanRunIds: readonly string[],
|
|
gpoBackups: readonly Pick<GpoBackupStatusRecord, 'state'>[] = []
|
|
): DatahubClientProcessingSummary {
|
|
if (gpoBackups.some(record => record.state === 'dead-letter' || record.state === 'recovery-required')) {
|
|
return { state: 'error', label: 'GPO processing failed or requires recovery in a client timeslot.' };
|
|
}
|
|
|
|
const runIds = new Set(activeScanRunIds);
|
|
const statuses = files
|
|
.filter(file => runIds.has(file.scanRunId))
|
|
.map(file => processingReadiness(file.detectedTool.id || file.processedWith.profileId, file.processedWith));
|
|
|
|
if (statuses.some(status => ERROR_STATES.has(status))) {
|
|
return { state: 'error', label: 'Processing error requires attention in a client timeslot.' };
|
|
}
|
|
|
|
if (statuses.some(status => UPDATE_STATES.has(status))) {
|
|
return { state: 'update', label: 'ASSESSOR processing profile update available in a client timeslot.' };
|
|
}
|
|
|
|
if (gpoBackups.some(record => record.state === 'queued' || record.state === 'running')) {
|
|
return { state: 'processing', label: 'GPO processing in progress.' };
|
|
}
|
|
|
|
return statuses.length || gpoBackups.length
|
|
? { state: 'ready', label: 'Processing ready across all client timeslots.' }
|
|
: { state: 'no-data', label: 'No processing status available.' };
|
|
}
|