Files
electric-cherry/src/lib/operation-flow.ts
T

111 lines
3.8 KiB
TypeScript

export interface ActivityEvent {
id: number;
created_at: string;
message: string;
}
export interface OperationStep {
label: string;
state: 'pending' | 'current' | 'done' | 'failed';
}
interface BetaServer {
stage: string;
deployed_at: string | null;
restarted_at: string | null;
healthy_at: string | null;
match_seen_at: string | null;
}
export function betaFlow(
beta: { state: string; wait_for_health: number; wait_for_match: number; testPlan?: boolean },
servers: BetaServer[]
): OperationStep[] {
const stages = ['deploying', 'awaiting-empty', 'restarting'];
const labels = ['Stage file changes', 'Wait for empty servers', 'Apply files and restart'];
if (beta.wait_for_health) {
stages.push('awaiting-health');
labels.push('Verify health');
}
if (beta.wait_for_match) {
stages.push('awaiting-match');
labels.push('Observe a completed match');
}
if (beta.testPlan) {
stages.push('observing');
labels.push('Evaluate test requirements');
}
stages.push('ready');
labels.push('Review and promote');
const positions = servers.map((s) => {
if (s.stage !== 'failed') return Math.max(0, stages.indexOf(s.stage));
if (!s.deployed_at) return 0;
if (!s.restarted_at) return 2;
if (beta.wait_for_health && !s.healthy_at) return stages.indexOf('awaiting-health');
if (beta.wait_for_match && !s.match_seen_at) return stages.indexOf('awaiting-match');
return stages.length - 1;
});
const current =
beta.state === 'promoted'
? stages.length
: beta.state === 'ready-promote'
? stages.length - 1
: positions.length
? Math.min(...positions)
: 0;
return labels.map((label, i) => ({
label,
state:
i < current
? 'done'
: i === current
? beta.state === 'rolled-back'
? 'pending'
: servers.some((s, j) => s.stage === 'failed' && positions[j] === i)
? 'failed'
: 'current'
: 'pending'
}));
}
export function campaignFlow(
servers: {
server?: string;
restart_state: string;
restarted_at: string | null;
files_changed: number;
}[],
finished: boolean,
files: { server: string; snapshot_pending: number; apply_state: string }[] = []
): OperationStep[] {
const active = servers.filter((s) => !['skipped', 'cancelled'].includes(s.restart_state));
const positions = active.map((s) => {
if (s.restart_state === 'done') return 5;
if (s.restarted_at) return 4;
const changes = files.filter((f) => f.server === s.server && f.apply_state !== 'cancelled');
if (changes.some((f) => f.apply_state === 'applying'))
return changes.some((f) => f.snapshot_pending) ? 1 : 2;
if (changes.length && changes.every((f) => f.apply_state === 'applied')) return 3;
return 0;
});
const current = positions.length ? Math.min(...positions) : finished ? 5 : 0;
return [
'Wait for empty servers and restart capacity',
'Capture rollback snapshots',
'Apply file changes',
'Restart servers',
'Verify health'
].map((label, i) => ({
label,
state:
i < current
? 'done'
: i !== current
? 'pending'
: active.some((s, j) => s.restart_state === 'failed' && positions[j] === i)
? 'failed'
: active.some((s) => ['manual', 'held'].includes(s.restart_state))
? 'pending'
: 'current'
}));
}