diff --git a/packages/frontend/@n8n/i18n/src/locales/en.json b/packages/frontend/@n8n/i18n/src/locales/en.json
index de54deee6b..39eeae0ca0 100644
--- a/packages/frontend/@n8n/i18n/src/locales/en.json
+++ b/packages/frontend/@n8n/i18n/src/locales/en.json
@@ -980,10 +980,12 @@
"folders.move.modal.message.usedCredentials.warning": "Workflow may not execute correctly if you choose not to share the credentials.",
"folders.move.success.title": "Successfully moved folder",
"folders.move.success.message": "{folderName} has been moved to {newFolderName}, along with all its workflows and subfolders.
View {newFolderName}",
+ "folders.move.success.messageNoAccess": "{folderName} has been moved to {newFolderName}, along with all its workflows and subfolders.",
"folders.move.error.title": "Problem moving folder",
"folders.move.workflow.error.title": "Problem moving workflow",
"folders.move.workflow.success.title": "Successfully moved workflow",
"folders.move.workflow.success.message": "{workflowName} has been moved to {newFolderName}.
View {newFolderName}",
+ "folders.move.workflow.success.messageNoAccess": "{workflowName} has been moved to {newFolderName}.",
"folders.move.project.root.name": "No folder (project root)",
"folders.open.error.title": "Problem opening folder",
"folders.create.error.title": "Problem creating folder",
diff --git a/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.test.ts b/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.test.ts
index b7fa94924a..556fae82be 100644
--- a/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.test.ts
+++ b/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.test.ts
@@ -487,14 +487,21 @@ describe('MoveToFolderModal', () => {
await userEvent.click(submitButton);
expect(mockEventBus.emit).toHaveBeenCalledWith('folder-transferred', {
- newParent: {
- id: folder.id,
- name: folder.name,
- type: folder.resource,
+ source: {
+ projectId: personalProject.id,
+ folder: {
+ id: TEST_FOLDER_RESOURCE.id,
+ name: TEST_FOLDER_RESOURCE.name,
+ },
+ },
+ destination: {
+ projectId: teamProjects[0].id,
+ parentFolder: {
+ id: folder.id,
+ name: folder.name,
+ },
+ canAccess: true,
},
- folder: { id: TEST_FOLDER_RESOURCE.id, name: TEST_FOLDER_RESOURCE.name },
- projectId: personalProject.id,
- destinationProjectId: teamProjects[0].id,
shareCredentials: undefined,
});
});
@@ -536,14 +543,21 @@ describe('MoveToFolderModal', () => {
await userEvent.click(submitButton);
expect(mockEventBus.emit).toHaveBeenCalledWith('folder-transferred', {
- newParent: {
- id: folder.id,
- name: folder.name,
- type: folder.resource,
+ source: {
+ projectId: personalProject.id,
+ folder: {
+ id: TEST_FOLDER_RESOURCE.id,
+ name: TEST_FOLDER_RESOURCE.name,
+ },
+ },
+ destination: {
+ projectId: teamProjects[0].id,
+ parentFolder: {
+ id: folder.id,
+ name: folder.name,
+ },
+ canAccess: true,
},
- folder: { id: TEST_FOLDER_RESOURCE.id, name: TEST_FOLDER_RESOURCE.name },
- projectId: personalProject.id,
- destinationProjectId: teamProjects[0].id,
shareCredentials: [shareableUsedCredential.id],
});
});
@@ -584,14 +598,21 @@ describe('MoveToFolderModal', () => {
await userEvent.click(submitButton);
expect(mockEventBus.emit).toHaveBeenCalledWith('folder-transferred', {
- newParent: {
- id: folder.id,
- name: folder.name,
- type: folder.resource,
+ source: {
+ projectId: personalProject.id,
+ folder: {
+ id: TEST_FOLDER_RESOURCE.id,
+ name: TEST_FOLDER_RESOURCE.name,
+ },
+ },
+ destination: {
+ projectId: teamProjects[0].id,
+ parentFolder: {
+ id: folder.id,
+ name: folder.name,
+ },
+ canAccess: true,
},
- folder: { id: TEST_FOLDER_RESOURCE.id, name: TEST_FOLDER_RESOURCE.name },
- projectId: personalProject.id,
- destinationProjectId: teamProjects[0].id,
shareCredentials: undefined,
});
});
@@ -628,14 +649,21 @@ describe('MoveToFolderModal', () => {
await userEvent.click(submitButton);
expect(mockEventBus.emit).toHaveBeenCalledWith('folder-transferred', {
- newParent: {
- id: anotherUser.id,
- name: anotherUser.name,
- type: 'project',
+ source: {
+ projectId: personalProject.id,
+ folder: {
+ id: TEST_FOLDER_RESOURCE.id,
+ name: TEST_FOLDER_RESOURCE.name,
+ },
+ },
+ destination: {
+ projectId: anotherUser.id,
+ parentFolder: {
+ id: undefined,
+ name: anotherUser.name,
+ },
+ canAccess: false,
},
- folder: { id: TEST_FOLDER_RESOURCE.id, name: TEST_FOLDER_RESOURCE.name },
- projectId: personalProject.id,
- destinationProjectId: anotherUser.id,
shareCredentials: undefined,
});
});
@@ -731,17 +759,21 @@ describe('MoveToFolderModal', () => {
await userEvent.click(submitButton);
expect(mockEventBus.emit).toHaveBeenCalledWith('workflow-transferred', {
- newParent: {
- id: folder.id,
- name: folder.name,
- type: folder.resource,
+ source: {
+ projectId: personalProject.id,
+ workflow: {
+ id: TEST_WORKFLOW_RESOURCE.id,
+ name: TEST_WORKFLOW_RESOURCE.name,
+ },
},
- workflow: {
- id: TEST_WORKFLOW_RESOURCE.id,
- name: TEST_WORKFLOW_RESOURCE.name,
- oldParentId: TEST_WORKFLOW_RESOURCE.parentFolderId,
+ destination: {
+ projectId: teamProjects[0].id,
+ parentFolder: {
+ id: folder.id,
+ name: folder.name,
+ },
+ canAccess: true,
},
- projectId: teamProjects[0].id,
shareCredentials: undefined,
});
});
@@ -778,17 +810,21 @@ describe('MoveToFolderModal', () => {
await userEvent.click(submitButton);
expect(mockEventBus.emit).toHaveBeenCalledWith('workflow-transferred', {
- newParent: {
- id: anotherUser.id,
- name: anotherUser.name,
- type: 'project',
+ source: {
+ projectId: personalProject.id,
+ workflow: {
+ id: TEST_WORKFLOW_RESOURCE.id,
+ name: TEST_WORKFLOW_RESOURCE.name,
+ },
},
- workflow: {
- id: TEST_WORKFLOW_RESOURCE.id,
- name: TEST_WORKFLOW_RESOURCE.name,
- oldParentId: TEST_WORKFLOW_RESOURCE.parentFolderId,
+ destination: {
+ projectId: anotherUser.id,
+ parentFolder: {
+ id: undefined,
+ name: anotherUser.name,
+ },
+ canAccess: false,
},
- projectId: anotherUser.id,
shareCredentials: undefined,
});
});
diff --git a/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.vue b/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.vue
index bfa827a178..63919c6200 100644
--- a/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.vue
+++ b/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.vue
@@ -60,14 +60,14 @@ const credentialsStore = useCredentialsStore();
const workflowsStore = useWorkflowsStore();
const selectedFolder = ref(null);
-const selectedProject = ref(projectsStore.currentProject ?? null);
+const selectedProject = ref(projectsStore.currentProject);
const isPersonalProject = computed(() => {
return selectedProject.value?.type === ProjectTypes.Personal;
});
const isOwnPersonalProject = computed(() => {
return (
selectedProject.value?.type === ProjectTypes.Personal &&
- selectedProject.value.id === projectsStore.personalProject?.id
+ selectedProject.value?.id === projectsStore.personalProject?.id
);
});
const isTransferringOwnership = computed(() => {
@@ -196,12 +196,29 @@ const onSubmit = () => {
};
if (props.data.resourceType === 'folder') {
- if (selectedProject.value?.id !== projectsStore.currentProject?.id) {
+ if (selectedProject.value.id !== projectsStore.currentProject?.id) {
props.data.workflowListEventBus.emit('folder-transferred', {
- newParent,
- folder: { id: props.data.resource.id, name: props.data.resource.name },
- projectId: projectsStore.currentProject?.id,
- destinationProjectId: selectedProject.value.id,
+ source: {
+ projectId: projectsStore.currentProject?.id,
+ folder: {
+ id: props.data.resource.id,
+ name: props.data.resource.name,
+ },
+ },
+ destination: {
+ projectId: selectedProject.value.id,
+ parentFolder: {
+ id:
+ selectedFolder.value && selectedFolder.value.id !== selectedProject.value.id
+ ? selectedFolder.value.id
+ : undefined,
+ name:
+ selectedFolder.value && selectedFolder.value.id !== selectedProject.value.id
+ ? selectedFolder.value.name
+ : targetProjectName.value,
+ },
+ canAccess: isFolderSelectable.value,
+ },
shareCredentials: shareUsedCredentials.value
? shareableCredentials.value.map((c) => c.id)
: undefined,
@@ -215,12 +232,26 @@ const onSubmit = () => {
} else {
if (isTransferringOwnership.value) {
props.data.workflowListEventBus.emit('workflow-transferred', {
- newParent,
- projectId: selectedProject.value.id,
- workflow: {
- id: props.data.resource.id,
- name: props.data.resource.name,
- oldParentId: props.data.resource.parentFolderId,
+ source: {
+ projectId: projectsStore.currentProject?.id,
+ workflow: {
+ id: props.data.resource.id,
+ name: props.data.resource.name,
+ },
+ },
+ destination: {
+ projectId: selectedProject.value.id,
+ parentFolder: {
+ id:
+ selectedFolder.value && selectedFolder.value.id !== selectedProject.value.id
+ ? selectedFolder.value.id
+ : undefined,
+ name:
+ selectedFolder.value && selectedFolder.value.id !== selectedProject.value.id
+ ? selectedFolder.value.name
+ : targetProjectName.value,
+ },
+ canAccess: isFolderSelectable.value,
},
shareCredentials: shareUsedCredentials.value
? shareableCredentials.value.map((c) => c.id)
@@ -237,6 +268,7 @@ const onSubmit = () => {
});
}
}
+
uiStore.closeModal(MOVE_FOLDER_MODAL_KEY);
};
diff --git a/packages/frontend/editor-ui/src/views/WorkflowsView.vue b/packages/frontend/editor-ui/src/views/WorkflowsView.vue
index d2f9c65294..a414e8422d 100644
--- a/packages/frontend/editor-ui/src/views/WorkflowsView.vue
+++ b/packages/frontend/editor-ui/src/views/WorkflowsView.vue
@@ -1181,6 +1181,7 @@ const moveFolder = async (payload: {
};
}) => {
if (!route.params.projectId) return;
+
try {
await foldersStore.moveFolder(
route.params.projectId as string,
@@ -1227,55 +1228,85 @@ const moveFolder = async (payload: {
};
const onFolderTransferred = async (payload: {
- folder: { id: string; name: string };
- projectId: string;
- destinationProjectId: string;
- newParent: { id: string; name: string; type: 'folder' | 'project' };
+ source: {
+ projectId: string;
+ folder: { id: string; name: string };
+ };
+ destination: {
+ projectId: string;
+ parentFolder: { id: string | undefined; name: string };
+ canAccess: boolean;
+ };
shareCredentials?: string[];
}) => {
- const destinationParentFolderId =
- payload.newParent.type === 'folder' ? payload.newParent.id : undefined;
+ try {
+ await foldersStore.moveFolderToProject(
+ payload.source.projectId,
+ payload.source.folder.id,
+ payload.destination.projectId,
+ payload.destination.parentFolder.id,
+ payload.shareCredentials,
+ );
- await foldersStore.moveFolderToProject(
- payload.projectId,
- payload.folder.id,
- payload.destinationProjectId,
- destinationParentFolderId,
- payload.shareCredentials,
- );
-
- const isCurrentFolder = currentFolderId.value === payload.folder.id;
- const newFolderURL = router.resolve({
- name: VIEWS.PROJECTS_FOLDERS,
- params: {
- projectId: payload.destinationProjectId,
- folderId: destinationParentFolderId,
- },
- }).href;
-
- if (isCurrentFolder) {
- // If we just moved the current folder, automatically navigate to the new folder
- void router.push(newFolderURL);
- } else {
- // Else show success message and update the list
- toast.showToast({
- title: i18n.baseText('folders.move.success.title'),
- message: i18n.baseText('folders.move.success.message', {
- interpolate: {
- folderName: payload.folder.name,
- newFolderName: payload.newParent.name,
- },
- }),
- onClick: (event: MouseEvent | undefined) => {
- if (event?.target instanceof HTMLAnchorElement) {
- event.preventDefault();
- void router.push(newFolderURL);
- }
+ const isCurrentFolder = currentFolderId.value === payload.source.folder.id;
+ const newFolderURL = router.resolve({
+ name: VIEWS.PROJECTS_FOLDERS,
+ params: {
+ projectId: payload.destination.canAccess
+ ? payload.destination.projectId
+ : payload.source.projectId,
+ folderId: payload.destination.canAccess ? payload.source.folder.id : undefined,
},
- type: 'success',
- });
+ }).href;
- await fetchWorkflows();
+ if (isCurrentFolder) {
+ if (payload.destination.canAccess) {
+ // If we just moved the current folder and can access the destination navigate there
+ void router.push(newFolderURL);
+ } else {
+ // Otherwise navigate to the workflows page of the source project
+ void router.push({
+ name: VIEWS.PROJECTS_WORKFLOWS,
+ params: {
+ projectId: payload.source.projectId,
+ },
+ });
+ }
+ } else {
+ await refreshWorkflows();
+
+ if (payload.destination.canAccess) {
+ toast.showToast({
+ title: i18n.baseText('folders.move.success.title'),
+ message: i18n.baseText('folders.move.success.message', {
+ interpolate: {
+ folderName: payload.source.folder.name,
+ newFolderName: payload.destination.parentFolder.name,
+ },
+ }),
+ onClick: (event: MouseEvent | undefined) => {
+ if (event?.target instanceof HTMLAnchorElement) {
+ event.preventDefault();
+ void router.push(newFolderURL);
+ }
+ },
+ type: 'success',
+ });
+ } else {
+ toast.showToast({
+ title: i18n.baseText('folders.move.success.title'),
+ message: i18n.baseText('folders.move.success.messageNoAccess', {
+ interpolate: {
+ folderName: payload.source.folder.name,
+ newFolderName: payload.destination.parentFolder.name,
+ },
+ }),
+ type: 'success',
+ });
+ }
+ }
+ } catch (error) {
+ toast.showError(error, i18n.baseText('folders.move.error.title'));
}
};
@@ -1305,46 +1336,63 @@ const moveWorkflowToFolder = async (payload: {
};
const onWorkflowTransferred = async (payload: {
- projectId: string;
- workflow: { id: string; name: string; oldParentId: string };
- newParent: { id: string; name: string; type: 'folder' | 'project' };
+ source: {
+ projectId: string;
+ workflow: { id: string; name: string };
+ };
+ destination: {
+ projectId: string;
+ parentFolder: { id: string | undefined; name: string };
+ canAccess: boolean;
+ };
shareCredentials?: string[];
}) => {
- const parentFolderId = payload.newParent.type === 'folder' ? payload.newParent.id : undefined;
-
- await projectsStore.moveResourceToProject(
- 'workflow',
- payload.workflow.id,
- payload.projectId,
- parentFolderId,
- payload.shareCredentials,
- );
-
- await fetchWorkflows();
-
try {
- toast.showToast({
- title: i18n.baseText('folders.move.workflow.success.title'),
- message: i18n.baseText('folders.move.workflow.success.message', {
- interpolate: {
- workflowName: payload.workflow.name,
- newFolderName: payload.newParent.name,
+ await projectsStore.moveResourceToProject(
+ 'workflow',
+ payload.source.workflow.id,
+ payload.destination.projectId,
+ payload.destination.parentFolder.id,
+ payload.shareCredentials,
+ );
+
+ await refreshWorkflows();
+
+ if (payload.destination.canAccess) {
+ toast.showToast({
+ title: i18n.baseText('folders.move.workflow.success.title'),
+ message: i18n.baseText('folders.move.workflow.success.message', {
+ interpolate: {
+ workflowName: payload.source.workflow.name,
+ newFolderName: payload.destination.parentFolder.name,
+ },
+ }),
+ onClick: (event: MouseEvent | undefined) => {
+ if (event?.target instanceof HTMLAnchorElement) {
+ event.preventDefault();
+ void router.push({
+ name: VIEWS.PROJECTS_FOLDERS,
+ params: {
+ projectId: payload.destination.projectId,
+ folderId: payload.destination.parentFolder.id,
+ },
+ });
+ }
},
- }),
- onClick: (event: MouseEvent | undefined) => {
- if (event?.target instanceof HTMLAnchorElement) {
- event.preventDefault();
- void router.push({
- name: VIEWS.PROJECTS_FOLDERS,
- params: {
- projectId: payload.projectId,
- folderId: payload.newParent.type === 'folder' ? payload.newParent.id : undefined,
- },
- });
- }
- },
- type: 'success',
- });
+ type: 'success',
+ });
+ } else {
+ toast.showToast({
+ title: i18n.baseText('folders.move.workflow.success.title'),
+ message: i18n.baseText('folders.move.workflow.success.messageNoAccess', {
+ interpolate: {
+ workflowName: payload.source.workflow.name,
+ newFolderName: payload.destination.parentFolder.name,
+ },
+ }),
+ type: 'success',
+ });
+ }
} catch (error) {
toast.showError(error, i18n.baseText('folders.move.workflow.error.title'));
}