fix: disable save button when workflow has no changes (#25973)

* fix: disable save button when workflow has no changes

* test: wait for save button to be enabled before clicking

* test: ensure input value is set before pressing Enter

* test fix

* fix: add shouldDirty to enable save button on name change

* test: clean up workflow test fixtures

* fix: disable save button when workflow has no changes

* test: update workflow E2E tests

* test: fix workflow E2E

* fix: enable save button when workflow form fields change

* fix tests

* fix: disable save button when workflow has no changes

* remove comments

* chore: improve fixture logic

* cleanup: remove unnecessary changes

* update workflow fixture to handle team workflow creation

---------

Co-authored-by: Pallav <90088723+Pallava-Joshi@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Rajiv Sahal <sahalrajiv-extc@atharvacoe.ac.in>
Co-authored-by: Romit <romitgabani@icloud.com>
Co-authored-by: Romit <romitgabani1.work@gmail.com>
Co-authored-by: Romit <85230081+romitg2@users.noreply.github.com>
This commit is contained in:
Anshuman
2026-02-09 08:55:49 +00:00
committed by GitHub
co-authored by Pallav Devin AI Rajiv Sahal Romit Romit Romit
parent 19366700b3
commit 9172fe6b1b
3 changed files with 42 additions and 26 deletions
@@ -489,7 +489,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
.replace(/ /g, "_")}}${currentEmailSubject.substring(cursorPosition)}`;
form.setValue(
`steps.${step.stepNumber - 1}.emailSubject`,
subjectWithAddedVariable
subjectWithAddedVariable,
{ shouldDirty: true }
);
}
};
@@ -623,15 +624,15 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
isFormTrigger(currentTrigger);
const isNewFormTrigger = isFormTrigger(triggerValue);
form.setValue("trigger", triggerValue);
form.setValue("trigger", triggerValue, { shouldDirty: true });
// Reset activeOn when switching between form and non-form triggers
if (isCurrentFormTrigger !== isNewFormTrigger) {
form.setValue("activeOn", []);
form.setValue("activeOn", [], { shouldDirty: true });
if (setSelectedOptions) {
setSelectedOptions([]);
}
form.setValue("selectAll", false);
form.setValue("selectAll", false, { shouldDirty: true });
}
const newTimeSectionText = getTimeSectionText(
@@ -646,11 +647,11 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
triggerValue ===
WorkflowTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW
) {
form.setValue("time", 5);
form.setValue("timeUnit", TimeUnit.MINUTE);
form.setValue("time", 5, { shouldDirty: true });
form.setValue("timeUnit", TimeUnit.MINUTE, { shouldDirty: true });
} else {
form.setValue("time", 24);
form.setValue("timeUnit", TimeUnit.HOUR);
form.setValue("time", 24, { shouldDirty: true });
form.setValue("timeUnit", TimeUnit.HOUR, { shouldDirty: true });
}
} else {
setTimeSectionText(null);
@@ -670,7 +671,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
template: WorkflowTemplates.CUSTOM,
}
);
form.setValue("steps", updatedSteps);
form.setValue("steps", updatedSteps, { shouldDirty: true });
setUpdateTemplate(!updateTemplate);
}
}
@@ -733,7 +734,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
: selectedOptions
}
setValue={(s: Option[]) => {
form.setValue("activeOn", s);
form.setValue("activeOn", s, { shouldDirty: true });
}}
countText={
isOrganization
@@ -765,7 +766,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
onChange(e);
if (e.target.value) {
setSelectedOptions(allOptions);
form.setValue("activeOn", allOptions);
form.setValue("activeOn", allOptions, { shouldDirty: true });
}
}}
checked={value}
@@ -936,7 +937,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
form.clearErrors(`steps.${step.stepNumber - 1}.sendTo`);
form.setValue(
`steps.${step.stepNumber - 1}.action`,
val.value
val.value,
{ shouldDirty: true }
);
setUpdateTemplate(!updateTemplate);
}
@@ -1348,7 +1350,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
onChange={(e) =>
form.setValue(
`steps.${step.stepNumber - 1}.numberRequired`,
e.target.checked
e.target.checked,
{ shouldDirty: true }
)
}
/>
@@ -1532,7 +1535,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
field.onChange(value);
form.setValue(
`steps.${step.stepNumber - 1}.template`,
value
value,
{ shouldDirty: true }
);
setUpdateTemplate(!updateTemplate);
}
@@ -1628,7 +1632,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
setText={(text: string) => {
props.form.setValue(
`steps.${step.stepNumber - 1}.reminderBody`,
text
text,
{ shouldDirty: true }
);
props.form.clearErrors();
}}
@@ -1678,7 +1683,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
onChange={(e) =>
form.setValue(
`steps.${step.stepNumber - 1}.includeCalendarEvent`,
e.target.checked
e.target.checked,
{ shouldDirty: true }
)
}
/>
@@ -2113,7 +2119,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
}
return updatedStep;
});
form.setValue("steps", updatedSteps);
form.setValue("steps", updatedSteps, { shouldDirty: true });
if (setReload) {
setReload(!reload);
}
@@ -160,13 +160,13 @@ function WorkflowPage({
};
const handleNameSubmit = () => {
form.setValue("name", nameValue);
form.setValue("name", nameValue, { shouldDirty: true });
setIsEditingName(false);
};
const handleNameKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
form.setValue("name", nameValue);
form.setValue("name", nameValue, { shouldDirty: true });
setIsEditingName(false);
} else if (e.key === "Escape") {
setNameValue(watchedName || "");
@@ -291,6 +291,7 @@ function WorkflowPage({
form.setValue("timeUnit", workflowData.timeUnit || undefined);
form.setValue("activeOn", activeOn || []);
form.setValue("selectAll", workflowData.isActiveOnAll ?? false);
form.reset(form.getValues());
setNameValue(workflowData.name);
setIsAllDataLoaded(true);
}
@@ -300,6 +301,7 @@ function WorkflowPage({
onSuccess: async ({ workflow }) => {
utils.viewer.workflows.get.setData({ id: +workflow.id }, workflow);
setFormData(workflow);
form.reset(form.getValues());
const autoCreateAgent = searchParams?.get("autoCreateAgent");
if (!autoCreateAgent) {
@@ -319,6 +321,8 @@ function WorkflowPage({
},
});
const isDisabled = permissions.readOnly || updateMutation.isPending || !form.formState.isDirty;
const validateAndSubmitWorkflow = async (values: FormValues): Promise<void> => {
let isEmpty = false;
let isVerified = true;
@@ -509,7 +513,7 @@ function WorkflowPage({
</Tooltip>
<Button
loading={updateMutation.isPending}
disabled={permissions.readOnly || updateMutation.isPending}
disabled={isDisabled}
data-testid="save-workflow"
type="submit"
size="sm"
+12 -6
View File
@@ -31,11 +31,16 @@ export function createWorkflowPageFixture(page: Page) {
if (name) {
await fillNameInput(name);
}
if (trigger) {
await page.locator("#trigger-select").click();
await page.getByTestId(`select-option-${trigger ?? WorkflowTriggerEvents.BEFORE_EVENT}`).click();
await page.locator("#trigger-select").click();
await page.getByTestId(`select-option-${trigger ?? WorkflowTriggerEvents.BEFORE_EVENT}`).click();
if (isTeam) {
await page.getByText(/Apply to all team/i).first().click();
} else {
await selectEventType("30 min");
}
const workflow = await saveWorkflow();
for (const step of workflow.steps) {
@@ -50,7 +55,7 @@ export function createWorkflowPageFixture(page: Page) {
const saveWorkflow = async () => {
const submitPromise = page.waitForResponse("/api/trpc/workflows/update?batch=1");
const saveButton = await page.getByTestId("save-workflow");
const saveButton = page.getByTestId("save-workflow");
await saveButton.click();
const response = await submitPromise;
expect(response.status()).toBe(200);
@@ -67,8 +72,9 @@ export function createWorkflowPageFixture(page: Page) {
const fillNameInput = async (name: string) => {
await page.getByTestId("edit-workflow-name-button").click();
await page.getByTestId("workflow-name").fill(name);
await page.keyboard.press("Enter");
const nameInput = page.getByTestId("workflow-name");
await nameInput.fill(name);
await nameInput.blur();
};
const editSelectedWorkflow = async (name: string) => {