fix: persist ws_error_handler_muted for flows in create/update (#7797)

This commit is contained in:
Ruben Fiszel
2026-02-04 19:11:47 +00:00
committed by GitHub
parent 460b9bf82c
commit d113546169
4 changed files with 227 additions and 9 deletions
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO flow (\n workspace_id, path, summary, description,\n dependency_job, lock_error_logs, draft_only, tag,\n dedicated_worker, visible_to_runner_only, on_behalf_of_email,\n value, schema, edited_by, edited_at\n ) VALUES (\n $1, $2, $3, $4,\n NULL, '', $5, $6,\n $7, $8, $9,\n $10, $11::text::json, $12, now()\n )",
"query": "INSERT INTO flow (\n workspace_id, path, summary, description,\n dependency_job, lock_error_logs, draft_only, tag,\n dedicated_worker, visible_to_runner_only, on_behalf_of_email,\n ws_error_handler_muted,\n value, schema, edited_by, edited_at\n ) VALUES (\n $1, $2, $3, $4,\n NULL, '', $5, $6,\n $7, $8, $9,\n $10,\n $11, $12::text::json, $13, now()\n )",
"describe": {
"columns": [],
"parameters": {
@@ -14,6 +14,7 @@
"Bool",
"Bool",
"Text",
"Bool",
"Jsonb",
"Text",
"Varchar"
@@ -21,5 +22,5 @@
},
"nullable": []
},
"hash": "081dc94a7d0fdaade77cfb593a025d8c48d7eab3dbb30ca0b43fb1ef45d8d8bd"
"hash": "6bde827da007b470b9d0acccfc3e00ce6aac650b9138a236f34c614eed753849"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE\n flow\n SET\n path = $1,\n summary = $2,\n description = $3,\n dependency_job = NULL,\n lock_error_logs = '',\n draft_only = NULL,\n tag = $4,\n dedicated_worker = $5,\n visible_to_runner_only = $6,\n on_behalf_of_email = $7,\n value = $8,\n schema = $9::text::json,\n edited_by = $10,\n edited_at = now()\n WHERE\n path = $11 AND workspace_id = $12",
"query": "\n UPDATE\n flow\n SET\n path = $1,\n summary = $2,\n description = $3,\n dependency_job = NULL,\n lock_error_logs = '',\n draft_only = NULL,\n tag = $4,\n dedicated_worker = $5,\n visible_to_runner_only = $6,\n on_behalf_of_email = $7,\n ws_error_handler_muted = $8,\n value = $9,\n schema = $10::text::json,\n edited_by = $11,\n edited_at = now()\n WHERE\n path = $12 AND workspace_id = $13",
"describe": {
"columns": [],
"parameters": {
@@ -12,6 +12,7 @@
"Bool",
"Bool",
"Text",
"Bool",
"Jsonb",
"Text",
"Varchar",
@@ -21,5 +22,5 @@
},
"nullable": []
},
"hash": "207a0721b6f0b8b6ddd4120343eba524a2bc1e9047bdde5f568af4d993dbb74c"
"hash": "77ac7257be02fb04c4b3213e2221e6f60621b4b2909d770de744ef5671e12ed9"
}
+10 -5
View File
@@ -473,12 +473,14 @@ async fn create_flow(
workspace_id, path, summary, description,
dependency_job, lock_error_logs, draft_only, tag,
dedicated_worker, visible_to_runner_only, on_behalf_of_email,
ws_error_handler_muted,
value, schema, edited_by, edited_at
) VALUES (
$1, $2, $3, $4,
NULL, '', $5, $6,
$7, $8, $9,
$10, $11::text::json, $12, now()
$10,
$11, $12::text::json, $13, now()
)"#,
w_id,
nf.path,
@@ -489,6 +491,7 @@ async fn create_flow(
nf.dedicated_worker,
nf.visible_to_runner_only.unwrap_or(false),
nf.on_behalf_of_email.and(Some(&authed.email)),
nf.ws_error_handler_muted.unwrap_or(false),
sqlx::types::Json(&nf.value) as _,
schema_str,
&authed.username,
@@ -897,12 +900,13 @@ async fn update_flow(
dedicated_worker = $5,
visible_to_runner_only = $6,
on_behalf_of_email = $7,
value = $8,
schema = $9::text::json,
edited_by = $10,
ws_error_handler_muted = $8,
value = $9,
schema = $10::text::json,
edited_by = $11,
edited_at = now()
WHERE
path = $11 AND workspace_id = $12",
path = $12 AND workspace_id = $13",
if is_new_path { flow_path } else { &nf.path },
nf.summary,
nf.description.as_deref().unwrap_or(""),
@@ -910,6 +914,7 @@ async fn update_flow(
nf.dedicated_worker,
nf.visible_to_runner_only.unwrap_or(false),
nf.on_behalf_of_email.and(Some(&authed.email)),
nf.ws_error_handler_muted.unwrap_or(false),
sqlx::types::Json(&nf.value) as _,
schema_str,
authed.username,
+211
View File
@@ -906,7 +906,9 @@ Deno.test("readDirRecursive reads all files correctly", async () => {
// Integration Tests (use withTestBackend for automated backend setup)
// =============================================================================
import { yamlParseFile } from "../deps.ts";
import { withTestBackend } from "./test_backend.ts";
import { shouldSkipOnCI } from "./cargo_backend.ts";
Deno.test({
name: "Integration: Pull creates correct local structure",
@@ -1950,3 +1952,212 @@ excludes: []
});
},
});
// =============================================================================
// ws_error_handler_muted Persistence Tests
// =============================================================================
Deno.test({
name: "Integration: Script ws_error_handler_muted is persisted through push/pull",
ignore: shouldSkipOnCI(), // Requires EE features
sanitizeResources: false,
sanitizeOps: false,
async fn() {
await withTestBackend(async (backend, tempDir) => {
await Deno.writeTextFile(
`${tempDir}/wmill.yaml`,
`defaultTs: bun
includes:
- "**"
excludes: []
`,
);
const uniqueId = Date.now();
await ensureDir(`${tempDir}/f/test`);
// Create a script with ws_error_handler_muted: true
const scriptName = `f/test/muted_script_${uniqueId}`;
const script = createScriptFixture(scriptName, "deno");
await Deno.writeTextFile(`${tempDir}/${script.contentFile.path}`, script.contentFile.content);
// Add ws_error_handler_muted to the metadata
const metadataWithMuted = script.metadataFile.content + `ws_error_handler_muted: true\n`;
await Deno.writeTextFile(`${tempDir}/${script.metadataFile.path}`, metadataWithMuted);
// Push
const pushResult = await backend.runCLICommand(
["sync", "push", "--yes", "--includes", `f/test/muted_script_${uniqueId}**`],
tempDir,
);
assertEquals(
pushResult.code,
0,
`Push should succeed.\nstdout: ${pushResult.stdout}\nstderr: ${pushResult.stderr}`,
);
// Verify via API that ws_error_handler_muted was persisted
const apiResp = await backend.apiRequest!(
`/api/w/${backend.workspace}/scripts/get/p/${scriptName}`,
);
assertEquals(apiResp.status, 200, "API should return the script");
const scriptData = await apiResp.json();
assertEquals(
scriptData.ws_error_handler_muted,
true,
"API should return ws_error_handler_muted: true for the pushed script",
);
// Pull into a fresh directory and verify the field round-trips
const pullDir = await Deno.makeTempDir({ prefix: "wmill_muted_script_pull_" });
try {
await Deno.writeTextFile(
`${pullDir}/wmill.yaml`,
`defaultTs: bun
includes:
- "f/test/muted_script_${uniqueId}**"
excludes: []
`,
);
const pullResult = await backend.runCLICommand(["sync", "pull", "--yes"], pullDir);
assertEquals(
pullResult.code,
0,
`Pull should succeed.\nstdout: ${pullResult.stdout}\nstderr: ${pullResult.stderr}`,
);
// Verify ws_error_handler_muted is in the pulled metadata
const pulledMetadata = await Deno.readTextFile(`${pullDir}/${script.metadataFile.path}`);
assertStringIncludes(
pulledMetadata,
"ws_error_handler_muted: true",
"Pulled script metadata should contain ws_error_handler_muted: true",
);
// Verify push from pulled dir is idempotent (no changes)
const push2 = await backend.runCLICommand(
["sync", "push", "--dry-run", "--includes", `f/test/muted_script_${uniqueId}**`],
pullDir,
);
assertEquals(push2.code, 0, `Second push dry-run should succeed: ${push2.stderr}`);
const output = (push2.stdout + push2.stderr).toLowerCase();
assert(
output.includes("0 change") || output.includes("no change") || output.includes("nothing"),
`Should have no changes after push-pull cycle for script with ws_error_handler_muted. Output: ${output}`,
);
} finally {
await Deno.remove(pullDir, { recursive: true }).catch(() => {});
}
});
},
});
Deno.test({
name: "Integration: Flow ws_error_handler_muted is persisted through push/pull",
ignore: shouldSkipOnCI(), // Requires EE features
sanitizeResources: false,
sanitizeOps: false,
async fn() {
await withTestBackend(async (backend, tempDir) => {
await Deno.writeTextFile(
`${tempDir}/wmill.yaml`,
`defaultTs: bun
includes:
- "**"
excludes: []
`,
);
const uniqueId = Date.now();
const flowName = `f/test/muted_flow_${uniqueId}`;
const flowFixture = createFlowFixture(flowName);
// Create flow directory and files
await ensureDir(`${tempDir}/f/test/muted_flow_${uniqueId}${getFolderSuffix("flow")}`);
for (const [key, file] of Object.entries(flowFixture)) {
if (key === "metadata") {
// Add ws_error_handler_muted to flow metadata
const contentWithMuted = file.content + `ws_error_handler_muted: true\n`;
await Deno.writeTextFile(`${tempDir}/${file.path}`, contentWithMuted);
} else {
await Deno.writeTextFile(`${tempDir}/${file.path}`, file.content);
}
}
// Push
const pushResult = await backend.runCLICommand(
["sync", "push", "--yes", "--includes", `f/test/muted_flow_${uniqueId}*/**`],
tempDir,
);
assertEquals(
pushResult.code,
0,
`Push should succeed.\nstdout: ${pushResult.stdout}\nstderr: ${pushResult.stderr}`,
);
// Verify via API that ws_error_handler_muted was persisted
const apiResp = await backend.apiRequest!(
`/api/w/${backend.workspace}/flows/get/${flowName}`,
);
assertEquals(apiResp.status, 200, "API should return the flow");
const flowData = await apiResp.json();
assertEquals(
flowData.ws_error_handler_muted,
true,
"API should return ws_error_handler_muted: true for the pushed flow",
);
// Pull into a fresh directory and verify the field round-trips
const pullDir = await Deno.makeTempDir({ prefix: "wmill_muted_flow_pull_" });
try {
await Deno.writeTextFile(
`${pullDir}/wmill.yaml`,
`defaultTs: bun
includes:
- "f/test/muted_flow_${uniqueId}*/**"
excludes: []
`,
);
const pullResult = await backend.runCLICommand(["sync", "pull", "--yes"], pullDir);
assertEquals(
pullResult.code,
0,
`Pull should succeed.\nstdout: ${pullResult.stdout}\nstderr: ${pullResult.stderr}`,
);
// Verify ws_error_handler_muted is in the pulled flow.yaml
const flowYamlPath = `${pullDir}/${flowFixture.metadata.path}`;
const pulledFlowYaml = await Deno.readTextFile(flowYamlPath);
assertStringIncludes(
pulledFlowYaml,
"ws_error_handler_muted: true",
"Pulled flow.yaml should contain ws_error_handler_muted: true",
);
// Parse the YAML to confirm it's a proper boolean value
// deno-lint-ignore no-explicit-any
const parsed = await yamlParseFile(flowYamlPath) as any;
assertEquals(
parsed.ws_error_handler_muted,
true,
"ws_error_handler_muted should be boolean true in parsed flow YAML",
);
// Verify push from pulled dir is idempotent (no changes)
const push2 = await backend.runCLICommand(
["sync", "push", "--dry-run", "--includes", `f/test/muted_flow_${uniqueId}*/**`],
pullDir,
);
assertEquals(push2.code, 0, `Second push dry-run should succeed: ${push2.stderr}`);
const output = (push2.stdout + push2.stderr).toLowerCase();
assert(
output.includes("0 change") || output.includes("no change") || output.includes("nothing"),
`Should have no changes after push-pull cycle for flow with ws_error_handler_muted. Output: ${output}`,
);
} finally {
await Deno.remove(pullDir, { recursive: true }).catch(() => {});
}
});
},
});