Merge pull request 'Ticket bleibt bei WIP-Wechseln bis zur Board-Ankunft des Entwicklers zurueckgehalten (Issue #61)' (#62) from fds2610-bot/monitor:main into main
Reviewed-on: https://codeberg.org/fds2610/monitor/pulls/62
This commit is contained in:
commit
d4d127a578
3 changed files with 124 additions and 0 deletions
|
|
@ -45,6 +45,8 @@ import {
|
|||
advancePoVisitStage,
|
||||
PO_VISIT_STAGES,
|
||||
applyPendingTicketMoves,
|
||||
detectAgentOwnedTicketChanges,
|
||||
hasAgentReachedBoard,
|
||||
PO_AGENT_ID,
|
||||
PO_DISPLAY_NAME,
|
||||
resolveReloadIntervalSeconds,
|
||||
|
|
@ -1946,6 +1948,30 @@ async function boot() {
|
|||
// 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.
|
||||
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) {
|
||||
const displayTickets = applyPendingTicketMoves(data.tickets, pendingTicketMoves);
|
||||
|
|
@ -1990,7 +2016,16 @@ async function boot() {
|
|||
});
|
||||
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;
|
||||
renderLiveScene(data);
|
||||
setApiBanner(false);
|
||||
|
|
|
|||
|
|
@ -853,6 +853,56 @@ export function detectUnassignedTicketChanges(previousTickets = [], currentTicke
|
|||
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
|
||||
* Aenderung erkannt wurde - Tuerbesuch/PO-Besuch liefen davon unabhaengig erst danach als reine
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ import {
|
|||
detectUnassignedTicketChanges,
|
||||
advancePoVisitStage,
|
||||
applyPendingTicketMoves,
|
||||
detectAgentOwnedTicketChanges,
|
||||
hasAgentReachedBoard,
|
||||
DEFAULT_RELOAD_INTERVAL_SECONDS,
|
||||
resolveReloadIntervalSeconds,
|
||||
nextCountdownSeconds,
|
||||
|
|
@ -770,6 +772,43 @@ test("detectUnassignedTicketChanges verarbeitet mehrere gleichzeitige Wechsel",
|
|||
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", () => {
|
||||
let stage = PO_VISIT_STAGES[0];
|
||||
const seen = [stage];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue