S12 - turned schedule status into enum and some other review comments
All checks were successful
Build, test and publish / verify (push) Successful in 7m6s

This commit is contained in:
2026-11-04 08:05:39 +01:00
parent c241392132
commit a3cff48a92
12 changed files with 112 additions and 41 deletions

View File

@@ -1,5 +1,5 @@
import { TestBed } from '@angular/core/testing';
import { SchedulingRun, SchedulingStatus } from './scheduling-status';
import { SchedulingRun, SchedulingState, SchedulingStatus } from './scheduling-status';
class FakeEventSource {
static current: FakeEventSource;
@@ -14,7 +14,7 @@ class FakeEventSource {
}
}
function run(state: string, runId = 'run-1'): SchedulingRun {
function run(state: SchedulingState, runId = 'run-1'): SchedulingRun {
return {
runId,
schoolId: 1,
@@ -82,6 +82,19 @@ describe('Scheduling status over SSE', () => {
expect(status.notifications()).toHaveLength(1);
});
it('announces a locally started run completed before the first SSE snapshot', () => {
FakeEventSource.current.onerror?.();
status.started(run('RUNNING'));
vi.advanceTimersByTime(3000);
FakeEventSource.current.send([run('OPTIMAL'), run('VALID', 'historical')]);
expect(status.notifications().map((item) => item.runId)).toEqual(['run-1']);
status.dismiss();
FakeEventSource.current.onerror?.();
vi.advanceTimersByTime(3000);
FakeEventSource.current.send([run('OPTIMAL'), run('VALID', 'historical')]);
expect(status.notifications()).toEqual([]);
});
it('retains a fast completion when the start response arrives after SSE', () => {
FakeEventSource.current.send([]);
FakeEventSource.current.send([run('OPTIMAL')]);

View File

@@ -1,5 +1,16 @@
import { DestroyRef, inject, Injectable, signal } from '@angular/core';
export type SchedulingState =
| 'RUNNING'
| 'OPTIMAL'
| 'VALID'
| 'STALE'
| 'INVALID_INPUT'
| 'INFEASIBLE'
| 'NO_SOLUTION'
| 'SOLVER_FAILED';
export type SchedulingPhase = 'PREPARING' | 'ATTENDANCE' | 'SPREAD' | 'VARIETY';
export interface SchedulingRun {
runId: string;
schoolId: number;
@@ -7,8 +18,8 @@ export interface SchedulingRun {
showName: string;
startedAt: string;
finishedAt: string | null;
state: string;
progress: { phase: string; feasible: boolean };
state: SchedulingState;
progress: { phase: SchedulingPhase; feasible: boolean };
message: string;
summary: {
attendanceExcess: number;
@@ -58,10 +69,16 @@ export class SchedulingStatus {
}
private accept(runs: SchedulingRun[]): void {
const activeRuns = new Set(
this.runs()
.filter((run) => run.state === 'RUNNING')
.map((run) => run.runId),
);
for (const run of runs) {
if (run.state === 'RUNNING' || this.seen.has(run.runId)) continue;
this.seen.add(run.runId);
if (this.initialized) this.notifications.update((items) => [...items, run]);
if (this.initialized || activeRuns.has(run.runId))
this.notifications.update((items) => [...items, run]);
}
this.initialized = true;
this.runs.set(runs);
@@ -96,7 +113,7 @@ export class SchedulingStatus {
ATTENDANCE: 'Aanwezigheid',
SPREAD: 'Spreiding',
VARIETY: 'Variatie',
} as Record<string, string>
} satisfies Record<SchedulingPhase, string>
)[run.progress.phase] ?? run.progress.phase
);
}