Limit workflow ID conditions to equality

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
Taylor Ho
2026-08-13 22:07:35 -07:00
parent 21268599d6
commit 2d51c6beef
4 changed files with 59 additions and 6 deletions
@@ -11,8 +11,10 @@ import { FieldLabel, FormSelect } from "./workflowFormPrimitives";
import {
buildConditionExpression,
conditionFieldsForTrigger,
conditionOperatorsForField,
conditionOperatorNeedsValue,
CUSTOM_CONDITION_FIELD,
defaultConditionOperatorForField,
normalizeWebhookField,
parseConditionExpression,
} from "./workflowConditionExpression";
@@ -38,7 +40,15 @@ function initialEditorState(
triggerType: TriggerType,
): ParsedConditionExpression {
const parsed = parseConditionExpression(value, triggerType);
if (parsed) return parsed;
if (parsed) {
const supportedOperators = conditionOperatorsForField(parsed.field);
return supportedOperators.includes(parsed.operator)
? parsed
: {
...parsed,
operator: defaultConditionOperatorForField(parsed.field),
};
}
return {
field: value.trim() ? CUSTOM_CONDITION_FIELD : "",
@@ -154,6 +164,8 @@ export function WorkflowConditionBuilder({
};
const needsValue = conditionOperatorNeedsValue(editor.operator);
const operatorOptions = conditionOperatorsForField(editor.field);
const usesExactMatchOperators = operatorOptions.length === 2;
const webhookFieldInvalid =
editor.field === "webhook_field" &&
editor.webhookField.length > 0 &&
@@ -216,7 +228,7 @@ export function WorkflowConditionBuilder({
}
emitEditor({
field: field.value,
operator: "contains",
operator: defaultConditionOperatorForField(field.value),
value: "",
webhookField: "",
});
@@ -264,9 +276,11 @@ export function WorkflowConditionBuilder({
}
value={editor.operator}
>
{Object.entries(OPERATOR_LABELS).map(([operator, label]) => (
{operatorOptions.map((operator) => (
<option key={operator} value={operator}>
{label}
{usesExactMatchOperators && operator === "equals"
? "is"
: OPERATOR_LABELS[operator]}
</option>
))}
</FormSelect>
@@ -4,6 +4,7 @@ import { test } from "node:test";
import {
buildConditionExpression,
conditionFieldsForTrigger,
conditionOperatorsForField,
normalizeWebhookField,
parseConditionExpression,
} from "./workflowConditionExpression.ts";
@@ -77,6 +78,21 @@ test("shows trigger-relevant fields", () => {
assert.equal(conditionFieldsForTrigger("webhook")[0].value, "webhook_field");
});
test("limits opaque identifiers to equality operators", () => {
for (const field of [
"trigger_author",
"trigger_channel_id",
"trigger_message_id",
"future_resource_id",
]) {
assert.deepEqual(conditionOperatorsForField(field), [
"equals",
"not_equals",
]);
}
assert.equal(conditionOperatorsForField("trigger_text").length, 8);
});
test("parses generated conditions back into editor fields", () => {
assert.deepEqual(
parseConditionExpression(
@@ -13,6 +13,26 @@ export const CONDITION_OPERATORS = [
export type ConditionOperator = (typeof CONDITION_OPERATORS)[number];
const EXACT_MATCH_OPERATORS = [
"equals",
"not_equals",
] as const satisfies readonly ConditionOperator[];
/** IDs and pubkeys are opaque identifiers, so partial string matching is not meaningful. */
export function conditionOperatorsForField(
field: string,
): readonly ConditionOperator[] {
return field === "trigger_author" || field.endsWith("_id")
? EXACT_MATCH_OPERATORS
: CONDITION_OPERATORS;
}
export function defaultConditionOperatorForField(
field: string,
): ConditionOperator {
return conditionOperatorsForField(field)[0];
}
export type ConditionField = {
label: string;
value: string;
+5 -2
View File
@@ -336,6 +336,9 @@ test("chooses a trigger channel condition from the live channel list", async ({
const inspector = dialog.getByTestId("workflow-node-inspector");
await inspector.getByRole("button", { name: "Channel ID" }).click();
const matchOperator = inspector.getByLabel("Match");
await expect(matchOperator).toHaveValue("equals");
await expect(matchOperator.locator("option")).toHaveText(["is", "is not"]);
const channelCondition = inspector.getByRole("combobox", {
name: "Channel ID",
});
@@ -356,7 +359,7 @@ test("chooses a trigger channel condition from the live channel list", async ({
await dialog.getByRole("tab", { name: "YAML" }).click();
await expect(dialog.getByLabel("Workflow YAML")).toHaveValue(
/str_contains\(trigger_channel_id, "[0-9a-f-]{36}"\)/,
/trigger_channel_id == "[0-9a-f-]{36}"/,
);
});
@@ -448,7 +451,7 @@ test("chooses a trigger author condition from live user search", async ({
await dialog.getByRole("tab", { name: "YAML" }).click();
await expect(dialog.getByLabel("Workflow YAML")).toHaveValue(
/str_contains\(trigger_author,\s+"[0-9a-f]{64}"\)/,
/trigger_author\s+==\s+"[0-9a-f]{64}"/,
);
});