mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-19 07:36:52 +00:00
fix: Prompt to save before manual run with autosave disabled (#32513)
Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -4002,6 +4002,10 @@
|
||||
"workflowPreview.executionMode.showError.previewError.message": "Unable to preview workflow execution",
|
||||
"workflowPreview.showError.previewError.title": "Preview error",
|
||||
"workflowRun.noActiveConnectionToTheServer": "Lost connection to the server",
|
||||
"workflowRun.saveBeforeRun.headline": "Save before running?",
|
||||
"workflowRun.saveBeforeRun.message": "You have to save your workflow before running a manual execution.",
|
||||
"workflowRun.saveBeforeRun.confirmButtonText": "Save & run",
|
||||
"workflowRun.saveBeforeRun.cancelButtonText": "Cancel",
|
||||
"workflowRun.showError.deactivate": "Deactivate workflow to execute",
|
||||
"workflowRun.showError.title": "Problem running workflow",
|
||||
"workflowRun.showError.payloadTooLarge": "Please execute the whole workflow, rather than just the node. (Existing execution data is too large.)",
|
||||
|
||||
@@ -215,6 +215,14 @@ vi.mock('@/app/composables/useToast', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/app/composables/useMessage', () => ({
|
||||
useMessage: vi.fn().mockReturnValue({
|
||||
confirm: vi.fn().mockResolvedValue('confirm'),
|
||||
alert: vi.fn(),
|
||||
prompt: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/app/composables/useWorkflowHelpers', () => ({
|
||||
useWorkflowHelpers: vi.fn().mockReturnValue({
|
||||
saveCurrentWorkflow: vi.fn(),
|
||||
@@ -448,7 +456,11 @@ describe('useRunWorkflow({ router })', () => {
|
||||
expect(workflowSaving.saveCurrentWorkflow).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not save before execute when autosave is disabled and state is dirty', async () => {
|
||||
it('should prompt the user and save when they confirm if autosave is disabled and state is dirty (ADO-5328)', async () => {
|
||||
const { useMessage } = await import('@/app/composables/useMessage');
|
||||
const messageMock = vi.mocked(useMessage)();
|
||||
vi.mocked(messageMock.confirm).mockResolvedValueOnce('confirm');
|
||||
|
||||
vi.spyOn(settingsStore, 'isAutosaveEnabled', 'get').mockReturnValue(false);
|
||||
vi.mocked(uiStore).stateIsDirty = true;
|
||||
vi.mocked(workflowsStore).isWorkflowSaved = { '123': true };
|
||||
@@ -457,7 +469,27 @@ describe('useRunWorkflow({ router })', () => {
|
||||
const { runWorkflow } = useRunWorkflow({ router });
|
||||
await runWorkflow({});
|
||||
|
||||
expect(messageMock.confirm).toHaveBeenCalledTimes(1);
|
||||
expect(workflowSaving.saveCurrentWorkflow).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not run when the user cancels the save-before-run prompt (ADO-5328)', async () => {
|
||||
const { useMessage } = await import('@/app/composables/useMessage');
|
||||
const messageMock = vi.mocked(useMessage)();
|
||||
vi.mocked(messageMock.confirm).mockResolvedValueOnce('cancel');
|
||||
|
||||
vi.spyOn(settingsStore, 'isAutosaveEnabled', 'get').mockReturnValue(false);
|
||||
vi.mocked(uiStore).stateIsDirty = true;
|
||||
vi.mocked(workflowsStore).isWorkflowSaved = { '123': true };
|
||||
|
||||
const workflowSaving = useWorkflowSaving({ router });
|
||||
const { runWorkflow } = useRunWorkflow({ router });
|
||||
const result = await runWorkflow({});
|
||||
|
||||
expect(messageMock.confirm).toHaveBeenCalledTimes(1);
|
||||
expect(workflowSaving.saveCurrentWorkflow).not.toHaveBeenCalled();
|
||||
expect(workflowsStore.runWorkflow).not.toHaveBeenCalled();
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should save new workflow before execute even when autosave is disabled', async () => {
|
||||
|
||||
@@ -25,6 +25,7 @@ import { retry } from '@n8n/utils/retry';
|
||||
import { computed, getCurrentInstance, type Ref } from 'vue';
|
||||
|
||||
import { useToast } from '@/app/composables/useToast';
|
||||
import { useMessage } from '@/app/composables/useMessage';
|
||||
import { useNodeHelpers } from '@/app/composables/useNodeHelpers';
|
||||
|
||||
import {
|
||||
@@ -33,6 +34,7 @@ import {
|
||||
CHAT_HITL_TOOL_NODE_TYPE,
|
||||
CHAT_TRIGGER_NODE_TYPE,
|
||||
IN_PROGRESS_EXECUTION_ID,
|
||||
MODAL_CONFIRM,
|
||||
RESPOND_TO_WEBHOOK_NODE_TYPE,
|
||||
} from '@/app/constants';
|
||||
|
||||
@@ -78,6 +80,7 @@ export function useRunWorkflow(useRunWorkflowOpts: {
|
||||
const workflowHelpers = useWorkflowHelpers();
|
||||
const i18n = useI18n();
|
||||
const toast = useToast();
|
||||
const message = useMessage();
|
||||
const telemetry = useTelemetry();
|
||||
const externalHooks = useExternalHooks();
|
||||
const settingsStore = useSettingsStore();
|
||||
@@ -182,7 +185,31 @@ export function useRunWorkflow(useRunWorkflowOpts: {
|
||||
const runData = workflowsStore.getWorkflowRunData;
|
||||
|
||||
const isNewWorkflow = !workflowsStore.isWorkflowSaved[workflowDocumentStore.value.workflowId];
|
||||
if (isNewWorkflow || (uiStore.stateIsDirty && settingsStore.isAutosaveEnabled)) {
|
||||
|
||||
// With N8N_WORKFLOWS_AUTOSAVE_DISABLED=true the editor no longer
|
||||
// force-saves before executing, so canvas-only edits would be
|
||||
// dropped by the executor (it only ever runs the DB copy). Prompt
|
||||
// the user to save first so the run reflects the canvas. ADO-5328.
|
||||
if (!isNewWorkflow && uiStore.stateIsDirty && !settingsStore.isAutosaveEnabled) {
|
||||
const response = await message.confirm(i18n.baseText('workflowRun.saveBeforeRun.message'), {
|
||||
title: i18n.baseText('workflowRun.saveBeforeRun.headline'),
|
||||
type: 'info',
|
||||
confirmButtonText: i18n.baseText('workflowRun.saveBeforeRun.confirmButtonText'),
|
||||
cancelButtonText: i18n.baseText('workflowRun.saveBeforeRun.cancelButtonText'),
|
||||
showClose: true,
|
||||
});
|
||||
|
||||
if (response !== MODAL_CONFIRM) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const saved = await workflowSaving.saveCurrentWorkflow({
|
||||
id: workflowDocumentStore.value.workflowId,
|
||||
});
|
||||
if (!saved) {
|
||||
return undefined;
|
||||
}
|
||||
} else if (isNewWorkflow || (uiStore.stateIsDirty && settingsStore.isAutosaveEnabled)) {
|
||||
await workflowSaving.saveCurrentWorkflow({ id: workflowDocumentStore.value.workflowId });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user