Ticket bleibt bei WIP-Wechseln bis zur Board-Ankunft des Entwicklers zurueckgehalten (Issue #61)
Bislang wurde ein Ticket beim Wechsel von/zu "in_arbeit" sofort an seiner neuen
Spalte gezeichnet, unabhaengig davon, ob der zustaendige Entwickler laut eigenem
gemeldetem state ("walking_to_board") ueberhaupt schon am Board angekommen war -
die Animation wirkte dadurch unvollstaendig/vorgezogen. Analog zu der fuer
Tuer-/PO-Besuche in Issue #55 eingefuehrten Merge-Logik haelt office-render.js
das Ticket nun an seiner alten Stelle, bis der zustaendige Agent laut aktuellstem
Poll das Board erreicht hat (oder nicht mehr feststellbar ist).
detectAgentOwnedTicketChanges() und hasAgentReachedBoard() in office-scene.js
kapseln die dafuer noetige Erkennungs-/Entscheidungslogik pur und testbar.
This commit is contained in:
parent
32f3a3d30c
commit
90e8a61962
3 changed files with 124 additions and 0 deletions
|
|
@ -45,6 +45,8 @@ import {
|
||||||
advancePoVisitStage,
|
advancePoVisitStage,
|
||||||
PO_VISIT_STAGES,
|
PO_VISIT_STAGES,
|
||||||
applyPendingTicketMoves,
|
applyPendingTicketMoves,
|
||||||
|
detectAgentOwnedTicketChanges,
|
||||||
|
hasAgentReachedBoard,
|
||||||
PO_AGENT_ID,
|
PO_AGENT_ID,
|
||||||
PO_DISPLAY_NAME,
|
PO_DISPLAY_NAME,
|
||||||
resolveReloadIntervalSeconds,
|
resolveReloadIntervalSeconds,
|
||||||
|
|
@ -1946,6 +1948,30 @@ async function boot() {
|
||||||
// zugehoerige Persona (Tuerbesucher oder PO) das Board erreicht hat - vorher wird das Ticket
|
// zugehoerige Persona (Tuerbesucher oder PO) das Board erreicht hat - vorher wird das Ticket
|
||||||
// weiterhin an seiner alten Stelle (bzw. bei neuen Tickets: gar nicht) angezeigt.
|
// weiterhin an seiner alten Stelle (bzw. bei neuen Tickets: gar nicht) angezeigt.
|
||||||
const pendingTicketMoves = new Map();
|
const pendingTicketMoves = new Map();
|
||||||
|
// Issue #61: ticketId -> zustaendiger agentId fuer zurueckgehaltene "in_arbeit"-Wechsel (siehe
|
||||||
|
// detectAgentOwnedTicketChanges() in office-scene.js). Anders als bei Tuer-/PO-Besuchen gibt es
|
||||||
|
// dafuer keinen eigenen onArrived-Callback - der Agent meldet seine Ankunft am Board
|
||||||
|
// unabhaengig ueber seinen eigenen state, daher wird bei jedem refresh() erneut geprueft
|
||||||
|
// (siehe resolvePendingAgentTicketMoves()).
|
||||||
|
const pendingAgentTicketMoves = new Map();
|
||||||
|
|
||||||
|
// Issue #61: entlaesst genau die zurueckgehaltenen "in_arbeit"-Wechsel, deren zustaendiger
|
||||||
|
// Agent laut aktuellstem Poll bereits am Board angekommen ist (oder nicht mehr feststellbar
|
||||||
|
// ist) - siehe hasAgentReachedBoard(). Laeuft bei jedem refresh(), nicht nur beim Erkennen der
|
||||||
|
// Aenderung, da die Ankunft typischerweise erst in einem spaeteren Poll sichtbar wird.
|
||||||
|
function resolvePendingAgentTicketMoves(agents) {
|
||||||
|
if (pendingAgentTicketMoves.size === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const agentsById = new Map(agents.map((agent) => [agent.id, agent]));
|
||||||
|
for (const [ticketId, agentId] of pendingAgentTicketMoves) {
|
||||||
|
const agent = agentsById.get(agentId);
|
||||||
|
if (hasAgentReachedBoard(agent?.state)) {
|
||||||
|
pendingTicketMoves.delete(ticketId);
|
||||||
|
pendingAgentTicketMoves.delete(ticketId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderLiveScene(data) {
|
function renderLiveScene(data) {
|
||||||
const displayTickets = applyPendingTicketMoves(data.tickets, pendingTicketMoves);
|
const displayTickets = applyPendingTicketMoves(data.tickets, pendingTicketMoves);
|
||||||
|
|
@ -1990,7 +2016,16 @@ async function boot() {
|
||||||
});
|
});
|
||||||
triggerPoVisit(() => commitTicketMoves(ticketIds));
|
triggerPoVisit(() => commitTicketMoves(ticketIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue #61: Wechsel von/zu "in_arbeit" meldet der zustaendige Agent selbst ueber seinen
|
||||||
|
// state (siehe AGENT_OWNED_COLUMN in office-scene.js) - das Ticket bleibt daher an seiner
|
||||||
|
// alten Stelle, bis resolvePendingAgentTicketMoves() unten die Ankunft am Board bestaetigt.
|
||||||
|
detectAgentOwnedTicketChanges(previousTickets, data.tickets).forEach((change) => {
|
||||||
|
pendingTicketMoves.set(change.ticketId, previousById.get(change.ticketId) ?? null);
|
||||||
|
pendingAgentTicketMoves.set(change.ticketId, change.agentId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
resolvePendingAgentTicketMoves(data.agents);
|
||||||
previousTickets = data.tickets;
|
previousTickets = data.tickets;
|
||||||
renderLiveScene(data);
|
renderLiveScene(data);
|
||||||
setApiBanner(false);
|
setApiBanner(false);
|
||||||
|
|
|
||||||
|
|
@ -853,6 +853,56 @@ export function detectUnassignedTicketChanges(previousTickets = [], currentTicke
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issue #61: Gegenstueck zu detectUnassignedTicketChanges() fuer genau die dort ausgeschlossenen
|
||||||
|
* Wechsel von/zu "in_arbeit" (siehe AGENT_OWNED_COLUMN). Anders als Tuer-/PO-Besuche gibt es fuer
|
||||||
|
* diese Wechsel keine hier selbst getaktete Animation - der zustaendige Agent meldet seine
|
||||||
|
* Bewegung zum Board (state "walking_to_board"/"interacting_with_board") unabhaengig ueber seinen
|
||||||
|
* eigenen state. office-render.js nutzt das Ergebnis, um das Ticket so lange an seiner alten
|
||||||
|
* Stelle zu halten, bis hasAgentReachedBoard() fuer den zustaendigen Agenten zutrifft (siehe dort)
|
||||||
|
* - sonst wuerde das Ticket bereits die Spalte wechseln, waehrend der Entwickler sichtbar noch
|
||||||
|
* auf dem Weg zum Board ist.
|
||||||
|
*
|
||||||
|
* @param {Array<{id: number, column_name: string, assignee_agent_id?: number|string|null}>} previousTickets
|
||||||
|
* @param {Array<{id: number, column_name: string, assignee_agent_id?: number|string|null}>} currentTickets
|
||||||
|
* @returns {Array<{ticketId: number, agentId: number|string|null}>}
|
||||||
|
*/
|
||||||
|
export function detectAgentOwnedTicketChanges(previousTickets = [], currentTickets = []) {
|
||||||
|
const previousById = new Map(previousTickets.map((ticket) => [ticket.id, ticket]));
|
||||||
|
const changes = [];
|
||||||
|
|
||||||
|
for (const ticket of currentTickets) {
|
||||||
|
const before = previousById.get(ticket.id);
|
||||||
|
if (!before || before.column_name === ticket.column_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (before.column_name !== AGENT_OWNED_COLUMN && ticket.column_name !== AGENT_OWNED_COLUMN) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const agentId = ticket.assignee_agent_id ?? before.assignee_agent_id ?? null;
|
||||||
|
changes.push({ ticketId: ticket.id, agentId });
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stages aus AGENT_STATE_VISUALS, waehrend derer der zustaendige Agent laut eigenem state
|
||||||
|
// nachweislich noch NICHT am Board angekommen ist (Issue #61). Jeder andere gemeldete state -
|
||||||
|
// insbesondere "interacting_with_board" und "walking_back", aber auch ein voellig unbekannter/
|
||||||
|
// fehlender Agent - gilt als "Ankunft bereits erfolgt (oder nicht mehr feststellbar)", damit ein
|
||||||
|
// zurueckgehaltenes Ticket nicht dauerhaft an seiner alten Stelle haengen bleibt, nur weil eine
|
||||||
|
// Zwischen-Stage zwischen zwei Polls verpasst wurde.
|
||||||
|
const STATES_BEFORE_BOARD_ARRIVAL = new Set(["walking_to_board"]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string|undefined|null} agentState - zuletzt gemeldeter Agent.state (API).
|
||||||
|
* @returns {boolean} true, sobald das zurueckgehaltene Ticket sichtbar an die neue Spalte
|
||||||
|
* wechseln darf (Issue #61).
|
||||||
|
*/
|
||||||
|
export function hasAgentReachedBoard(agentState) {
|
||||||
|
return !STATES_BEFORE_BOARD_ARRIVAL.has(agentState);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Issue #55: Bislang wurde ein Ticket sofort an seinem neuen Platz gezeichnet, sobald eine
|
* Issue #55: Bislang wurde ein Ticket sofort an seinem neuen Platz gezeichnet, sobald eine
|
||||||
* Aenderung erkannt wurde - Tuerbesuch/PO-Besuch liefen davon unabhaengig erst danach als reine
|
* Aenderung erkannt wurde - Tuerbesuch/PO-Besuch liefen davon unabhaengig erst danach als reine
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@ import {
|
||||||
detectUnassignedTicketChanges,
|
detectUnassignedTicketChanges,
|
||||||
advancePoVisitStage,
|
advancePoVisitStage,
|
||||||
applyPendingTicketMoves,
|
applyPendingTicketMoves,
|
||||||
|
detectAgentOwnedTicketChanges,
|
||||||
|
hasAgentReachedBoard,
|
||||||
DEFAULT_RELOAD_INTERVAL_SECONDS,
|
DEFAULT_RELOAD_INTERVAL_SECONDS,
|
||||||
resolveReloadIntervalSeconds,
|
resolveReloadIntervalSeconds,
|
||||||
nextCountdownSeconds,
|
nextCountdownSeconds,
|
||||||
|
|
@ -770,6 +772,43 @@ test("detectUnassignedTicketChanges verarbeitet mehrere gleichzeitige Wechsel",
|
||||||
assert.deepEqual(detectUnassignedTicketChanges(before, after), [{ ticketId: 1, column: "bereit" }]);
|
assert.deepEqual(detectUnassignedTicketChanges(before, after), [{ ticketId: 1, column: "bereit" }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("detectAgentOwnedTicketChanges erkennt Wechsel nach in_arbeit inkl. zustaendigem Agenten (Issue #61)", () => {
|
||||||
|
const before = [{ id: 1, column_name: "bereit", assignee_agent_id: null }];
|
||||||
|
const after = [{ id: 1, column_name: "in_arbeit", assignee_agent_id: 7 }];
|
||||||
|
assert.deepEqual(detectAgentOwnedTicketChanges(before, after), [{ ticketId: 1, agentId: 7 }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("detectAgentOwnedTicketChanges erkennt Wechsel weg von in_arbeit und nutzt den vorherigen Agenten, falls das Ticket schon unassigned ist (Issue #61)", () => {
|
||||||
|
const before = [{ id: 1, column_name: "in_arbeit", assignee_agent_id: 7 }];
|
||||||
|
const after = [{ id: 1, column_name: "testing", assignee_agent_id: null }];
|
||||||
|
assert.deepEqual(detectAgentOwnedTicketChanges(before, after), [{ ticketId: 1, agentId: 7 }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("detectAgentOwnedTicketChanges ignoriert Wechsel ohne in_arbeit-Beteiligung (Issue #61)", () => {
|
||||||
|
const before = [{ id: 1, column_name: "qa", assignee_agent_id: 7 }];
|
||||||
|
const after = [{ id: 1, column_name: "fertig", assignee_agent_id: 7 }];
|
||||||
|
assert.deepEqual(detectAgentOwnedTicketChanges(before, after), []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("detectAgentOwnedTicketChanges ignoriert Tickets ohne Spaltenwechsel und neu angelegte Tickets (Issue #61)", () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
detectAgentOwnedTicketChanges(
|
||||||
|
[{ id: 1, column_name: "in_arbeit" }],
|
||||||
|
[{ id: 1, column_name: "in_arbeit" }],
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
assert.deepEqual(detectAgentOwnedTicketChanges([], [{ id: 1, column_name: "in_arbeit" }]), []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("hasAgentReachedBoard ist waehrend walking_to_board false, sonst true (Issue #61)", () => {
|
||||||
|
assert.equal(hasAgentReachedBoard("walking_to_board"), false);
|
||||||
|
assert.equal(hasAgentReachedBoard("interacting_with_board"), true);
|
||||||
|
assert.equal(hasAgentReachedBoard("walking_back"), true);
|
||||||
|
assert.equal(hasAgentReachedBoard("idle"), true);
|
||||||
|
assert.equal(hasAgentReachedBoard(undefined), true);
|
||||||
|
});
|
||||||
|
|
||||||
test("advancePoVisitStage durchlaeuft alle Stages und liefert am Ende null", () => {
|
test("advancePoVisitStage durchlaeuft alle Stages und liefert am Ende null", () => {
|
||||||
let stage = PO_VISIT_STAGES[0];
|
let stage = PO_VISIT_STAGES[0];
|
||||||
const seen = [stage];
|
const seen = [stage];
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue