fix: confirm queue closures across clients
This commit is contained in:
@@ -21,7 +21,7 @@ export type TicketRoleAssignmentSummary = { assignment_id: string, role: string,
|
||||
|
||||
export type TicketAssignmentPrincipalSummary = { "kind": "user", account_id: string, } | { "kind": "worker", runtime_id: string, worker_id: string, } | { "kind": "workspace_agent", agent_key: string, };
|
||||
|
||||
export type TicketActionEligibility = { can_assign_orchestrator: boolean, can_unassign_orchestrator: boolean, can_queue: boolean, can_start_manual_coder: boolean, blockers: Array<string>, };
|
||||
export type TicketActionEligibility = { can_assign_orchestrator: boolean, can_unassign_orchestrator: boolean, can_queue: boolean, can_start_manual_coder: boolean, queue_tickets: Array<string>, blockers: Array<string>, };
|
||||
|
||||
export type TicketMergeRequestSummary = { merge_request_id: string, repository_id: string, state: string, review_status: string, selector_from: string | null, selector_to: string, updated_at: string, current_subject_ref: string | null, review_subject_ref: string | null, review_requested_at: string | null, review_submitted_at: string | null, review_excerpt: string | null, };
|
||||
|
||||
|
||||
@@ -120,10 +120,9 @@ Deno.test("ticket detail uses server-derived role assignment actions", async ()
|
||||
|
||||
assertEquals(source.includes("ticket.action_eligibility.can_queue"), true);
|
||||
assertEquals(source.includes("ticket.relations.blockers.length > 0"), true);
|
||||
assertEquals(
|
||||
source.includes("Queue records orchestration demand. Dependency relations remain visible"),
|
||||
true,
|
||||
);
|
||||
assertEquals(source.includes("ticket.action_eligibility.queue_tickets"), true);
|
||||
assertEquals(source.includes("This operation queues:"), true);
|
||||
assertEquals(source.includes("outcome.queued_tickets.join"), true);
|
||||
assertEquals(
|
||||
source.includes("resolve the listed blockers before Queue"),
|
||||
false,
|
||||
|
||||
@@ -38,6 +38,11 @@
|
||||
if (!loadedTicket) throw new Error(initialData.ticket.error ?? "ticket load failed");
|
||||
const loadedRepositories = initialData.repositories.data;
|
||||
|
||||
type QueueOutcome = {
|
||||
requested_ticket: string;
|
||||
queued_tickets: string[];
|
||||
};
|
||||
|
||||
let ticket = $state<TicketDetail>(loadedTicket);
|
||||
const mergeRequest = $derived(ticket.merge_request);
|
||||
let editing = $state(false);
|
||||
@@ -52,6 +57,7 @@
|
||||
let resolution = $state("");
|
||||
let busy = $state<string | null>(null);
|
||||
let errorMessage = $state<string | null>(null);
|
||||
let queueMessage = $state<string | null>(null);
|
||||
let readyOperationKey = $state<string | null>(null);
|
||||
let manualRuntimeId = $state("");
|
||||
let manualWorkerId = $state("");
|
||||
@@ -117,6 +123,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function queueTicket(): Promise<void> {
|
||||
if (busy) return;
|
||||
busy = "queue";
|
||||
errorMessage = null;
|
||||
queueMessage = null;
|
||||
try {
|
||||
const outcome = await workspaceApiJsonWithBody<QueueOutcome>(
|
||||
`${ticketPath}/queue`,
|
||||
{ method: "POST", body: JSON.stringify({}) },
|
||||
);
|
||||
queueMessage = `Queued ${outcome.queued_tickets.length} Ticket(s): ${outcome.queued_tickets.join(", ")}`;
|
||||
applyTicket(await workspaceApiJson<TicketDetail>(ticketPath));
|
||||
} catch (error) {
|
||||
errorMessage = error instanceof Error ? error.message : String(error);
|
||||
} finally {
|
||||
busy = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function mutateAssignment(
|
||||
action: string,
|
||||
role: "orchestrator" | "coder",
|
||||
@@ -272,6 +297,10 @@
|
||||
<div class="workspace-callout is-error" role="alert">{errorMessage}</div>
|
||||
{/if}
|
||||
|
||||
{#if queueMessage}
|
||||
<div class="workspace-callout" role="status">{queueMessage}</div>
|
||||
{/if}
|
||||
|
||||
{#if editing}
|
||||
<form class="ticket-editor" onsubmit={saveEdit}>
|
||||
<label>Title<input bind:value={editTitle} required /></label>
|
||||
@@ -462,13 +491,16 @@
|
||||
<p class="workspace-empty-copy">Choose a healthy repository and an effective ref selector before marking ready.</p>
|
||||
{/if}
|
||||
{:else if ticket.state === "ready"}
|
||||
<button class="workspace-primary-button ticket-queue-button" type="button" disabled={busy === "queue" || !ticket.action_eligibility.can_queue} onclick={() => mutate("queue", "/queue", {})}>
|
||||
{busy === "queue" ? "Queueing…" : "Queue ticket"}
|
||||
<button class="workspace-primary-button ticket-queue-button" type="button" disabled={busy === "queue" || !ticket.action_eligibility.can_queue} onclick={() => void queueTicket()}>
|
||||
{busy === "queue" ? "Queueing…" : `Queue ${ticket.action_eligibility.queue_tickets.length} Ticket(s)`}
|
||||
</button>
|
||||
{#if !ticket.action_eligibility.can_queue}
|
||||
<p class="workspace-empty-copy">Queue requires a valid target, an active Orchestrator assignment, no active Coder assignment, and no dependency still in planning.</p>
|
||||
{:else if ticket.relations.blockers.length > 0}
|
||||
<p class="workspace-empty-copy">Ready dependencies are queued atomically. Queued or in-progress dependencies remain unchanged for the Orchestrator to schedule.</p>
|
||||
{:else if ticket.action_eligibility.queue_tickets.length > 0}
|
||||
<p class="workspace-empty-copy">This operation queues: {ticket.action_eligibility.queue_tickets.join(", ")}.</p>
|
||||
{#if ticket.relations.blockers.length > 0}
|
||||
<p class="workspace-empty-copy">Ready dependencies are queued atomically. Queued or in-progress dependencies remain unchanged for the Orchestrator to schedule.</p>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user