mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-19 07:36:52 +00:00
fix(editor): Make workflow settings and actions menu work in the AI artifact view (#32082)
This commit is contained in:
committed by
GitHub
parent
9406b4abc7
commit
5a28683c78
@@ -71,10 +71,6 @@ const workflowTelemetry = useTelemetry();
|
||||
const favoritesStore = useFavoritesStore();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
|
||||
const onWorkflowPage = computed(() => {
|
||||
return route.meta && (route.meta.nodeView || route.meta.keepWorkflowAlive === true);
|
||||
});
|
||||
|
||||
const onExecutionsTab = computed(() => {
|
||||
return [
|
||||
VIEWS.EXECUTION_HOME.toString(),
|
||||
@@ -120,7 +116,6 @@ const workflowMenuItems = computed<Array<ActionDropdownItem<WORKFLOW_MENU_ACTION
|
||||
{
|
||||
id: WORKFLOW_MENU_ACTIONS.DOWNLOAD,
|
||||
label: locale.baseText('menuActions.download'),
|
||||
disabled: !onWorkflowPage.value,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -128,7 +123,6 @@ const workflowMenuItems = computed<Array<ActionDropdownItem<WORKFLOW_MENU_ACTION
|
||||
actions.push({
|
||||
id: WORKFLOW_MENU_ACTIONS.SHARE,
|
||||
label: locale.baseText('workflowDetails.share'),
|
||||
disabled: !onWorkflowPage.value,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -148,7 +142,7 @@ const workflowMenuItems = computed<Array<ActionDropdownItem<WORKFLOW_MENU_ACTION
|
||||
actions.push({
|
||||
id: WORKFLOW_MENU_ACTIONS.RENAME,
|
||||
label: locale.baseText('generic.rename'),
|
||||
disabled: !onWorkflowPage.value || props.workflowPermissions.update !== true,
|
||||
disabled: props.workflowPermissions.update !== true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -157,7 +151,7 @@ const workflowMenuItems = computed<Array<ActionDropdownItem<WORKFLOW_MENU_ACTION
|
||||
label: favoritesStore.isFavorite(props.id, 'workflow')
|
||||
? locale.baseText('favorites.remove')
|
||||
: locale.baseText('favorites.add'),
|
||||
disabled: !onWorkflowPage.value || props.isNewWorkflow,
|
||||
disabled: props.isNewWorkflow,
|
||||
});
|
||||
|
||||
if (
|
||||
@@ -170,24 +164,24 @@ const workflowMenuItems = computed<Array<ActionDropdownItem<WORKFLOW_MENU_ACTION
|
||||
actions.unshift({
|
||||
id: WORKFLOW_MENU_ACTIONS.DUPLICATE,
|
||||
label: locale.baseText('menuActions.duplicate'),
|
||||
disabled: !onWorkflowPage.value || !props.id,
|
||||
disabled: !props.id,
|
||||
});
|
||||
actions.unshift({
|
||||
id: WORKFLOW_MENU_ACTIONS.EDIT_DESCRIPTION,
|
||||
label: locale.baseText('menuActions.editDescription'),
|
||||
disabled: !onWorkflowPage.value || !props.id,
|
||||
disabled: !props.id,
|
||||
});
|
||||
|
||||
actions.push(
|
||||
{
|
||||
id: WORKFLOW_MENU_ACTIONS.IMPORT_FROM_URL,
|
||||
label: locale.baseText('menuActions.importFromUrl'),
|
||||
disabled: !onWorkflowPage.value || onExecutionsTab.value,
|
||||
disabled: onExecutionsTab.value,
|
||||
},
|
||||
{
|
||||
id: WORKFLOW_MENU_ACTIONS.IMPORT_FROM_FILE,
|
||||
label: locale.baseText('menuActions.importFromFile'),
|
||||
disabled: !onWorkflowPage.value || onExecutionsTab.value,
|
||||
disabled: onExecutionsTab.value,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -198,7 +192,6 @@ const workflowMenuItems = computed<Array<ActionDropdownItem<WORKFLOW_MENU_ACTION
|
||||
label: locale.baseText('menuActions.push'),
|
||||
disabled:
|
||||
!sourceControlStore.isEnterpriseSourceControlEnabled ||
|
||||
!onWorkflowPage.value ||
|
||||
onExecutionsTab.value ||
|
||||
sourceControlStore.preferences.branchReadOnly,
|
||||
});
|
||||
@@ -207,7 +200,7 @@ const workflowMenuItems = computed<Array<ActionDropdownItem<WORKFLOW_MENU_ACTION
|
||||
actions.push({
|
||||
id: WORKFLOW_MENU_ACTIONS.SETTINGS,
|
||||
label: locale.baseText('generic.settings'),
|
||||
disabled: !onWorkflowPage.value || props.isNewWorkflow,
|
||||
disabled: props.isNewWorkflow,
|
||||
});
|
||||
|
||||
if (
|
||||
@@ -220,12 +213,12 @@ const workflowMenuItems = computed<Array<ActionDropdownItem<WORKFLOW_MENU_ACTION
|
||||
actions.push({
|
||||
id: WORKFLOW_MENU_ACTIONS.UNARCHIVE,
|
||||
label: locale.baseText('menuActions.unarchive'),
|
||||
disabled: !onWorkflowPage.value || props.isNewWorkflow,
|
||||
disabled: props.isNewWorkflow,
|
||||
});
|
||||
actions.push({
|
||||
id: WORKFLOW_MENU_ACTIONS.DELETE,
|
||||
label: locale.baseText('menuActions.delete'),
|
||||
disabled: !onWorkflowPage.value || props.isNewWorkflow,
|
||||
disabled: props.isNewWorkflow,
|
||||
customClass: $style.deleteItem,
|
||||
divided: true,
|
||||
});
|
||||
@@ -233,7 +226,7 @@ const workflowMenuItems = computed<Array<ActionDropdownItem<WORKFLOW_MENU_ACTION
|
||||
actions.push({
|
||||
id: WORKFLOW_MENU_ACTIONS.ARCHIVE,
|
||||
label: locale.baseText('menuActions.archive'),
|
||||
disabled: !onWorkflowPage.value || props.isNewWorkflow,
|
||||
disabled: props.isNewWorkflow,
|
||||
customClass: $style.deleteItem,
|
||||
divided: true,
|
||||
});
|
||||
|
||||
@@ -31,14 +31,15 @@ import {
|
||||
createWorkflowDocumentId,
|
||||
} from '@/app/stores/workflowDocument.store';
|
||||
|
||||
// No workflow route meta on purpose: the menu renders both on workflow-layout
|
||||
// routes and in host-embedded editors without a workflow route (e.g. the AI
|
||||
// artifact view), so nothing here may depend on route meta.
|
||||
vi.mock('vue-router', async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
useRoute: vi.fn().mockReturnValue({
|
||||
params: { workflowId: 'test' },
|
||||
query: { parentFolderId: '1' },
|
||||
meta: {
|
||||
nodeView: true,
|
||||
},
|
||||
meta: {},
|
||||
}),
|
||||
useRouter: vi.fn().mockReturnValue({
|
||||
replace: vi.fn(),
|
||||
@@ -251,16 +252,6 @@ describe('WorkflowDetails', () => {
|
||||
});
|
||||
|
||||
describe('Workflow menu', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(useRoute).mockReturnValueOnce({
|
||||
meta: {
|
||||
nodeView: true,
|
||||
},
|
||||
query: { parentFolderId: '1' },
|
||||
params: { workflowId: 'test' },
|
||||
} as unknown as ReturnType<typeof useRoute>);
|
||||
});
|
||||
|
||||
it('should not have workflow duplicate and import when branch is read-only', async () => {
|
||||
sourceControlStore.preferences.branchReadOnly = true;
|
||||
|
||||
@@ -308,10 +299,10 @@ describe('WorkflowDetails', () => {
|
||||
|
||||
await userEvent.click(getByTestId('workflow-menu'));
|
||||
|
||||
expect(getByTestId('workflow-menu-item-duplicate')).toBeInTheDocument();
|
||||
expect(getByTestId('workflow-menu-item-import-from-url')).toBeInTheDocument();
|
||||
expect(getByTestId('workflow-menu-item-import-from-file')).toBeInTheDocument();
|
||||
expect(queryByTestId('workflow-menu-item-share')).toBeInTheDocument();
|
||||
expect(getByTestId('workflow-menu-item-duplicate')).not.toHaveClass('disabled');
|
||||
expect(getByTestId('workflow-menu-item-import-from-url')).not.toHaveClass('disabled');
|
||||
expect(getByTestId('workflow-menu-item-import-from-file')).not.toHaveClass('disabled');
|
||||
expect(queryByTestId('workflow-menu-item-share')).not.toHaveClass('disabled');
|
||||
expect(queryByTestId('workflow-menu-item-delete')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('workflow-menu-item-archive')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('workflow-menu-item-unarchive')).not.toBeInTheDocument();
|
||||
@@ -321,9 +312,7 @@ describe('WorkflowDetails', () => {
|
||||
vi.mocked(useRoute)
|
||||
.mockReset()
|
||||
.mockReturnValue({
|
||||
meta: {
|
||||
nodeView: true,
|
||||
},
|
||||
meta: {},
|
||||
query: { parentFolderId: '1', new: 'true' },
|
||||
params: { workflowId: 'test' },
|
||||
} as unknown as ReturnType<typeof useRoute>);
|
||||
|
||||
+5
-4
@@ -34,13 +34,14 @@ vi.mock('@/app/composables/useToast', () => ({
|
||||
useToast: () => toast,
|
||||
}));
|
||||
|
||||
// The modal is mounted globally, so it can be opened from views whose route has no
|
||||
// `workflowId` param (e.g. the AI artifact view). The whole suite runs under that
|
||||
// condition: the workflow id must always come from the document store, never the route.
|
||||
vi.mock('vue-router', async () => ({
|
||||
useRouter: vi.fn(),
|
||||
useRoute: () =>
|
||||
reactive({
|
||||
params: {
|
||||
workflowId: '1',
|
||||
},
|
||||
params: {},
|
||||
query: {},
|
||||
}),
|
||||
RouterLink: {
|
||||
@@ -284,7 +285,7 @@ describe('WorkflowSettingsVue', () => {
|
||||
await userEvent.click(getByRole('button', { name: 'Save' }));
|
||||
|
||||
expect(workflowsStore.updateWorkflow).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
'1',
|
||||
expect.objectContaining({
|
||||
settings: expect.objectContaining({
|
||||
customTelemetryTags: [{ key: 'env', value: 'production' }],
|
||||
|
||||
+2
-4
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick, h } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useToast } from '@/app/composables/useToast';
|
||||
import { usePostHog } from '@/app/stores/posthog.store';
|
||||
import type { ITimeoutHMS, IWorkflowSettings, IWorkflowShortResponse } from '@/Interface';
|
||||
@@ -70,7 +69,6 @@ import WorkflowCustomTelemetryTags from '@/app/components/WorkflowSettings/Workf
|
||||
|
||||
import { ElCol, ElRow, ElSwitch } from 'element-plus';
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const externalHooks = useExternalHooks();
|
||||
const toast = useToast();
|
||||
@@ -659,7 +657,7 @@ const convertToHMS = (num: number): ITimeoutHMS => {
|
||||
|
||||
const saveCustomTelemetryTags = async (customTelemetryTags: ICustomTelemetryTag[]) => {
|
||||
try {
|
||||
await workflowsStore.updateWorkflow(String(route.params.workflowId), {
|
||||
await workflowsStore.updateWorkflow(workflowId.value, {
|
||||
settings: { customTelemetryTags },
|
||||
expectedChecksum: workflowDocumentStore.value.checksum,
|
||||
});
|
||||
@@ -747,7 +745,7 @@ const saveSettings = async () => {
|
||||
data.expectedChecksum = workflowDocumentStore.value.checksum;
|
||||
|
||||
try {
|
||||
await workflowsStore.updateWorkflow(String(route.params.workflowId), data);
|
||||
await workflowsStore.updateWorkflow(workflowId.value, data);
|
||||
} catch (error) {
|
||||
toast.showError(error, i18n.baseText('workflowSettings.showError.saveSettings3.title'));
|
||||
isLoading.value = false;
|
||||
|
||||
Reference in New Issue
Block a user