fix(editor): Make workflow settings and actions menu work in the AI artifact view (#32082)

This commit is contained in:
Raúl Gómez Morales
2026-06-15 11:34:17 +02:00
committed by GitHub
parent 9406b4abc7
commit 5a28683c78
4 changed files with 26 additions and 45 deletions
@@ -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>);
@@ -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' }],
@@ -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;