Assessor_2.0/src/compliance/ui/mapping-run-tracker.ts

58 lines
2.8 KiB
TypeScript

import { readEnginePreferences } from '../../app/engine-preferences';
import { beginActivity } from '../../platform/activity-store';
import type { RegulatoryFramework, RegulatoryMappingResponse } from '../regulatory-mapping';
import { statusPollApi } from './status-poll-client';
const tracked = new Set<string>();
const listeners = new Set<(key: string, response: RegulatoryMappingResponse) => void>();
const isActive = (response: RegulatoryMappingResponse) => response.state === 'queued' || response.state === 'running';
const wait = (milliseconds: number) => new Promise(resolve => setTimeout(resolve, milliseconds));
// ISO keys keep their earlier form; a BSI mapping of the same finding is tracked separately.
export const mappingRunKey = (projectId: string, resultProjectionId: string, framework: RegulatoryFramework = 'iso27001') =>
`${projectId}\u0000${resultProjectionId}${framework === 'iso27001' ? '' : `\u0000${framework}`}`;
/** Views showing a mapping receive the tracker's status updates instead of polling themselves. */
export function subscribeRegulatoryMappingRuns(listener: (key: string, response: RegulatoryMappingResponse) => void): () => void {
listeners.add(listener);
return () => { listeners.delete(listener); };
}
/**
* Keeps one `Mapping ISO controls` activity for a queued or running mapping until the backend ends it,
* independent of the open window, the selected finding or the active view. Switching the Compliance
* Engine off stops observing; the backend work itself is never cancelled.
*/
export function trackRegulatoryMappingRun(projectId: string, resultProjectionId: string, response: RegulatoryMappingResponse): void {
const framework = response.framework ?? 'iso27001';
const key = mappingRunKey(projectId, resultProjectionId, framework);
if (!isActive(response) || tracked.has(key)) return;
tracked.add(key);
const label = framework === 'bsi-kernel' ? 'Mapping BSI requirements' : 'Mapping ISO controls';
const activity = beginActivity('Compliance Engine', label, `compliance-mapping:${key}`);
if (response.state === 'queued') activity.queue();
void (async () => {
let current = response;
try {
while (isActive(current)) {
await wait(current.pollAfterMs ?? 1_500);
if (!readEnginePreferences().compliance) {
activity.update(`${label} continues in the backend`);
activity.complete();
return;
}
current = await statusPollApi.getRegulatoryMappings(projectId, resultProjectionId, framework);
if (current.state === 'running') activity.run();
listeners.forEach(listener => listener(key, current));
}
if (current.state === 'failed') activity.fail();
else activity.complete();
} catch {
activity.update(`${label}: status could not be checked`);
activity.fail();
} finally {
tracked.delete(key);
}
})();
}