FN-6030: fix workflow terminal notifications
Ensure merged workflow tasks still emit the expected terminal notifications. - emit task:merged when PR-driven workflow merges move tasks to done - send merged notifications for merge-backed done transitions and suppress duplicates - add regression coverage, a published changeset, and reconcile overlapping docs/test updates Files changed: .../fn-6030-workflow-terminal-notifications.md | 5 + docs/storage.md | 8 +- .../core/src/__tests__/builtin-workflows.test.ts | 1 - .../__tests__/store-pr-merged-transition.test.ts | 14 ++- .../src/__tests__/workflow-ir-resolver.test.ts | 1 - packages/core/src/store.ts | 14 ++- .../settings/sections/ProjectModelsSection.tsx | 3 +- .../engine/src/__tests__/merger-post-merge.test.ts | 9 +- .../src/__tests__/notification-service.test.ts | 129 ++++++++++++++++++++- .../__tests__/workflow-graph-task-runner.test.ts | 57 +++++++++ .../src/notification/notification-service.ts | 36 ++++-- 11 files changed, 257 insertions(+), 20 deletions(-) Fusion-Task-Id: FN-6030 Fusion-Task-Lineage: a991f765-0986-4541-ab38-ff476cf88d16
This commit is contained in:
@@ -151,7 +151,6 @@ describe("built-in workflows", () => {
|
||||
"builtin:stepwise-coding",
|
||||
]);
|
||||
});
|
||||
|
||||
it("builtin:coding exposes execute retries after registry lookup and parse round-trip", () => {
|
||||
const coding = getBuiltinWorkflow("builtin:coding");
|
||||
expect(coding).toBeDefined();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { TaskStore } from "../store.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
@@ -16,7 +16,7 @@ describe("TaskStore.applyPrMergedTransition", () => {
|
||||
await harness.afterEach();
|
||||
});
|
||||
|
||||
it("moves in-review merged tasks to done once", async () => {
|
||||
it("moves in-review merged tasks to done once and emits task:merged", async () => {
|
||||
const task = await store.createTask({ description: "merged task" });
|
||||
await store.moveTask(task.id, "todo");
|
||||
await store.moveTask(task.id, "in-progress");
|
||||
@@ -30,9 +30,19 @@ describe("TaskStore.applyPrMergedTransition", () => {
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
});
|
||||
const mergedListener = vi.fn();
|
||||
store.on("task:merged", mergedListener);
|
||||
|
||||
await expect(store.applyPrMergedTransition(task.id)).resolves.toEqual({ moved: true });
|
||||
expect(mergedListener).toHaveBeenCalledTimes(1);
|
||||
expect(mergedListener).toHaveBeenCalledWith(expect.objectContaining({
|
||||
branch: "fusion/fn-1",
|
||||
merged: true,
|
||||
task: expect.objectContaining({ id: task.id, column: "done" }),
|
||||
}));
|
||||
|
||||
await expect(store.applyPrMergedTransition(task.id)).resolves.toEqual({ moved: false, skipped: "already-done" });
|
||||
expect(mergedListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("skips when pr status is not merged", async () => {
|
||||
|
||||
@@ -118,7 +118,6 @@ describe("resolveWorkflowIrById", () => {
|
||||
expect(ir).toBe(BUILTIN_CODING_WORKFLOW_IR);
|
||||
expect(store.getWorkflowDefinition).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("parses a raw-string IR from the definition", async () => {
|
||||
const raw = JSON.stringify(CUSTOM_IR);
|
||||
const store = makeStore({ defs: { "wf-raw": { ir: raw } } });
|
||||
|
||||
@@ -11809,13 +11809,25 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
|
||||
return { moved: false, skipped: "wrong-column" };
|
||||
}
|
||||
|
||||
await this.moveTask(taskId, "done", {
|
||||
const movedTask = await this.moveTask(taskId, "done", {
|
||||
moveSource: "engine",
|
||||
preserveProgress: true,
|
||||
preserveWorktree: true,
|
||||
skipMergeBlocker: true,
|
||||
});
|
||||
|
||||
this.emit("task:merged", {
|
||||
task: movedTask,
|
||||
branch: movedTask.branch ?? movedTask.prInfo?.headBranch ?? freshTask.branch ?? freshTask.prInfo?.headBranch ?? "",
|
||||
merged: true,
|
||||
worktreeRemoved: false,
|
||||
branchDeleted: false,
|
||||
mergeConfirmed: movedTask.mergeDetails?.mergeConfirmed ?? freshTask.mergeDetails?.mergeConfirmed,
|
||||
mergedAt: movedTask.mergeDetails?.mergedAt ?? freshTask.mergeDetails?.mergedAt,
|
||||
mergeTargetBranch: movedTask.mergeDetails?.mergeTargetBranch ?? freshTask.mergeDetails?.mergeTargetBranch,
|
||||
mergeTargetSource: movedTask.mergeDetails?.mergeTargetSource ?? freshTask.mergeDetails?.mergeTargetSource,
|
||||
} satisfies MergeResult);
|
||||
|
||||
if (ctx?.agentId && ctx?.runId) {
|
||||
this.recordRunAuditEvent({
|
||||
taskId,
|
||||
|
||||
Reference in New Issue
Block a user