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:
co-authored by
Pallav
Devin AI
Rajiv Sahal
Romit
Romit
Romit
parent
19366700b3
commit
9172fe6b1b
@@ -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"
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user