mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(editor): Add "time saved per execution" workflow setting (#13369)
This commit is contained in:
@@ -1,55 +1,61 @@
|
|||||||
import { createPinia, setActivePinia } from 'pinia';
|
import { nextTick, reactive } from 'vue';
|
||||||
import WorkflowSettingsVue from '@/components/WorkflowSettings.vue';
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
|
||||||
import { setupServer } from '@/__tests__/server';
|
|
||||||
import type { MockInstance } from 'vitest';
|
import type { MockInstance } from 'vitest';
|
||||||
import { afterAll, beforeAll } from 'vitest';
|
import { within, waitFor } from '@testing-library/vue';
|
||||||
import { within } from '@testing-library/vue';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import type { FrontendSettings } from '@n8n/api-types';
|
||||||
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
|
import { getDropdownItems, mockedStore, type MockedStore } from '@/__tests__/utils';
|
||||||
|
import { EnterpriseEditionFeature } from '@/constants';
|
||||||
|
import WorkflowSettingsVue from '@/components/WorkflowSettings.vue';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||||
|
|
||||||
import { createComponentRenderer } from '@/__tests__/render';
|
vi.mock('vue-router', async () => ({
|
||||||
import { cleanupAppModals, createAppModals, getDropdownItems } from '@/__tests__/utils';
|
useRouter: vi.fn(),
|
||||||
import { EnterpriseEditionFeature, WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants';
|
useRoute: () =>
|
||||||
|
reactive({
|
||||||
|
params: {
|
||||||
|
name: '1',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
RouterLink: {
|
||||||
|
template: '<a><slot /></a>',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
import { nextTick } from 'vue';
|
let workflowsStore: MockedStore<typeof useWorkflowsStore>;
|
||||||
import type { IWorkflowDb } from '@/Interface';
|
let settingsStore: MockedStore<typeof useSettingsStore>;
|
||||||
import * as permissions from '@/permissions';
|
let sourceControlStore: MockedStore<typeof useSourceControlStore>;
|
||||||
import type { PermissionsRecord } from '@/permissions';
|
let pinia: ReturnType<typeof createTestingPinia>;
|
||||||
|
|
||||||
let pinia: ReturnType<typeof createPinia>;
|
|
||||||
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
|
|
||||||
let settingsStore: ReturnType<typeof useSettingsStore>;
|
|
||||||
let uiStore: ReturnType<typeof useUIStore>;
|
|
||||||
|
|
||||||
let fetchAllWorkflowsSpy: MockInstance<(typeof workflowsStore)['fetchAllWorkflows']>;
|
let fetchAllWorkflowsSpy: MockInstance<(typeof workflowsStore)['fetchAllWorkflows']>;
|
||||||
|
|
||||||
const createComponent = createComponentRenderer(WorkflowSettingsVue);
|
const createComponent = createComponentRenderer(WorkflowSettingsVue, {
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
Modal: {
|
||||||
|
template:
|
||||||
|
'<div role="dialog"><slot name="header" /><slot name="content" /><slot name="footer" /></div>',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
describe('WorkflowSettingsVue', () => {
|
describe('WorkflowSettingsVue', () => {
|
||||||
let server: ReturnType<typeof setupServer>;
|
|
||||||
beforeAll(() => {
|
|
||||||
server = setupServer();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
pinia = createPinia();
|
pinia = createTestingPinia();
|
||||||
setActivePinia(pinia);
|
workflowsStore = mockedStore(useWorkflowsStore);
|
||||||
|
settingsStore = mockedStore(useSettingsStore);
|
||||||
|
sourceControlStore = mockedStore(useSourceControlStore);
|
||||||
|
|
||||||
createAppModals();
|
settingsStore.settings = {
|
||||||
|
enterprise: {},
|
||||||
workflowsStore = useWorkflowsStore();
|
} as FrontendSettings;
|
||||||
settingsStore = useSettingsStore();
|
workflowsStore.workflowName = 'Test Workflow';
|
||||||
uiStore = useUIStore();
|
workflowsStore.workflowId = '1';
|
||||||
|
fetchAllWorkflowsSpy = workflowsStore.fetchAllWorkflows.mockResolvedValue([
|
||||||
await settingsStore.getSettings();
|
|
||||||
|
|
||||||
vi.spyOn(workflowsStore, 'workflowName', 'get').mockReturnValue('Test Workflow');
|
|
||||||
vi.spyOn(workflowsStore, 'workflowId', 'get').mockReturnValue('1');
|
|
||||||
fetchAllWorkflowsSpy = vi.spyOn(workflowsStore, 'fetchAllWorkflows').mockResolvedValue([
|
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
name: 'Test Workflow',
|
name: 'Test Workflow',
|
||||||
@@ -61,7 +67,7 @@ describe('WorkflowSettingsVue', () => {
|
|||||||
versionId: '123',
|
versionId: '123',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
vi.spyOn(workflowsStore, 'getWorkflowById').mockReturnValue({
|
workflowsStore.getWorkflowById.mockImplementation(() => ({
|
||||||
id: '1',
|
id: '1',
|
||||||
name: 'Test Workflow',
|
name: 'Test Workflow',
|
||||||
active: true,
|
active: true,
|
||||||
@@ -70,24 +76,12 @@ describe('WorkflowSettingsVue', () => {
|
|||||||
createdAt: 1,
|
createdAt: 1,
|
||||||
updatedAt: 1,
|
updatedAt: 1,
|
||||||
versionId: '123',
|
versionId: '123',
|
||||||
} as IWorkflowDb);
|
scopes: ['workflow:update'],
|
||||||
vi.spyOn(permissions, 'getResourcePermissions').mockReturnValue({
|
}));
|
||||||
workflow: {
|
|
||||||
update: true,
|
|
||||||
},
|
|
||||||
} as PermissionsRecord);
|
|
||||||
|
|
||||||
uiStore.modalsById[WORKFLOW_SETTINGS_MODAL_KEY] = {
|
|
||||||
open: true,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
cleanupAppModals();
|
vi.clearAllMocks();
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
server.shutdown();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render correctly', async () => {
|
it('should render correctly', async () => {
|
||||||
@@ -220,4 +214,79 @@ describe('WorkflowSettingsVue', () => {
|
|||||||
expect(dropdownItems[0]).toHaveTextContent(optionText);
|
expect(dropdownItems[0]).toHaveTextContent(optionText);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it('should save time saved per execution correctly', async () => {
|
||||||
|
const { getByTestId, getByRole } = createComponent({ pinia });
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
const timeSavedPerExecutionInput = getByTestId('workflow-settings-time-saved-per-execution');
|
||||||
|
|
||||||
|
expect(timeSavedPerExecutionInput).toBeVisible();
|
||||||
|
|
||||||
|
await userEvent.type(timeSavedPerExecutionInput as Element, '10');
|
||||||
|
expect(timeSavedPerExecutionInput).toHaveValue(10);
|
||||||
|
|
||||||
|
await userEvent.click(getByRole('button', { name: 'Save' }));
|
||||||
|
expect(workflowsStore.updateWorkflow).toHaveBeenCalledWith(
|
||||||
|
expect.any(String),
|
||||||
|
expect.objectContaining({ settings: expect.objectContaining({ timeSavedPerExecution: 10 }) }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove time saved per execution setting', async () => {
|
||||||
|
workflowsStore.workflowSettings.timeSavedPerExecution = 10;
|
||||||
|
|
||||||
|
const { getByTestId, getByRole } = createComponent({ pinia });
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
const timeSavedPerExecutionInput = getByTestId('workflow-settings-time-saved-per-execution');
|
||||||
|
|
||||||
|
expect(timeSavedPerExecutionInput).toBeVisible();
|
||||||
|
await waitFor(() => expect(timeSavedPerExecutionInput).toHaveValue(10));
|
||||||
|
|
||||||
|
await userEvent.clear(timeSavedPerExecutionInput as Element);
|
||||||
|
expect(timeSavedPerExecutionInput).not.toHaveValue();
|
||||||
|
|
||||||
|
await userEvent.click(getByRole('button', { name: 'Save' }));
|
||||||
|
expect(workflowsStore.updateWorkflow).toHaveBeenCalledWith(
|
||||||
|
expect.any(String),
|
||||||
|
expect.objectContaining({
|
||||||
|
settings: expect.not.objectContaining({ timeSavedPerExecution: 10 }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable save time saved per execution if env is read-only', async () => {
|
||||||
|
sourceControlStore.preferences.branchReadOnly = true;
|
||||||
|
|
||||||
|
const { getByTestId } = createComponent({ pinia });
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
const timeSavedPerExecutionInput = getByTestId('workflow-settings-time-saved-per-execution');
|
||||||
|
|
||||||
|
expect(timeSavedPerExecutionInput).toBeVisible();
|
||||||
|
expect(timeSavedPerExecutionInput).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable save time saved per execution if user has no permission to update workflow', async () => {
|
||||||
|
workflowsStore.getWorkflowById.mockImplementation(() => ({
|
||||||
|
id: '1',
|
||||||
|
name: 'Test Workflow',
|
||||||
|
active: true,
|
||||||
|
nodes: [],
|
||||||
|
connections: {},
|
||||||
|
createdAt: 1,
|
||||||
|
updatedAt: 1,
|
||||||
|
versionId: '123',
|
||||||
|
scopes: ['workflow:read'],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { getByTestId } = createComponent({ pinia });
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
const timeSavedPerExecutionInput = getByTestId('workflow-settings-time-saved-per-execution');
|
||||||
|
|
||||||
|
expect(timeSavedPerExecutionInput).toBeVisible();
|
||||||
|
expect(timeSavedPerExecutionInput).toBeDisabled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -378,6 +378,11 @@ const toggleTimeout = () => {
|
|||||||
workflowSettings.value.executionTimeout = workflowSettings.value.executionTimeout === -1 ? 0 : -1;
|
workflowSettings.value.executionTimeout = workflowSettings.value.executionTimeout === -1 ? 0 : -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateTimeSavedPerExecution = (value: string) => {
|
||||||
|
const numValue = parseInt(value, 10);
|
||||||
|
workflowSettings.value.timeSavedPerExecution = isNaN(numValue) ? undefined : numValue;
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
executionTimeout.value = rootStore.executionTimeout;
|
executionTimeout.value = rootStore.executionTimeout;
|
||||||
maxExecutionTimeout.value = rootStore.maxExecutionTimeout;
|
maxExecutionTimeout.value = rootStore.maxExecutionTimeout;
|
||||||
@@ -484,7 +489,7 @@ onMounted(async () => {
|
|||||||
{{ i18n.baseText('workflowSettings.executionOrder') + ':' }}
|
{{ i18n.baseText('workflowSettings.executionOrder') + ':' }}
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="14" class="ignore-key-press-canvas">
|
<el-col :span="14" class="ignore-key-press-canvas">
|
||||||
<n8n-select
|
<N8nSelect
|
||||||
v-model="workflowSettings.executionOrder"
|
v-model="workflowSettings.executionOrder"
|
||||||
placeholder="Select Execution Order"
|
placeholder="Select Execution Order"
|
||||||
size="medium"
|
size="medium"
|
||||||
@@ -493,29 +498,29 @@ onMounted(async () => {
|
|||||||
:limit-popper-width="true"
|
:limit-popper-width="true"
|
||||||
data-test-id="workflow-settings-execution-order"
|
data-test-id="workflow-settings-execution-order"
|
||||||
>
|
>
|
||||||
<n8n-option
|
<N8nOption
|
||||||
v-for="option in executionOrderOptions"
|
v-for="option in executionOrderOptions"
|
||||||
:key="option.key"
|
:key="option.key"
|
||||||
:label="option.value"
|
:label="option.value"
|
||||||
:value="option.key"
|
:value="option.key"
|
||||||
>
|
>
|
||||||
</n8n-option>
|
</N8nOption>
|
||||||
</n8n-select>
|
</N8nSelect>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row data-test-id="error-workflow">
|
<el-row data-test-id="error-workflow">
|
||||||
<el-col :span="10" class="setting-name">
|
<el-col :span="10" class="setting-name">
|
||||||
{{ i18n.baseText('workflowSettings.errorWorkflow') + ':' }}
|
{{ i18n.baseText('workflowSettings.errorWorkflow') + ':' }}
|
||||||
<n8n-tooltip placement="top">
|
<N8nTooltip placement="top">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div v-n8n-html="helpTexts.errorWorkflow"></div>
|
<div v-n8n-html="helpTexts.errorWorkflow"></div>
|
||||||
</template>
|
</template>
|
||||||
<font-awesome-icon icon="question-circle" />
|
<font-awesome-icon icon="question-circle" />
|
||||||
</n8n-tooltip>
|
</N8nTooltip>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="14" class="ignore-key-press-canvas">
|
<el-col :span="14" class="ignore-key-press-canvas">
|
||||||
<n8n-select
|
<N8nSelect
|
||||||
v-model="workflowSettings.errorWorkflow"
|
v-model="workflowSettings.errorWorkflow"
|
||||||
placeholder="Select Workflow"
|
placeholder="Select Workflow"
|
||||||
filterable
|
filterable
|
||||||
@@ -523,58 +528,58 @@ onMounted(async () => {
|
|||||||
:limit-popper-width="true"
|
:limit-popper-width="true"
|
||||||
data-test-id="workflow-settings-error-workflow"
|
data-test-id="workflow-settings-error-workflow"
|
||||||
>
|
>
|
||||||
<n8n-option
|
<N8nOption
|
||||||
v-for="item in workflows"
|
v-for="item in workflows"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:label="item.name"
|
:label="item.name"
|
||||||
:value="item.id"
|
:value="item.id"
|
||||||
>
|
>
|
||||||
</n8n-option>
|
</N8nOption>
|
||||||
</n8n-select>
|
</N8nSelect>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<div v-if="isSharingEnabled" data-test-id="workflow-caller-policy">
|
<div v-if="isSharingEnabled" data-test-id="workflow-caller-policy">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10" class="setting-name">
|
<el-col :span="10" class="setting-name">
|
||||||
{{ i18n.baseText('workflowSettings.callerPolicy') + ':' }}
|
{{ i18n.baseText('workflowSettings.callerPolicy') + ':' }}
|
||||||
<n8n-tooltip placement="top">
|
<N8nTooltip placement="top">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div v-text="helpTexts.workflowCallerPolicy"></div>
|
<div v-text="helpTexts.workflowCallerPolicy"></div>
|
||||||
</template>
|
</template>
|
||||||
<font-awesome-icon icon="question-circle" />
|
<font-awesome-icon icon="question-circle" />
|
||||||
</n8n-tooltip>
|
</N8nTooltip>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :span="14" class="ignore-key-press-canvas">
|
<el-col :span="14" class="ignore-key-press-canvas">
|
||||||
<n8n-select
|
<N8nSelect
|
||||||
v-model="workflowSettings.callerPolicy"
|
v-model="workflowSettings.callerPolicy"
|
||||||
:disabled="readOnlyEnv || !workflowPermissions.update"
|
:disabled="readOnlyEnv || !workflowPermissions.update"
|
||||||
:placeholder="i18n.baseText('workflowSettings.selectOption')"
|
:placeholder="i18n.baseText('workflowSettings.selectOption')"
|
||||||
filterable
|
filterable
|
||||||
:limit-popper-width="true"
|
:limit-popper-width="true"
|
||||||
>
|
>
|
||||||
<n8n-option
|
<N8nOption
|
||||||
v-for="option of workflowCallerPolicyOptions"
|
v-for="option of workflowCallerPolicyOptions"
|
||||||
:key="option.key"
|
:key="option.key"
|
||||||
:label="option.value"
|
:label="option.value"
|
||||||
:value="option.key"
|
:value="option.key"
|
||||||
>
|
>
|
||||||
</n8n-option>
|
</N8nOption>
|
||||||
</n8n-select>
|
</N8nSelect>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row v-if="workflowSettings.callerPolicy === 'workflowsFromAList'">
|
<el-row v-if="workflowSettings.callerPolicy === 'workflowsFromAList'">
|
||||||
<el-col :span="10" class="setting-name">
|
<el-col :span="10" class="setting-name">
|
||||||
{{ i18n.baseText('workflowSettings.callerIds') + ':' }}
|
{{ i18n.baseText('workflowSettings.callerIds') + ':' }}
|
||||||
<n8n-tooltip placement="top">
|
<N8nTooltip placement="top">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div v-text="helpTexts.workflowCallerIds"></div>
|
<div v-text="helpTexts.workflowCallerIds"></div>
|
||||||
</template>
|
</template>
|
||||||
<font-awesome-icon icon="question-circle" />
|
<font-awesome-icon icon="question-circle" />
|
||||||
</n8n-tooltip>
|
</N8nTooltip>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="14">
|
<el-col :span="14">
|
||||||
<n8n-input
|
<N8nInput
|
||||||
v-model="workflowSettings.callerIds"
|
v-model="workflowSettings.callerIds"
|
||||||
:disabled="readOnlyEnv || !workflowPermissions.update"
|
:disabled="readOnlyEnv || !workflowPermissions.update"
|
||||||
:placeholder="i18n.baseText('workflowSettings.callerIds.placeholder')"
|
:placeholder="i18n.baseText('workflowSettings.callerIds.placeholder')"
|
||||||
@@ -588,15 +593,15 @@ onMounted(async () => {
|
|||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10" class="setting-name">
|
<el-col :span="10" class="setting-name">
|
||||||
{{ i18n.baseText('workflowSettings.timezone') + ':' }}
|
{{ i18n.baseText('workflowSettings.timezone') + ':' }}
|
||||||
<n8n-tooltip placement="top">
|
<N8nTooltip placement="top">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div v-text="helpTexts.timezone"></div>
|
<div v-text="helpTexts.timezone"></div>
|
||||||
</template>
|
</template>
|
||||||
<font-awesome-icon icon="question-circle" />
|
<font-awesome-icon icon="question-circle" />
|
||||||
</n8n-tooltip>
|
</N8nTooltip>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="14" class="ignore-key-press-canvas">
|
<el-col :span="14" class="ignore-key-press-canvas">
|
||||||
<n8n-select
|
<N8nSelect
|
||||||
v-model="workflowSettings.timezone"
|
v-model="workflowSettings.timezone"
|
||||||
placeholder="Select Timezone"
|
placeholder="Select Timezone"
|
||||||
filterable
|
filterable
|
||||||
@@ -604,28 +609,28 @@ onMounted(async () => {
|
|||||||
:limit-popper-width="true"
|
:limit-popper-width="true"
|
||||||
data-test-id="workflow-settings-timezone"
|
data-test-id="workflow-settings-timezone"
|
||||||
>
|
>
|
||||||
<n8n-option
|
<N8nOption
|
||||||
v-for="timezone of timezones"
|
v-for="timezone of timezones"
|
||||||
:key="timezone.key"
|
:key="timezone.key"
|
||||||
:label="timezone.value"
|
:label="timezone.value"
|
||||||
:value="timezone.key"
|
:value="timezone.key"
|
||||||
>
|
>
|
||||||
</n8n-option>
|
</N8nOption>
|
||||||
</n8n-select>
|
</N8nSelect>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10" class="setting-name">
|
<el-col :span="10" class="setting-name">
|
||||||
{{ i18n.baseText('workflowSettings.saveDataErrorExecution') + ':' }}
|
{{ i18n.baseText('workflowSettings.saveDataErrorExecution') + ':' }}
|
||||||
<n8n-tooltip placement="top">
|
<N8nTooltip placement="top">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div v-text="helpTexts.saveDataErrorExecution"></div>
|
<div v-text="helpTexts.saveDataErrorExecution"></div>
|
||||||
</template>
|
</template>
|
||||||
<font-awesome-icon icon="question-circle" />
|
<font-awesome-icon icon="question-circle" />
|
||||||
</n8n-tooltip>
|
</N8nTooltip>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="14" class="ignore-key-press-canvas">
|
<el-col :span="14" class="ignore-key-press-canvas">
|
||||||
<n8n-select
|
<N8nSelect
|
||||||
v-model="workflowSettings.saveDataErrorExecution"
|
v-model="workflowSettings.saveDataErrorExecution"
|
||||||
:placeholder="i18n.baseText('workflowSettings.selectOption')"
|
:placeholder="i18n.baseText('workflowSettings.selectOption')"
|
||||||
filterable
|
filterable
|
||||||
@@ -633,28 +638,28 @@ onMounted(async () => {
|
|||||||
:limit-popper-width="true"
|
:limit-popper-width="true"
|
||||||
data-test-id="workflow-settings-save-failed-executions"
|
data-test-id="workflow-settings-save-failed-executions"
|
||||||
>
|
>
|
||||||
<n8n-option
|
<N8nOption
|
||||||
v-for="option of saveDataErrorExecutionOptions"
|
v-for="option of saveDataErrorExecutionOptions"
|
||||||
:key="option.key"
|
:key="option.key"
|
||||||
:label="option.value"
|
:label="option.value"
|
||||||
:value="option.key"
|
:value="option.key"
|
||||||
>
|
>
|
||||||
</n8n-option>
|
</N8nOption>
|
||||||
</n8n-select>
|
</N8nSelect>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10" class="setting-name">
|
<el-col :span="10" class="setting-name">
|
||||||
{{ i18n.baseText('workflowSettings.saveDataSuccessExecution') + ':' }}
|
{{ i18n.baseText('workflowSettings.saveDataSuccessExecution') + ':' }}
|
||||||
<n8n-tooltip placement="top">
|
<N8nTooltip placement="top">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div v-text="helpTexts.saveDataSuccessExecution"></div>
|
<div v-text="helpTexts.saveDataSuccessExecution"></div>
|
||||||
</template>
|
</template>
|
||||||
<font-awesome-icon icon="question-circle" />
|
<font-awesome-icon icon="question-circle" />
|
||||||
</n8n-tooltip>
|
</N8nTooltip>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="14" class="ignore-key-press-canvas">
|
<el-col :span="14" class="ignore-key-press-canvas">
|
||||||
<n8n-select
|
<N8nSelect
|
||||||
v-model="workflowSettings.saveDataSuccessExecution"
|
v-model="workflowSettings.saveDataSuccessExecution"
|
||||||
:placeholder="i18n.baseText('workflowSettings.selectOption')"
|
:placeholder="i18n.baseText('workflowSettings.selectOption')"
|
||||||
filterable
|
filterable
|
||||||
@@ -662,28 +667,28 @@ onMounted(async () => {
|
|||||||
:limit-popper-width="true"
|
:limit-popper-width="true"
|
||||||
data-test-id="workflow-settings-save-success-executions"
|
data-test-id="workflow-settings-save-success-executions"
|
||||||
>
|
>
|
||||||
<n8n-option
|
<N8nOption
|
||||||
v-for="option of saveDataSuccessExecutionOptions"
|
v-for="option of saveDataSuccessExecutionOptions"
|
||||||
:key="option.key"
|
:key="option.key"
|
||||||
:label="option.value"
|
:label="option.value"
|
||||||
:value="option.key"
|
:value="option.key"
|
||||||
>
|
>
|
||||||
</n8n-option>
|
</N8nOption>
|
||||||
</n8n-select>
|
</N8nSelect>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10" class="setting-name">
|
<el-col :span="10" class="setting-name">
|
||||||
{{ i18n.baseText('workflowSettings.saveManualExecutions') + ':' }}
|
{{ i18n.baseText('workflowSettings.saveManualExecutions') + ':' }}
|
||||||
<n8n-tooltip placement="top">
|
<N8nTooltip placement="top">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div v-text="helpTexts.saveManualExecutions"></div>
|
<div v-text="helpTexts.saveManualExecutions"></div>
|
||||||
</template>
|
</template>
|
||||||
<font-awesome-icon icon="question-circle" />
|
<font-awesome-icon icon="question-circle" />
|
||||||
</n8n-tooltip>
|
</N8nTooltip>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="14" class="ignore-key-press-canvas">
|
<el-col :span="14" class="ignore-key-press-canvas">
|
||||||
<n8n-select
|
<N8nSelect
|
||||||
v-model="workflowSettings.saveManualExecutions"
|
v-model="workflowSettings.saveManualExecutions"
|
||||||
:placeholder="i18n.baseText('workflowSettings.selectOption')"
|
:placeholder="i18n.baseText('workflowSettings.selectOption')"
|
||||||
filterable
|
filterable
|
||||||
@@ -691,28 +696,28 @@ onMounted(async () => {
|
|||||||
:limit-popper-width="true"
|
:limit-popper-width="true"
|
||||||
data-test-id="workflow-settings-save-manual-executions"
|
data-test-id="workflow-settings-save-manual-executions"
|
||||||
>
|
>
|
||||||
<n8n-option
|
<N8nOption
|
||||||
v-for="option of saveManualOptions"
|
v-for="option of saveManualOptions"
|
||||||
:key="option.key"
|
:key="option.key"
|
||||||
:label="option.value"
|
:label="option.value"
|
||||||
:value="option.key"
|
:value="option.key"
|
||||||
>
|
>
|
||||||
</n8n-option>
|
</N8nOption>
|
||||||
</n8n-select>
|
</N8nSelect>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10" class="setting-name">
|
<el-col :span="10" class="setting-name">
|
||||||
{{ i18n.baseText('workflowSettings.saveExecutionProgress') + ':' }}
|
{{ i18n.baseText('workflowSettings.saveExecutionProgress') + ':' }}
|
||||||
<n8n-tooltip placement="top">
|
<N8nTooltip placement="top">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div v-text="helpTexts.saveExecutionProgress"></div>
|
<div v-text="helpTexts.saveExecutionProgress"></div>
|
||||||
</template>
|
</template>
|
||||||
<font-awesome-icon icon="question-circle" />
|
<font-awesome-icon icon="question-circle" />
|
||||||
</n8n-tooltip>
|
</N8nTooltip>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="14" class="ignore-key-press-canvas">
|
<el-col :span="14" class="ignore-key-press-canvas">
|
||||||
<n8n-select
|
<N8nSelect
|
||||||
v-model="workflowSettings.saveExecutionProgress"
|
v-model="workflowSettings.saveExecutionProgress"
|
||||||
:placeholder="i18n.baseText('workflowSettings.selectOption')"
|
:placeholder="i18n.baseText('workflowSettings.selectOption')"
|
||||||
filterable
|
filterable
|
||||||
@@ -720,25 +725,25 @@ onMounted(async () => {
|
|||||||
:limit-popper-width="true"
|
:limit-popper-width="true"
|
||||||
data-test-id="workflow-settings-save-execution-progress"
|
data-test-id="workflow-settings-save-execution-progress"
|
||||||
>
|
>
|
||||||
<n8n-option
|
<N8nOption
|
||||||
v-for="option of saveExecutionProgressOptions"
|
v-for="option of saveExecutionProgressOptions"
|
||||||
:key="option.key"
|
:key="option.key"
|
||||||
:label="option.value"
|
:label="option.value"
|
||||||
:value="option.key"
|
:value="option.key"
|
||||||
>
|
>
|
||||||
</n8n-option>
|
</N8nOption>
|
||||||
</n8n-select>
|
</N8nSelect>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10" class="setting-name">
|
<el-col :span="10" class="setting-name">
|
||||||
{{ i18n.baseText('workflowSettings.timeoutWorkflow') + ':' }}
|
{{ i18n.baseText('workflowSettings.timeoutWorkflow') + ':' }}
|
||||||
<n8n-tooltip placement="top">
|
<N8nTooltip placement="top">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div v-text="helpTexts.executionTimeoutToggle"></div>
|
<div v-text="helpTexts.executionTimeoutToggle"></div>
|
||||||
</template>
|
</template>
|
||||||
<font-awesome-icon icon="question-circle" />
|
<font-awesome-icon icon="question-circle" />
|
||||||
</n8n-tooltip>
|
</N8nTooltip>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="14">
|
<el-col :span="14">
|
||||||
<div>
|
<div>
|
||||||
@@ -760,25 +765,25 @@ onMounted(async () => {
|
|||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10" class="setting-name">
|
<el-col :span="10" class="setting-name">
|
||||||
{{ i18n.baseText('workflowSettings.timeoutAfter') + ':' }}
|
{{ i18n.baseText('workflowSettings.timeoutAfter') + ':' }}
|
||||||
<n8n-tooltip placement="top">
|
<N8nTooltip placement="top">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div v-text="helpTexts.executionTimeout"></div>
|
<div v-text="helpTexts.executionTimeout"></div>
|
||||||
</template>
|
</template>
|
||||||
<font-awesome-icon icon="question-circle" />
|
<font-awesome-icon icon="question-circle" />
|
||||||
</n8n-tooltip>
|
</N8nTooltip>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="4">
|
<el-col :span="4">
|
||||||
<n8n-input
|
<N8nInput
|
||||||
:disabled="readOnlyEnv || !workflowPermissions.update"
|
:disabled="readOnlyEnv || !workflowPermissions.update"
|
||||||
:model-value="timeoutHMS.hours"
|
:model-value="timeoutHMS.hours"
|
||||||
:min="0"
|
:min="0"
|
||||||
@update:model-value="(value: string) => setTheTimeout('hours', value)"
|
@update:model-value="(value: string) => setTheTimeout('hours', value)"
|
||||||
>
|
>
|
||||||
<template #append>{{ i18n.baseText('workflowSettings.hours') }}</template>
|
<template #append>{{ i18n.baseText('workflowSettings.hours') }}</template>
|
||||||
</n8n-input>
|
</N8nInput>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="4" class="timeout-input">
|
<el-col :span="4" class="timeout-input">
|
||||||
<n8n-input
|
<N8nInput
|
||||||
:disabled="readOnlyEnv || !workflowPermissions.update"
|
:disabled="readOnlyEnv || !workflowPermissions.update"
|
||||||
:model-value="timeoutHMS.minutes"
|
:model-value="timeoutHMS.minutes"
|
||||||
:min="0"
|
:min="0"
|
||||||
@@ -786,10 +791,10 @@ onMounted(async () => {
|
|||||||
@update:model-value="(value: string) => setTheTimeout('minutes', value)"
|
@update:model-value="(value: string) => setTheTimeout('minutes', value)"
|
||||||
>
|
>
|
||||||
<template #append>{{ i18n.baseText('workflowSettings.minutes') }}</template>
|
<template #append>{{ i18n.baseText('workflowSettings.minutes') }}</template>
|
||||||
</n8n-input>
|
</N8nInput>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="4" class="timeout-input">
|
<el-col :span="4" class="timeout-input">
|
||||||
<n8n-input
|
<N8nInput
|
||||||
:disabled="readOnlyEnv || !workflowPermissions.update"
|
:disabled="readOnlyEnv || !workflowPermissions.update"
|
||||||
:model-value="timeoutHMS.seconds"
|
:model-value="timeoutHMS.seconds"
|
||||||
:min="0"
|
:min="0"
|
||||||
@@ -797,15 +802,41 @@ onMounted(async () => {
|
|||||||
@update:model-value="(value: string) => setTheTimeout('seconds', value)"
|
@update:model-value="(value: string) => setTheTimeout('seconds', value)"
|
||||||
>
|
>
|
||||||
<template #append>{{ i18n.baseText('workflowSettings.seconds') }}</template>
|
<template #append>{{ i18n.baseText('workflowSettings.seconds') }}</template>
|
||||||
</n8n-input>
|
</N8nInput>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="10" class="setting-name">
|
||||||
|
<label for="timeSavedPerExecution">
|
||||||
|
{{ i18n.baseText('workflowSettings.timeSavedPerExecution') + ':' }}
|
||||||
|
<N8nTooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
{{ i18n.baseText('workflowSettings.timeSavedPerExecution.tooltip') }}
|
||||||
|
</template>
|
||||||
|
<font-awesome-icon icon="question-circle" />
|
||||||
|
</N8nTooltip>
|
||||||
|
</label>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="14">
|
||||||
|
<div class="time-saved">
|
||||||
|
<N8nInput
|
||||||
|
id="timeSavedPerExecution"
|
||||||
|
v-model="workflowSettings.timeSavedPerExecution"
|
||||||
|
:disabled="readOnlyEnv || !workflowPermissions.update"
|
||||||
|
data-test-id="workflow-settings-time-saved-per-execution"
|
||||||
|
type="number"
|
||||||
|
@update:model-value="updateTimeSavedPerExecution"
|
||||||
|
/>
|
||||||
|
<span>{{ i18n.baseText('workflowSettings.timeSavedPerExecution.hint') }}</span>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="action-buttons" data-test-id="workflow-settings-save-button">
|
<div class="action-buttons" data-test-id="workflow-settings-save-button">
|
||||||
<n8n-button
|
<N8nButton
|
||||||
:disabled="readOnlyEnv || !workflowPermissions.update"
|
:disabled="readOnlyEnv || !workflowPermissions.update"
|
||||||
:label="i18n.baseText('workflowSettings.save')"
|
:label="i18n.baseText('workflowSettings.save')"
|
||||||
size="large"
|
size="large"
|
||||||
@@ -844,4 +875,17 @@ onMounted(async () => {
|
|||||||
.timeout-input {
|
.timeout-input {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.time-saved {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
:deep(.el-input) {
|
||||||
|
width: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-left: var(--spacing-2xs);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2375,6 +2375,9 @@
|
|||||||
"workflowSettings.timeoutAfter": "Timeout After",
|
"workflowSettings.timeoutAfter": "Timeout After",
|
||||||
"workflowSettings.timeoutWorkflow": "Timeout Workflow",
|
"workflowSettings.timeoutWorkflow": "Timeout Workflow",
|
||||||
"workflowSettings.timezone": "Timezone",
|
"workflowSettings.timezone": "Timezone",
|
||||||
|
"workflowSettings.timeSavedPerExecution": "Estimated time saved",
|
||||||
|
"workflowSettings.timeSavedPerExecution.hint": "Minutes per production execution",
|
||||||
|
"workflowSettings.timeSavedPerExecution.tooltip": "Total time savings are summarised in the Overview page.",
|
||||||
"workflowHistory.title": "Version History",
|
"workflowHistory.title": "Version History",
|
||||||
"workflowHistory.content.title": "Version",
|
"workflowHistory.content.title": "Version",
|
||||||
"workflowHistory.content.editedBy": "Edited by",
|
"workflowHistory.content.editedBy": "Edited by",
|
||||||
|
|||||||
Reference in New Issue
Block a user