Skip to content

Commit 9172fe6

Browse files
Anshumancanrockpallava-joshidevin-ai-integration[bot]Ryukemeisterromitg2
authored
fix: disable save button when workflow has no changes (calcom#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>
1 parent 1936670 commit 9172fe6

3 files changed

Lines changed: 42 additions & 26 deletions

File tree

apps/web/modules/ee/workflows/components/WorkflowStepContainer.tsx

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
489489
.replace(/ /g, "_")}}${currentEmailSubject.substring(cursorPosition)}`;
490490
form.setValue(
491491
`steps.${step.stepNumber - 1}.emailSubject`,
492-
subjectWithAddedVariable
492+
subjectWithAddedVariable,
493+
{ shouldDirty: true }
493494
);
494495
}
495496
};
@@ -623,15 +624,15 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
623624
isFormTrigger(currentTrigger);
624625
const isNewFormTrigger = isFormTrigger(triggerValue);
625626

626-
form.setValue("trigger", triggerValue);
627+
form.setValue("trigger", triggerValue, { shouldDirty: true });
627628

628629
// Reset activeOn when switching between form and non-form triggers
629630
if (isCurrentFormTrigger !== isNewFormTrigger) {
630-
form.setValue("activeOn", []);
631+
form.setValue("activeOn", [], { shouldDirty: true });
631632
if (setSelectedOptions) {
632633
setSelectedOptions([]);
633634
}
634-
form.setValue("selectAll", false);
635+
form.setValue("selectAll", false, { shouldDirty: true });
635636
}
636637

637638
const newTimeSectionText = getTimeSectionText(
@@ -646,11 +647,11 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
646647
triggerValue ===
647648
WorkflowTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW
648649
) {
649-
form.setValue("time", 5);
650-
form.setValue("timeUnit", TimeUnit.MINUTE);
650+
form.setValue("time", 5, { shouldDirty: true });
651+
form.setValue("timeUnit", TimeUnit.MINUTE, { shouldDirty: true });
651652
} else {
652-
form.setValue("time", 24);
653-
form.setValue("timeUnit", TimeUnit.HOUR);
653+
form.setValue("time", 24, { shouldDirty: true });
654+
form.setValue("timeUnit", TimeUnit.HOUR, { shouldDirty: true });
654655
}
655656
} else {
656657
setTimeSectionText(null);
@@ -670,7 +671,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
670671
template: WorkflowTemplates.CUSTOM,
671672
}
672673
);
673-
form.setValue("steps", updatedSteps);
674+
form.setValue("steps", updatedSteps, { shouldDirty: true });
674675
setUpdateTemplate(!updateTemplate);
675676
}
676677
}
@@ -733,7 +734,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
733734
: selectedOptions
734735
}
735736
setValue={(s: Option[]) => {
736-
form.setValue("activeOn", s);
737+
form.setValue("activeOn", s, { shouldDirty: true });
737738
}}
738739
countText={
739740
isOrganization
@@ -765,7 +766,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
765766
onChange(e);
766767
if (e.target.value) {
767768
setSelectedOptions(allOptions);
768-
form.setValue("activeOn", allOptions);
769+
form.setValue("activeOn", allOptions, { shouldDirty: true });
769770
}
770771
}}
771772
checked={value}
@@ -936,7 +937,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
936937
form.clearErrors(`steps.${step.stepNumber - 1}.sendTo`);
937938
form.setValue(
938939
`steps.${step.stepNumber - 1}.action`,
939-
val.value
940+
val.value,
941+
{ shouldDirty: true }
940942
);
941943
setUpdateTemplate(!updateTemplate);
942944
}
@@ -1348,7 +1350,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
13481350
onChange={(e) =>
13491351
form.setValue(
13501352
`steps.${step.stepNumber - 1}.numberRequired`,
1351-
e.target.checked
1353+
e.target.checked,
1354+
{ shouldDirty: true }
13521355
)
13531356
}
13541357
/>
@@ -1532,7 +1535,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
15321535
field.onChange(value);
15331536
form.setValue(
15341537
`steps.${step.stepNumber - 1}.template`,
1535-
value
1538+
value,
1539+
{ shouldDirty: true }
15361540
);
15371541
setUpdateTemplate(!updateTemplate);
15381542
}
@@ -1628,7 +1632,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
16281632
setText={(text: string) => {
16291633
props.form.setValue(
16301634
`steps.${step.stepNumber - 1}.reminderBody`,
1631-
text
1635+
text,
1636+
{ shouldDirty: true }
16321637
);
16331638
props.form.clearErrors();
16341639
}}
@@ -1678,7 +1683,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
16781683
onChange={(e) =>
16791684
form.setValue(
16801685
`steps.${step.stepNumber - 1}.includeCalendarEvent`,
1681-
e.target.checked
1686+
e.target.checked,
1687+
{ shouldDirty: true }
16821688
)
16831689
}
16841690
/>
@@ -2113,7 +2119,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
21132119
}
21142120
return updatedStep;
21152121
});
2116-
form.setValue("steps", updatedSteps);
2122+
form.setValue("steps", updatedSteps, { shouldDirty: true });
21172123
if (setReload) {
21182124
setReload(!reload);
21192125
}

apps/web/modules/ee/workflows/views/WorkflowPage.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,13 @@ function WorkflowPage({
160160
};
161161

162162
const handleNameSubmit = () => {
163-
form.setValue("name", nameValue);
163+
form.setValue("name", nameValue, { shouldDirty: true });
164164
setIsEditingName(false);
165165
};
166166

167167
const handleNameKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
168168
if (e.key === "Enter") {
169-
form.setValue("name", nameValue);
169+
form.setValue("name", nameValue, { shouldDirty: true });
170170
setIsEditingName(false);
171171
} else if (e.key === "Escape") {
172172
setNameValue(watchedName || "");
@@ -291,6 +291,7 @@ function WorkflowPage({
291291
form.setValue("timeUnit", workflowData.timeUnit || undefined);
292292
form.setValue("activeOn", activeOn || []);
293293
form.setValue("selectAll", workflowData.isActiveOnAll ?? false);
294+
form.reset(form.getValues());
294295
setNameValue(workflowData.name);
295296
setIsAllDataLoaded(true);
296297
}
@@ -300,6 +301,7 @@ function WorkflowPage({
300301
onSuccess: async ({ workflow }) => {
301302
utils.viewer.workflows.get.setData({ id: +workflow.id }, workflow);
302303
setFormData(workflow);
304+
form.reset(form.getValues());
303305

304306
const autoCreateAgent = searchParams?.get("autoCreateAgent");
305307
if (!autoCreateAgent) {
@@ -319,6 +321,8 @@ function WorkflowPage({
319321
},
320322
});
321323

324+
const isDisabled = permissions.readOnly || updateMutation.isPending || !form.formState.isDirty;
325+
322326
const validateAndSubmitWorkflow = async (values: FormValues): Promise<void> => {
323327
let isEmpty = false;
324328
let isVerified = true;
@@ -509,7 +513,7 @@ function WorkflowPage({
509513
</Tooltip>
510514
<Button
511515
loading={updateMutation.isPending}
512-
disabled={permissions.readOnly || updateMutation.isPending}
516+
disabled={isDisabled}
513517
data-testid="save-workflow"
514518
type="submit"
515519
size="sm"

apps/web/playwright/fixtures/workflows.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,16 @@ export function createWorkflowPageFixture(page: Page) {
3131
if (name) {
3232
await fillNameInput(name);
3333
}
34-
if (trigger) {
35-
await page.locator("#trigger-select").click();
36-
await page.getByTestId(`select-option-${trigger ?? WorkflowTriggerEvents.BEFORE_EVENT}`).click();
34+
35+
await page.locator("#trigger-select").click();
36+
await page.getByTestId(`select-option-${trigger ?? WorkflowTriggerEvents.BEFORE_EVENT}`).click();
37+
38+
if (isTeam) {
39+
await page.getByText(/Apply to all team/i).first().click();
40+
} else {
3741
await selectEventType("30 min");
3842
}
43+
3944
const workflow = await saveWorkflow();
4045

4146
for (const step of workflow.steps) {
@@ -50,7 +55,7 @@ export function createWorkflowPageFixture(page: Page) {
5055

5156
const saveWorkflow = async () => {
5257
const submitPromise = page.waitForResponse("/api/trpc/workflows/update?batch=1");
53-
const saveButton = await page.getByTestId("save-workflow");
58+
const saveButton = page.getByTestId("save-workflow");
5459
await saveButton.click();
5560
const response = await submitPromise;
5661
expect(response.status()).toBe(200);
@@ -67,8 +72,9 @@ export function createWorkflowPageFixture(page: Page) {
6772

6873
const fillNameInput = async (name: string) => {
6974
await page.getByTestId("edit-workflow-name-button").click();
70-
await page.getByTestId("workflow-name").fill(name);
71-
await page.keyboard.press("Enter");
75+
const nameInput = page.getByTestId("workflow-name");
76+
await nameInput.fill(name);
77+
await nameInput.blur();
7278
};
7379

7480
const editSelectedWorkflow = async (name: string) => {

0 commit comments

Comments
 (0)