mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-28 20:45:11 +02:00
fix(control-plane): restore accepted view after render failure
Preserve contributor history and current-main behavior while resolving the exact reviewed follow-up.
Source-PR: https://github.com/affaan-m/ECC/pull/3243
Source-Parent: cfe6d90d9f
Review-Manifest-SHA256: 78e13c4fe9581ef82100bb721f9b80ab8e62a15371e89b5f32c42c593944552b
This commit is contained in:
@@ -281,8 +281,23 @@ function renderControlPlaneViewHtml() {
|
||||
!Number.isFinite(data.thresholds.ta) || !Number.isFinite(data.thresholds.ra)) {
|
||||
throw new Error('Invalid control-plane view');
|
||||
}
|
||||
var previous = view;
|
||||
var drawStarted = false;
|
||||
view = Object.assign({}, data);
|
||||
renderEvents(); renderLanes(); draw();
|
||||
try {
|
||||
renderEvents(); renderLanes();
|
||||
drawStarted = true;
|
||||
draw();
|
||||
} catch (error) {
|
||||
view = previous;
|
||||
try {
|
||||
renderEvents(); renderLanes();
|
||||
if (drawStarted) draw();
|
||||
} catch (_) {
|
||||
// Keep the accepted model even if the DOM cannot be restored.
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
var c = view.counts || {};
|
||||
var summary = (c.tasks || 0) + ' tasks in ' + (c.lanes || 0) + ' lanes, ' +
|
||||
(c.advisories || 0) + ' advisories, ' + (c.resolutions || 0) + ' steering. ' +
|
||||
|
||||
@@ -103,6 +103,7 @@ async function render(responses) {
|
||||
const timers = [];
|
||||
const timeouts = [];
|
||||
const queue = responses.slice();
|
||||
const listeners = new Map();
|
||||
let jsonReads = 0;
|
||||
const document = {
|
||||
getElementById(id) { if (!elements.has(id)) elements.set(id, element(null, context)); return elements.get(id); },
|
||||
@@ -115,7 +116,7 @@ async function render(responses) {
|
||||
const code = html.slice(start + '<script>'.length, end);
|
||||
vm.runInNewContext(code, {
|
||||
document,
|
||||
window: { addEventListener() {}, devicePixelRatio: 1 },
|
||||
window: { addEventListener(name, listener) { listeners.set(name, listener); }, devicePixelRatio: 1 },
|
||||
AbortController,
|
||||
setInterval(fn) { timers.push(fn); },
|
||||
// Timers are collected rather than run, so a test can fire the request
|
||||
@@ -145,6 +146,7 @@ async function render(responses) {
|
||||
elements,
|
||||
context,
|
||||
jsonReads() { return jsonReads; },
|
||||
resizeAgain() { listeners.get('resize')(); },
|
||||
writesTo(id) { return elements.get(id).writes; },
|
||||
labelOf(id) { return elements.get(id).attributes['aria-label']; },
|
||||
// Fire every pending request timeout, then let the rejections propagate.
|
||||
@@ -518,6 +520,44 @@ let failures = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, malformed] of [
|
||||
['events', { events: [null] }],
|
||||
['lanes', { lanes: [null] }],
|
||||
['drawing', { projection: { agents: [null] } }],
|
||||
]) {
|
||||
try {
|
||||
const retained = populatedView({ events: [{ level: 'advisory', kind: 'accepted', message: 'retained event' }] });
|
||||
const invalid = populatedView({
|
||||
events: [{ level: 'resolution', kind: 'rejected', message: 'invalid event' }],
|
||||
lanes: [{ label: 'rejected lane', kind: 'lane', taskIds: ['task-clear'] }],
|
||||
projection: { agents: [{ agentId: 'task-clear', point: [1, 0], maxRisk: 0.1 }] },
|
||||
...malformed,
|
||||
});
|
||||
const repaired = await render([
|
||||
{ ok: true, data: retained }, { ok: true, data: invalid }, { ok: true, data: newBody },
|
||||
]);
|
||||
const eventsBefore = textOf(repaired.elements.get('events'));
|
||||
const lanesBefore = textOf(repaired.elements.get('lanes'));
|
||||
const markersBefore = markerShapes(repaired.context);
|
||||
await repaired.pollAgain();
|
||||
assert.strictEqual(textOf(repaired.elements.get('events')), eventsBefore, `${name}: retain previous events`);
|
||||
assert.strictEqual(textOf(repaired.elements.get('lanes')), lanesBefore, `${name}: retain previous lanes`);
|
||||
assert.strictEqual(repaired.elements.get('status').textContent, 'offline');
|
||||
const drawStart = repaired.context.log.length;
|
||||
assert.doesNotThrow(() => repaired.resizeAgain(), `${name}: resize must use the last accepted view`);
|
||||
assert.deepStrictEqual(markerShapes({ log: repaired.context.log.slice(drawStart) }), markersBefore);
|
||||
assert.match(repaired.labelOf('c'), /unavailable.*unknown/i);
|
||||
assert.match(repaired.elements.get('announce').textContent, /unavailable.*unknown/i);
|
||||
await repaired.pollAgain();
|
||||
assert.match(textOf(repaired.elements.get('lanes')), /newest/);
|
||||
assert.doesNotMatch(repaired.labelOf('c'), /unavailable/i);
|
||||
passed += 1;
|
||||
} catch (error) {
|
||||
failures += 1;
|
||||
console.error(`render rollback ${name}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Results: Passed: ${passed}, Failed: ${failures}`);
|
||||
process.exitCode = failures > 0 ? 1 : 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user