From 952962cdd46a2c1622490f4adfc9a97f277b5959 Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Thu, 5 Sep 2024 12:56:03 +0200
Subject: [PATCH 01/30] fix(editor): Allow resources to move between personal
and team projects
---
.../Projects/ProjectMoveResourceModal.vue | 145 ++++++++++++++----
.../Projects/ProjectSharingInfo.vue | 4 +
.../src/plugins/i18n/locales/en.json | 10 +-
3 files changed, 129 insertions(+), 30 deletions(-)
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
index d115a18f39a2f..809ad73c7dbd8 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
@@ -1,14 +1,16 @@
@@ -249,15 +247,6 @@ import PromptMfaCodeModal from './PromptMfaCodeModal/PromptMfaCodeModal.vue';
/>
-
-
-
-
-
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceConfirmModal.test.ts b/packages/editor-ui/src/components/Projects/ProjectMoveResourceConfirmModal.test.ts
deleted file mode 100644
index b413550a39147..0000000000000
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceConfirmModal.test.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { createPinia, setActivePinia } from 'pinia';
-import userEvent from '@testing-library/user-event';
-import { createComponentRenderer } from '@/__tests__/render';
-import { PROJECT_MOVE_RESOURCE_CONFIRM_MODAL } from '@/constants';
-import ProjectMoveResourceConfirmModal from '@/components/Projects/ProjectMoveResourceConfirmModal.vue';
-import { useProjectsStore } from '@/stores/projects.store';
-import { useTelemetry } from '@/composables/useTelemetry';
-
-vi.mock('@/stores/ui.store', () => ({
- useUIStore: vi.fn().mockReturnValue({
- modalsById: vi.fn().mockReturnValue(() => {
- open: true;
- }),
- closeModal: vi.fn(),
- }),
-}));
-
-const renderComponent = createComponentRenderer(ProjectMoveResourceConfirmModal, {
- global: {
- stubs: {
- Modal: {
- template:
- '
',
- },
- },
- },
-});
-
-let projectsStore: ReturnType;
-let telemetry: ReturnType;
-
-describe('ProjectMoveResourceConfirmModal', () => {
- beforeEach(() => {
- setActivePinia(createPinia());
- projectsStore = useProjectsStore();
- telemetry = useTelemetry();
- });
-
- it('should send telemetry when resource moving is confirmed', async () => {
- vi.spyOn(projectsStore, 'moveResourceToProject').mockResolvedValue();
-
- const telemetryTrackSpy = vi.spyOn(telemetry, 'track');
-
- const props = {
- modalName: PROJECT_MOVE_RESOURCE_CONFIRM_MODAL,
- data: {
- resourceType: 'workflow',
- resource: {
- id: '1',
- },
- projectId: '1',
- projectName: 'My Project',
- },
- };
- const { getByRole, getAllByRole } = renderComponent({ props });
- const confirmBtn = getByRole('button', { name: /confirm/i });
- expect(confirmBtn).toBeDisabled();
- await Promise.all(
- getAllByRole('checkbox').map(async (checkbox) => await userEvent.click(checkbox)),
- );
- expect(confirmBtn).toBeEnabled();
- await userEvent.click(confirmBtn);
- expect(telemetryTrackSpy).toHaveBeenCalledWith(
- 'User successfully moved workflow',
- expect.objectContaining({ workflow_id: '1' }),
- );
- });
-});
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceConfirmModal.vue b/packages/editor-ui/src/components/Projects/ProjectMoveResourceConfirmModal.vue
deleted file mode 100644
index dcccb49a34374..0000000000000
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceConfirmModal.vue
+++ /dev/null
@@ -1,131 +0,0 @@
-
-
-
-
-
- {{ i18n.baseText('projects.move.resource.confirm.modal.title') }}
-
-
-
-
-
-
-
- {{ props.data.resourceTypeLabel }}
- {{
- i18n.baseText('projects.move.resource.confirm.modal.numberOfUsers', {
- interpolate: {
- numberOfUsers: props.data.resource.sharedWithProjects?.length ?? 0,
- },
- adjustToNumber: props.data.resource.sharedWithProjects?.length,
- })
- }}
-
-
-
-
-
-
-
- {{ i18n.baseText('generic.cancel') }}
-
-
- {{ i18n.baseText('projects.move.resource.confirm.modal.button.confirm') }}
-
-
-
-
-
-
-
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
index 809ad73c7dbd8..bb4fb79dcfbd7 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
@@ -2,7 +2,6 @@
import { ref, computed, onMounted, h } from 'vue';
import type { ICredentialsResponse, IWorkflowDb } from '@/Interface';
import { useI18n } from '@/composables/useI18n';
-import { useUIStore } from '@/stores/ui.store';
import { useProjectsStore } from '@/stores/projects.store';
import Modal from '@/components/Modal.vue';
import { VIEWS } from '@/constants';
@@ -23,7 +22,6 @@ const props = defineProps<{
const i18n = useI18n();
const toast = useToast();
-const uiStore = useUIStore();
const projectsStore = useProjectsStore();
const telemetry = useTelemetry();
@@ -55,10 +53,6 @@ const updateProject = (value: string) => {
projectId.value = value;
};
-const closeModal = () => {
- uiStore.closeModal(props.modalName);
-};
-
const moveResource = async () => {
if (!projectId.value) return;
try {
diff --git a/packages/editor-ui/src/constants.ts b/packages/editor-ui/src/constants.ts
index 638675ca7c23c..cf85b949875b0 100644
--- a/packages/editor-ui/src/constants.ts
+++ b/packages/editor-ui/src/constants.ts
@@ -69,7 +69,6 @@ export const PROMPT_MFA_CODE_MODAL_KEY = 'promptMfaCode';
export const WORKFLOW_HISTORY_VERSION_RESTORE = 'workflowHistoryVersionRestore';
export const SETUP_CREDENTIALS_MODAL_KEY = 'setupCredentials';
export const PROJECT_MOVE_RESOURCE_MODAL = 'projectMoveResourceModal';
-export const PROJECT_MOVE_RESOURCE_CONFIRM_MODAL = 'projectMoveResourceConfirmModal';
export const NEW_ASSISTANT_SESSION_MODAL = 'newAssistantSession';
export const EXTERNAL_SECRETS_PROVIDER_MODAL_KEY = 'externalSecretsProvider';
diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json
index a7f5f45ffb0e7..7baeddc535bbe 100644
--- a/packages/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/editor-ui/src/plugins/i18n/locales/en.json
@@ -2524,12 +2524,6 @@
"projects.move.resource.modal.message.sharingNote": "{note}: Moving will remove any existing sharing for this {resourceTypeLabel}.",
"projects.move.resource.modal.message.sharingInfo": "(Currently shared with {numberOfProjects} project) | (Currently shared with {numberOfProjects} projects)",
"projects.move.resource.modal.button": "Move {resourceTypeLabel}",
- "projects.move.resource.confirm.modal.title": "Please confirm the following",
- "projects.move.resource.confirm.modal.button.confirm": "Confirm move to new project",
- "projects.move.workflow.confirm.modal.label": "This workflow may stop working if the credentials used with it do not exist in the project its being moved to",
- "projects.move.credential.confirm.modal.label": "Any workflows currently using this credential will stop working once this credential has been moved",
- "projects.move.resource.confirm.modal.label": "Any individual sharing currently associated with this {resourceTypeLabel} will be removed. (currently shared with {numberOfUsers})",
- "projects.move.resource.confirm.modal.numberOfUsers": "{numberOfUsers} user | {numberOfUsers} users",
"projects.move.resource.error.title": "Error moving {resourceName} {resourceTypeLabel}",
"projects.move.resource.success.title": "Successfully moved {resourceTypeLabel}",
"projects.move.resource.success.message": "{resourceName} {resourceTypeLabel} was moved to {targetProjectName} {link}",
From b710d8e211c657cbbd7ea907a4cd189019d3a6c9 Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Thu, 5 Sep 2024 13:09:25 +0200
Subject: [PATCH 03/30] fix(editor): Remove resource moving confirmation modal
---
packages/editor-ui/src/stores/ui.store.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/packages/editor-ui/src/stores/ui.store.ts b/packages/editor-ui/src/stores/ui.store.ts
index e854ac656c63d..d187d9e55c7f6 100644
--- a/packages/editor-ui/src/stores/ui.store.ts
+++ b/packages/editor-ui/src/stores/ui.store.ts
@@ -35,7 +35,6 @@ import {
WORKFLOW_HISTORY_VERSION_RESTORE,
SETUP_CREDENTIALS_MODAL_KEY,
PROJECT_MOVE_RESOURCE_MODAL,
- PROJECT_MOVE_RESOURCE_CONFIRM_MODAL,
NEW_ASSISTANT_SESSION_MODAL,
PROMPT_MFA_CODE_MODAL_KEY,
} from '@/constants';
@@ -126,7 +125,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
WORKFLOW_HISTORY_VERSION_RESTORE,
SETUP_CREDENTIALS_MODAL_KEY,
PROJECT_MOVE_RESOURCE_MODAL,
- PROJECT_MOVE_RESOURCE_CONFIRM_MODAL,
NEW_ASSISTANT_SESSION_MODAL,
].map((modalKey) => [modalKey, { open: false }]),
),
From b511b24adf33a2b53afaaeedce4d777c621f20f7 Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Thu, 5 Sep 2024 13:53:41 +0200
Subject: [PATCH 04/30] fix(editor): Update move resource success toast message
---
.../components/Projects/ProjectMoveResourceModal.vue | 7 +++++++
.../Projects/ProjectMoveSuccessToastMessage.vue | 12 ++++++++++++
packages/editor-ui/src/plugins/i18n/locales/en.json | 3 ++-
3 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
index bb4fb79dcfbd7..f61c61372a2bf 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
@@ -2,6 +2,7 @@
import { ref, computed, onMounted, h } from 'vue';
import type { ICredentialsResponse, IWorkflowDb } from '@/Interface';
import { useI18n } from '@/composables/useI18n';
+import { useUIStore } from '@/stores/ui.store';
import { useProjectsStore } from '@/stores/projects.store';
import Modal from '@/components/Modal.vue';
import { VIEWS } from '@/constants';
@@ -21,6 +22,7 @@ const props = defineProps<{
}>();
const i18n = useI18n();
+const uiStore = useUIStore();
const toast = useToast();
const projectsStore = useProjectsStore();
const telemetry = useTelemetry();
@@ -53,6 +55,10 @@ const updateProject = (value: string) => {
projectId.value = value;
};
+const closeModal = () => {
+ uiStore.closeModal(props.modalName);
+};
+
const moveResource = async () => {
if (!projectId.value) return;
try {
@@ -78,6 +84,7 @@ const moveResource = async () => {
? VIEWS.PROJECTS_WORKFLOWS
: VIEWS.PROJECTS_CREDENTIALS,
resource: props.data.resource,
+ resourceType: props.data.resourceType,
resourceTypeLabel: props.data.resourceTypeLabel,
projectId: projectId.value,
projectName: availableProjects.value.find((p) => p.id === projectId.value)?.name ?? '',
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue b/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue
index bd5644c01712c..b1366f963eae7 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue
@@ -1,19 +1,31 @@
{{ props.resourceTypeLabel }}
{{ props.resource.name }}
{{ props.projectName }}
+
+
+
+
+ {{ props.projectName }}
+
+
+
Date: Thu, 5 Sep 2024 14:05:36 +0200
Subject: [PATCH 05/30] fix(editor): Type error and unit test
---
.../components/Projects/ProjectMoveResourceModal.test.ts | 4 ++--
.../src/components/Projects/ProjectMoveResourceModal.vue | 8 ++------
2 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.test.ts b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.test.ts
index 68dc7b4f3d75e..93e3249044a9b 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.test.ts
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.test.ts
@@ -1,6 +1,6 @@
import { createPinia, setActivePinia } from 'pinia';
import { createComponentRenderer } from '@/__tests__/render';
-import { PROJECT_MOVE_RESOURCE_CONFIRM_MODAL } from '@/constants';
+import { PROJECT_MOVE_RESOURCE_MODAL } from '@/constants';
import ProjectMoveResourceModal from '@/components/Projects/ProjectMoveResourceModal.vue';
import { useTelemetry } from '@/composables/useTelemetry';
@@ -27,7 +27,7 @@ describe('ProjectMoveResourceModal', () => {
const telemetryTrackSpy = vi.spyOn(telemetry, 'track');
const props = {
- modalName: PROJECT_MOVE_RESOURCE_CONFIRM_MODAL,
+ modalName: PROJECT_MOVE_RESOURCE_MODAL,
data: {
resourceType: 'workflow',
resource: {
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
index f61c61372a2bf..b3f40380bc329 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
@@ -28,8 +28,8 @@ const projectsStore = useProjectsStore();
const telemetry = useTelemetry();
const projectId = ref(null);
-const processedName = computed(() =>
- processProjectName(props.data.resource.homeProject?.name ?? ''),
+const processedName = computed(
+ () => processProjectName(props.data.resource.homeProject?.name ?? '') ?? '',
);
const availableProjects = computed(() =>
projectsStore.projects
@@ -39,10 +39,6 @@ const availableProjects = computed(() =>
const isResourceInTeamProject = computed(() => isHomeProjectTeam(props.data.resource));
-const selectedProject = computed(() =>
- availableProjects.value.find((p) => p.id === projectId.value),
-);
-
const isHomeProjectTeam = (resource: IWorkflowDb | ICredentialsResponse) =>
resource.homeProject?.type === ProjectTypes.Team;
From cf21509387a4037645339e77cca1492ecd2a6955 Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Mon, 9 Sep 2024 14:04:41 +0200
Subject: [PATCH 06/30] fix(editor): Update resource moving E2E test
---
cypress/composables/projects.ts | 2 -
cypress/e2e/39-projects.cy.ts | 73 ++++-----------------------------
2 files changed, 8 insertions(+), 67 deletions(-)
diff --git a/cypress/composables/projects.ts b/cypress/composables/projects.ts
index 84379088d1a39..da9c6fcc65678 100644
--- a/cypress/composables/projects.ts
+++ b/cypress/composables/projects.ts
@@ -32,8 +32,6 @@ export const addProjectMember = (email: string, role?: string) => {
}
};
export const getResourceMoveModal = () => cy.getByTestId('project-move-resource-modal');
-export const getResourceMoveConfirmModal = () =>
- cy.getByTestId('project-move-resource-confirm-modal');
export const getProjectMoveSelect = () => cy.getByTestId('project-move-resource-modal-select');
export function createProject(name: string) {
diff --git a/cypress/e2e/39-projects.cy.ts b/cypress/e2e/39-projects.cy.ts
index e2bf63df7dc46..90de91a5c33d8 100644
--- a/cypress/e2e/39-projects.cy.ts
+++ b/cypress/e2e/39-projects.cy.ts
@@ -481,44 +481,15 @@ describe('Projects', { disableAutoLogin: true }, () => {
projects
.getResourceMoveModal()
.should('be.visible')
- .find('button:contains("Next")')
+ .find('button:contains("Move workflow")')
.should('be.disabled');
projects.getProjectMoveSelect().click();
getVisibleSelect()
.find('li')
- .should('have.length', 2)
- .first()
- .should('contain.text', 'Project 1')
- .click();
- projects.getResourceMoveModal().find('button:contains("Next")').click();
-
- projects
- .getResourceMoveConfirmModal()
- .should('be.visible')
- .find('button:contains("Confirm")')
- .should('be.disabled');
-
- projects
- .getResourceMoveConfirmModal()
- .find('input[type="checkbox"]')
- .first()
- .parents('label')
- .click();
- projects
- .getResourceMoveConfirmModal()
- .find('button:contains("Confirm")')
- .should('be.disabled');
- projects
- .getResourceMoveConfirmModal()
- .find('input[type="checkbox"]')
- .last()
- .parents('label')
- .click();
- projects
- .getResourceMoveConfirmModal()
- .find('button:contains("Confirm")')
- .should('not.be.disabled')
+ .should('have.length', 5)
+ .filter(':contains("Project 1")')
.click();
+ projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
workflowsPage.getters
.workflowCards()
@@ -537,44 +508,16 @@ describe('Projects', { disableAutoLogin: true }, () => {
projects
.getResourceMoveModal()
.should('be.visible')
- .find('button:contains("Next")')
+ .find('button:contains("Move credential")')
.should('be.disabled');
projects.getProjectMoveSelect().click();
getVisibleSelect()
.find('li')
- .should('have.length', 1)
- .first()
- .should('contain.text', 'Project 2')
+ .should('have.length', 5)
+ .filter(':contains("Project 2")')
.click();
- projects.getResourceMoveModal().find('button:contains("Next")').click();
-
- projects
- .getResourceMoveConfirmModal()
- .should('be.visible')
- .find('button:contains("Confirm")')
- .should('be.disabled');
+ projects.getResourceMoveModal().find('button:contains("Move credential")').click();
- projects
- .getResourceMoveConfirmModal()
- .find('input[type="checkbox"]')
- .first()
- .parents('label')
- .click();
- projects
- .getResourceMoveConfirmModal()
- .find('button:contains("Confirm")')
- .should('be.disabled');
- projects
- .getResourceMoveConfirmModal()
- .find('input[type="checkbox"]')
- .last()
- .parents('label')
- .click();
- projects
- .getResourceMoveConfirmModal()
- .find('button:contains("Confirm")')
- .should('not.be.disabled')
- .click();
credentialsPage.getters.credentialCards().should('not.have.length');
projects.getMenuItems().last().click();
projects.getProjectTabCredentials().click();
From c5e73a05940a6b30e0e0fdfe9a6631549093bb37 Mon Sep 17 00:00:00 2001
From: Danny Martini
Date: Mon, 9 Sep 2024 17:47:49 +0200
Subject: [PATCH 07/30] allow transferring workflows from any project type to
any other project type
---
.../cli/src/workflows/workflow.service.ee.ts | 8 -
.../workflows/workflows.controller.ee.test.ts | 193 ++++++++++--------
2 files changed, 106 insertions(+), 95 deletions(-)
diff --git a/packages/cli/src/workflows/workflow.service.ee.ts b/packages/cli/src/workflows/workflow.service.ee.ts
index 5456ac62681bd..4e329e7464416 100644
--- a/packages/cli/src/workflows/workflow.service.ee.ts
+++ b/packages/cli/src/workflows/workflow.service.ee.ts
@@ -285,14 +285,6 @@ export class EnterpriseWorkflowService {
"You can't transfer a workflow into the project that's already owning it.",
);
}
- if (sourceProject.type !== 'team' && sourceProject.type !== 'personal') {
- throw new TransferWorkflowError(
- 'You can only transfer workflows out of personal or team projects.',
- );
- }
- if (destinationProject.type !== 'team') {
- throw new TransferWorkflowError('You can only transfer workflows into team projects.');
- }
// 6. deactivate workflow if necessary
const wasActive = workflow.active;
diff --git a/packages/cli/test/integration/workflows/workflows.controller.ee.test.ts b/packages/cli/test/integration/workflows/workflows.controller.ee.test.ts
index c8f2db889f7da..6dbb968975855 100644
--- a/packages/cli/test/integration/workflows/workflows.controller.ee.test.ts
+++ b/packages/cli/test/integration/workflows/workflows.controller.ee.test.ts
@@ -1385,18 +1385,6 @@ describe('PUT /:workflowId/transfer', () => {
.expect(400);
});
- test('cannot transfer into a personal project', async () => {
- const sourceProject = await createTeamProject('Team Project', member);
-
- const workflow = await createWorkflow({}, sourceProject);
-
- await testServer
- .authAgentFor(member)
- .put(`/workflows/${workflow.id}/transfer`)
- .send({ destinationProjectId: memberPersonalProject.id })
- .expect(400);
- });
-
test('cannot transfer somebody elses workflow', async () => {
const destinationProject = await createTeamProject('Team Project', member);
@@ -1509,93 +1497,124 @@ describe('PUT /:workflowId/transfer', () => {
});
});
+ test('can transfer from team to a personal project', async () => {
+ // ARRANGE
+ const sourceProject = await createTeamProject('Team Project 1', member);
+ const workflow = await createWorkflow({}, sourceProject);
+
+ const destinationProject = memberPersonalProject;
+
+ // ACT
+ const response = await testServer
+ .authAgentFor(member)
+ .put(`/workflows/${workflow.id}/transfer`)
+ .send({ destinationProjectId: destinationProject.id })
+ .expect(200);
+
+ // ASSERT
+ expect(response.body).toEqual({});
+
+ const allSharings = await getWorkflowSharing(workflow);
+ expect(allSharings).toHaveLength(1);
+ expect(allSharings[0]).toMatchObject({
+ projectId: destinationProject.id,
+ workflowId: workflow.id,
+ role: 'workflow:owner',
+ });
+ });
+
test.each([
- ['owners', () => owner],
- ['admins', () => admin],
+ [
+ // user role
+ 'owners',
+ // source project type
+ 'team',
+ // destination project type
+ 'team',
+ // user
+ () => owner,
+ // source project
+ async () => await createTeamProject('Source Project'),
+ // destination project
+ async () => await createTeamProject('Destination Project'),
+ ],
+ [
+ 'owners',
+ 'team',
+ 'personal',
+ () => owner,
+ async () => await createTeamProject('Source Project'),
+ () => memberPersonalProject,
+ ],
+ [
+ 'owners',
+ 'personal',
+ 'team',
+ () => owner,
+ () => memberPersonalProject,
+ async () => await createTeamProject('Destination Project'),
+ ],
+
+ [
+ 'admins',
+ 'team',
+ 'team',
+ () => admin,
+ async () => await createTeamProject('Source Project'),
+ async () => await createTeamProject('Destination Project'),
+ ],
+ [
+ 'admins',
+ 'team',
+ 'personal',
+ () => admin,
+ async () => await createTeamProject('Source Project'),
+ () => memberPersonalProject,
+ ],
+ [
+ 'admins',
+ 'personal',
+ 'team',
+ () => admin,
+ () => memberPersonalProject,
+ async () => await createTeamProject('Destination Project'),
+ ],
])(
- 'global %s can always transfer from any personal or team project into any team project',
- async (_name, actor) => {
- //
+ 'global %s can transfer workflows from a %s project to a %s project',
+ async (
+ _roleName,
+ _sourceProjectName,
+ _destinationProjectName,
+ getActor,
+ getSourceProject,
+ getDestinationProject,
+ ) => {
// ARRANGE
- //
- const sourceProject = await createTeamProject('Source Project', member);
- const teamWorkflow = await createWorkflow({}, sourceProject);
-
- const personalWorkflow = await createWorkflow({}, member);
-
- const destinationProject = await createTeamProject('Destination Project', member);
+ const actor = getActor();
+ const sourceProject = await getSourceProject();
+ const destinationProject = await getDestinationProject();
+ const workflow = await createWorkflow({}, sourceProject);
- //
// ACT
- //
- const response1 = await testServer
- .authAgentFor(actor())
- .put(`/workflows/${teamWorkflow.id}/transfer`)
- .send({ destinationProjectId: destinationProject.id })
- .expect(200);
- const response2 = await testServer
- .authAgentFor(actor())
- .put(`/workflows/${personalWorkflow.id}/transfer`)
+ const response = await testServer
+ .authAgentFor(actor)
+ .put(`/workflows/${workflow.id}/transfer`)
.send({ destinationProjectId: destinationProject.id })
.expect(200);
- //
// ASSERT
- //
- expect(response1.body).toEqual({});
- expect(response2.body).toEqual({});
-
- {
- const allSharings = await getWorkflowSharing(teamWorkflow);
- expect(allSharings).toHaveLength(1);
- expect(allSharings[0]).toMatchObject({
- projectId: destinationProject.id,
- workflowId: teamWorkflow.id,
- role: 'workflow:owner',
- });
- }
-
- {
- const allSharings = await getWorkflowSharing(personalWorkflow);
- expect(allSharings).toHaveLength(1);
- expect(allSharings[0]).toMatchObject({
- projectId: destinationProject.id,
- workflowId: personalWorkflow.id,
- role: 'workflow:owner',
- });
- }
+ expect(response.body).toEqual({});
+
+ const allSharings = await getWorkflowSharing(workflow);
+ expect(allSharings).toHaveLength(1);
+ expect(allSharings[0]).toMatchObject({
+ projectId: destinationProject.id,
+ workflowId: workflow.id,
+ role: 'workflow:owner',
+ });
},
);
- test.each([
- ['owners', () => owner],
- ['admins', () => admin],
- ])('global %s cannot transfer into personal projects', async (_name, actor) => {
- //
- // ARRANGE
- //
- const sourceProject = await createTeamProject('Source Project', member);
- const teamWorkflow = await createWorkflow({}, sourceProject);
-
- const personalWorkflow = await createWorkflow({}, member);
-
- const destinationProject = anotherMemberPersonalProject;
-
- //
- // ACT & ASSERT
- //
- await testServer
- .authAgentFor(actor())
- .put(`/workflows/${teamWorkflow.id}/transfer`)
- .send({ destinationProjectId: destinationProject.id })
- .expect(400);
- await testServer
- .authAgentFor(actor())
- .put(`/workflows/${personalWorkflow.id}/transfer`)
- .send({ destinationProjectId: destinationProject.id })
- .expect(400);
- });
-
test('removes and re-adds the workflow from the active workflow manager during the transfer', async () => {
//
// ARRANGE
From fdfc2a4848e777f9f3c4889ba666f9a56c912491 Mon Sep 17 00:00:00 2001
From: Danny Martini
Date: Mon, 9 Sep 2024 18:35:49 +0200
Subject: [PATCH 08/30] clean up the tests
---
.../workflows/workflows.controller.ee.test.ts | 155 +++++-------------
1 file changed, 39 insertions(+), 116 deletions(-)
diff --git a/packages/cli/test/integration/workflows/workflows.controller.ee.test.ts b/packages/cli/test/integration/workflows/workflows.controller.ee.test.ts
index 6dbb968975855..2002843bfe829 100644
--- a/packages/cli/test/integration/workflows/workflows.controller.ee.test.ts
+++ b/packages/cli/test/integration/workflows/workflows.controller.ee.test.ts
@@ -5,6 +5,7 @@ import { v4 as uuid } from 'uuid';
import { ActiveWorkflowManager } from '@/active-workflow-manager';
import config from '@/config';
import type { Project } from '@/databases/entities/project';
+import type { ProjectRole } from '@/databases/entities/project-relation';
import type { User } from '@/databases/entities/user';
import { ProjectRepository } from '@/databases/repositories/project.repository';
import { WorkflowHistoryRepository } from '@/databases/repositories/workflow-history.repository';
@@ -1409,133 +1410,54 @@ describe('PUT /:workflowId/transfer', () => {
.expect(404);
});
- test('project:editors cannot transfer workflows', async () => {
- //
- // ARRANGE
- //
- const sourceProject = await createTeamProject();
- await linkUserToProject(member, sourceProject, 'project:editor');
-
- const workflow = await createWorkflow({}, sourceProject);
-
- const destinationProject = await createTeamProject();
- await linkUserToProject(member, destinationProject, 'project:admin');
-
- //
- // ACT & ASSERT
- //
- await testServer
- .authAgentFor(member)
- .put(`/workflows/${workflow.id}/transfer`)
- .send({ destinationProjectId: destinationProject.id })
- .expect(403);
- });
-
- test('transferring from a personal project to a team project severs all sharings', async () => {
- //
- // ARRANGE
- //
- const workflow = await createWorkflow({}, member);
-
- // these sharings should be deleted by the transfer
- await shareWorkflowWithUsers(workflow, [anotherMember, owner]);
-
- const destinationProject = await createTeamProject('Team Project', member);
-
- //
- // ACT
- //
- const response = await testServer
- .authAgentFor(member)
- .put(`/workflows/${workflow.id}/transfer`)
- .send({ destinationProjectId: destinationProject.id })
- .expect(200);
-
- //
- // ASSERT
- //
- expect(response.body).toEqual({});
-
- const allSharings = await getWorkflowSharing(workflow);
- expect(allSharings).toHaveLength(1);
- expect(allSharings[0]).toMatchObject({
- projectId: destinationProject.id,
- workflowId: workflow.id,
- role: 'workflow:owner',
- });
- });
-
- test('can transfer from team to another team project', async () => {
- //
- // ARRANGE
- //
- const sourceProject = await createTeamProject('Team Project 1', member);
- const workflow = await createWorkflow({}, sourceProject);
-
- const destinationProject = await createTeamProject('Team Project 2', member);
-
- //
- // ACT
- //
- const response = await testServer
- .authAgentFor(member)
- .put(`/workflows/${workflow.id}/transfer`)
- .send({ destinationProjectId: destinationProject.id })
- .expect(200);
-
- //
- // ASSERT
- //
- expect(response.body).toEqual({});
-
- const allSharings = await getWorkflowSharing(workflow);
- expect(allSharings).toHaveLength(1);
- expect(allSharings[0]).toMatchObject({
- projectId: destinationProject.id,
- workflowId: workflow.id,
- role: 'workflow:owner',
- });
- });
-
- test('can transfer from team to a personal project', async () => {
- // ARRANGE
- const sourceProject = await createTeamProject('Team Project 1', member);
- const workflow = await createWorkflow({}, sourceProject);
-
- const destinationProject = memberPersonalProject;
+ test.each(['project:editor', 'project:viewer'])(
+ '%ss cannot transfer workflows',
+ async (projectRole) => {
+ //
+ // ARRANGE
+ //
+ const sourceProject = await createTeamProject();
+ await linkUserToProject(member, sourceProject, projectRole);
- // ACT
- const response = await testServer
- .authAgentFor(member)
- .put(`/workflows/${workflow.id}/transfer`)
- .send({ destinationProjectId: destinationProject.id })
- .expect(200);
+ const workflow = await createWorkflow({}, sourceProject);
- // ASSERT
- expect(response.body).toEqual({});
+ const destinationProject = await createTeamProject();
+ await linkUserToProject(member, destinationProject, 'project:admin');
- const allSharings = await getWorkflowSharing(workflow);
- expect(allSharings).toHaveLength(1);
- expect(allSharings[0]).toMatchObject({
- projectId: destinationProject.id,
- workflowId: workflow.id,
- role: 'workflow:owner',
- });
- });
+ //
+ // ACT & ASSERT
+ //
+ await testServer
+ .authAgentFor(member)
+ .put(`/workflows/${workflow.id}/transfer`)
+ .send({ destinationProjectId: destinationProject.id })
+ .expect(403);
+ },
+ );
- test.each([
+ test.each<
[
// user role
- 'owners',
+ 'owners' | 'admins',
// source project type
- 'team',
+ 'team' | 'personal',
// destination project type
+ 'team' | 'personal',
+ // actor
+ () => User,
+ // source project
+ () => Promise | Project,
+ // destination project
+ () => Promise | Project,
+ ]
+ >([
+ // owner
+ [
+ 'owners',
+ 'team',
'team',
- // user
() => owner,
- // source project
async () => await createTeamProject('Source Project'),
- // destination project
async () => await createTeamProject('Destination Project'),
],
[
@@ -1555,6 +1477,7 @@ describe('PUT /:workflowId/transfer', () => {
async () => await createTeamProject('Destination Project'),
],
+ // admin
[
'admins',
'team',
From a2023cb257a65d3b5af45a10415976e00fdcb7e3 Mon Sep 17 00:00:00 2001
From: Danny Martini
Date: Mon, 9 Sep 2024 18:36:08 +0200
Subject: [PATCH 09/30] allow transferring credentials from any project type to
any other project type
---
.../src/credentials/credentials.service.ee.ts | 8 -
.../credentials/credentials.api.ee.test.ts | 277 +++++++-----------
2 files changed, 109 insertions(+), 176 deletions(-)
diff --git a/packages/cli/src/credentials/credentials.service.ee.ts b/packages/cli/src/credentials/credentials.service.ee.ts
index 116137374acf9..aad78fe7b7303 100644
--- a/packages/cli/src/credentials/credentials.service.ee.ts
+++ b/packages/cli/src/credentials/credentials.service.ee.ts
@@ -157,14 +157,6 @@ export class EnterpriseCredentialsService {
"You can't transfer a credential into the project that's already owning it.",
);
}
- if (sourceProject.type !== 'team' && sourceProject.type !== 'personal') {
- throw new TransferCredentialError(
- 'You can only transfer credentials out of personal or team projects.',
- );
- }
- if (destinationProject.type !== 'team') {
- throw new TransferCredentialError('You can only transfer credentials into team projects.');
- }
await this.sharedCredentialsRepository.manager.transaction(async (trx) => {
// 6. transfer the credential
diff --git a/packages/cli/test/integration/credentials/credentials.api.ee.test.ts b/packages/cli/test/integration/credentials/credentials.api.ee.test.ts
index b1c0bfab75755..5428cafbd4dd2 100644
--- a/packages/cli/test/integration/credentials/credentials.api.ee.test.ts
+++ b/packages/cli/test/integration/credentials/credentials.api.ee.test.ts
@@ -3,6 +3,7 @@ import { Container } from 'typedi';
import config from '@/config';
import type { Project } from '@/databases/entities/project';
+import type { ProjectRole } from '@/databases/entities/project-relation';
import type { User } from '@/databases/entities/user';
import { ProjectRepository } from '@/databases/repositories/project.repository';
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
@@ -1118,18 +1119,6 @@ describe('PUT /:credentialId/transfer', () => {
.expect(400);
});
- test('cannot transfer into a personal project', async () => {
- const credential = await saveCredential(randomCredentialPayload(), {
- user: member,
- });
-
- await testServer
- .authAgentFor(member)
- .put(`/credentials/${credential.id}/transfer`)
- .send({ destinationProjectId: memberPersonalProject.id })
- .expect(400);
- });
-
test('cannot transfer somebody elses credential', async () => {
const destinationProject = await createTeamProject('Destination Project', member);
@@ -1158,187 +1147,139 @@ describe('PUT /:credentialId/transfer', () => {
.expect(404);
});
- test('project:editors cannot transfer credentials', async () => {
- //
- // ARRANGE
- //
- const sourceProject = await createTeamProject('Source Project');
- await linkUserToProject(member, sourceProject, 'project:editor');
-
- const credential = await saveCredential(randomCredentialPayload(), {
- project: sourceProject,
- });
-
- const destinationProject = await createTeamProject('Destination Project', member);
-
- //
- // ACT & ASSERT
- //
- await testServer
- .authAgentFor(member)
- .put(`/credentials/${credential.id}/transfer`)
- .send({ destinationProjectId: destinationProject.id })
- .expect(403);
- });
-
- test('transferring from a personal project to a team project severs all sharings', async () => {
- //
- // ARRANGE
- //
- const credential = await saveCredential(randomCredentialPayload(), { user: member });
-
- // these sharings should be deleted by the transfer
- await shareCredentialWithUsers(credential, [anotherMember, owner]);
-
- const destinationProject = await createTeamProject('Destination Project', member);
-
- //
- // ACT
- //
- const response = await testServer
- .authAgentFor(member)
- .put(`/credentials/${credential.id}/transfer`)
- .send({ destinationProjectId: destinationProject.id })
- .expect(200);
-
- //
- // ASSERT
- //
- expect(response.body).toEqual({});
-
- const allSharings = await getCredentialSharings(credential);
- expect(allSharings).toHaveLength(1);
- expect(allSharings[0]).toMatchObject({
- projectId: destinationProject.id,
- credentialsId: credential.id,
- role: 'credential:owner',
- });
- });
-
- test('can transfer from team to another team project', async () => {
- //
- // ARRANGE
- //
- const sourceProject = await createTeamProject('Team Project 1', member);
- const credential = await saveCredential(randomCredentialPayload(), {
- project: sourceProject,
- });
-
- const destinationProject = await createTeamProject('Team Project 2', member);
-
- //
- // ACT
- //
- const response = await testServer
- .authAgentFor(member)
- .put(`/credentials/${credential.id}/transfer`)
- .send({ destinationProjectId: destinationProject.id })
- .expect(200);
-
- //
- // ASSERT
- //
- expect(response.body).toEqual({});
-
- const allSharings = await getCredentialSharings(credential);
- expect(allSharings).toHaveLength(1);
- expect(allSharings[0]).toMatchObject({
- projectId: destinationProject.id,
- credentialsId: credential.id,
- role: 'credential:owner',
- });
- });
-
- test.each([
- ['owners', () => owner],
- ['admins', () => admin],
- ])(
- '%s can always transfer from any personal or team project into any team project',
- async (_name, actor) => {
+ test.each(['project:editor', 'project:viewer'])(
+ '%ss cannot transfer credentials',
+ async (projectRole) => {
//
// ARRANGE
//
- const sourceProject = await createTeamProject('Source Project', member);
- const teamCredential = await saveCredential(randomCredentialPayload(), {
+ const sourceProject = await createTeamProject('Source Project');
+ await linkUserToProject(member, sourceProject, projectRole);
+
+ const credential = await saveCredential(randomCredentialPayload(), {
project: sourceProject,
});
- const personalCredential = await saveCredential(randomCredentialPayload(), { user: member });
-
const destinationProject = await createTeamProject('Destination Project', member);
//
- // ACT
+ // ACT & ASSERT
//
- const response1 = await testServer
- .authAgentFor(actor())
- .put(`/credentials/${teamCredential.id}/transfer`)
+ await testServer
+ .authAgentFor(member)
+ .put(`/credentials/${credential.id}/transfer`)
.send({ destinationProjectId: destinationProject.id })
- .expect(200);
- const response2 = await testServer
- .authAgentFor(actor())
- .put(`/credentials/${personalCredential.id}/transfer`)
+ .expect(403);
+ },
+ );
+
+ test.each<
+ [
+ // user role
+ 'owners' | 'admins',
+ // source project type
+ 'team' | 'personal',
+ // destination project type
+ 'team' | 'personal',
+ // actor
+ () => User,
+ // source project
+ () => Promise | Project,
+ // destination project
+ () => Promise | Project,
+ ]
+ >([
+ // owner
+ [
+ 'owners',
+ 'team',
+ 'team',
+ () => owner,
+ async () => await createTeamProject('Source Project'),
+ async () => await createTeamProject('Destination Project'),
+ ],
+ [
+ 'owners',
+ 'team',
+ 'personal',
+ () => owner,
+ async () => await createTeamProject('Source Project'),
+ () => memberPersonalProject,
+ ],
+ [
+ 'owners',
+ 'personal',
+ 'team',
+ () => owner,
+ () => memberPersonalProject,
+ async () => await createTeamProject('Destination Project'),
+ ],
+
+ // admin
+ [
+ 'admins',
+ 'team',
+ 'team',
+ () => admin,
+ async () => await createTeamProject('Source Project'),
+ async () => await createTeamProject('Destination Project'),
+ ],
+ [
+ 'admins',
+ 'team',
+ 'personal',
+ () => admin,
+ async () => await createTeamProject('Source Project'),
+ () => memberPersonalProject,
+ ],
+ [
+ 'admins',
+ 'personal',
+ 'team',
+ () => admin,
+ () => memberPersonalProject,
+ async () => await createTeamProject('Destination Project'),
+ ],
+ ])(
+ '%s can always transfer from a %s project to a %s project',
+ async (
+ _roleName,
+ _sourceProjectName,
+ _destinationProjectName,
+ getUser,
+ getSourceProject,
+ getDestinationProject,
+ ) => {
+ // ARRANGE
+ const user = getUser();
+ const sourceProject = await getSourceProject();
+ const destinationProject = await getDestinationProject();
+
+ const credential = await saveCredential(randomCredentialPayload(), {
+ project: sourceProject,
+ });
+
+ // ACT
+ const response = await testServer
+ .authAgentFor(user)
+ .put(`/credentials/${credential.id}/transfer`)
.send({ destinationProjectId: destinationProject.id })
.expect(200);
- //
// ASSERT
- //
- expect(response1.body).toEqual({});
- expect(response2.body).toEqual({});
-
- {
- const allSharings = await getCredentialSharings(teamCredential);
- expect(allSharings).toHaveLength(1);
- expect(allSharings[0]).toMatchObject({
- projectId: destinationProject.id,
- credentialsId: teamCredential.id,
- role: 'credential:owner',
- });
- }
+ expect(response.body).toEqual({});
{
- const allSharings = await getCredentialSharings(personalCredential);
+ const allSharings = await getCredentialSharings(credential);
expect(allSharings).toHaveLength(1);
expect(allSharings[0]).toMatchObject({
projectId: destinationProject.id,
- credentialsId: personalCredential.id,
+ credentialsId: credential.id,
role: 'credential:owner',
});
}
},
);
-
- test.each([
- ['owners', () => owner],
- ['admins', () => admin],
- ])('%s cannot transfer into personal projects', async (_name, actor) => {
- //
- // ARRANGE
- //
- const sourceProject = await createTeamProject('Source Project', member);
- const teamCredential = await saveCredential(randomCredentialPayload(), {
- project: sourceProject,
- });
-
- const personalCredential = await saveCredential(randomCredentialPayload(), { user: member });
-
- const destinationProject = anotherMemberPersonalProject;
-
- //
- // ACT & ASSERT
- //
- await testServer
- .authAgentFor(actor())
- .put(`/credentials/${teamCredential.id}/transfer`)
- .send({ destinationProjectId: destinationProject.id })
- .expect(400);
- await testServer
- .authAgentFor(actor())
- .put(`/credentials/${personalCredential.id}/transfer`)
- .send({ destinationProjectId: destinationProject.id })
- .expect(400);
- });
});
function validateMainCredentialData(credential: ListQuery.Credentials.WithOwnedByAndSharedWith) {
From 253eaa9b61c808578bffcd86dba2fb860e6c92d2 Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Tue, 10 Sep 2024 13:16:35 +0200
Subject: [PATCH 10/30] fix(editor): Remove View project link from toast when
the target project is a personal project
---
.../Projects/ProjectMoveResourceModal.vue | 11 ++++++-----
.../ProjectMoveSuccessToastMessage.vue | 19 +++++++++++--------
2 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
index b3f40380bc329..e2a4a4a4149da 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
@@ -36,7 +36,9 @@ const availableProjects = computed(() =>
.filter((p) => p.id !== props.data.resource.homeProject?.id)
.sort((a, b) => a.name?.localeCompare(b.name ?? '') ?? 0),
);
-
+const selectedProject = computed(() =>
+ availableProjects.value.find((p) => p.id === projectId.value),
+);
const isResourceInTeamProject = computed(() => isHomeProjectTeam(props.data.resource));
const isHomeProjectTeam = (resource: IWorkflowDb | ICredentialsResponse) =>
@@ -56,12 +58,12 @@ const closeModal = () => {
};
const moveResource = async () => {
- if (!projectId.value) return;
+ if (!selectedProject.value) return;
try {
await projectsStore.moveResourceToProject(
props.data.resourceType,
props.data.resource.id,
- projectId.value,
+ selectedProject.value.id,
);
closeModal();
telemetry.track(`User successfully moved ${props.data.resourceType}`, {
@@ -82,8 +84,7 @@ const moveResource = async () => {
resource: props.data.resource,
resourceType: props.data.resourceType,
resourceTypeLabel: props.data.resourceTypeLabel,
- projectId: projectId.value,
- projectName: availableProjects.value.find((p) => p.id === projectId.value)?.name ?? '',
+ targetProject: selectedProject.value,
}),
type: 'success',
});
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue b/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue
index b1366f963eae7..69278d08e3ae6 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue
@@ -1,41 +1,44 @@
{{ props.resourceTypeLabel }}
{{ props.resource.name }}
- {{ props.projectName }}
+ {{ props.targetProject.name }}
- {{ props.projectName }}
+ {{ props.targetProject.name }}
-
+
- {{ props.projectName }}
+ {{ props.targetProject.name }}
From 2619298fd65d97e221a93f65e9cbd2a03e2faf71 Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Tue, 10 Sep 2024 15:35:50 +0200
Subject: [PATCH 11/30] test(editor): Extend resource moving E2E tests
---
cypress/e2e/39-projects.cy.ts | 148 +++++++++++++++++++++++++++++++++-
1 file changed, 146 insertions(+), 2 deletions(-)
diff --git a/cypress/e2e/39-projects.cy.ts b/cypress/e2e/39-projects.cy.ts
index 90de91a5c33d8..f86e3f79cdcde 100644
--- a/cypress/e2e/39-projects.cy.ts
+++ b/cypress/e2e/39-projects.cy.ts
@@ -1,4 +1,10 @@
-import { INSTANCE_MEMBERS, MANUAL_TRIGGER_NODE_NAME, NOTION_NODE_NAME } from '../constants';
+import {
+ INSTANCE_ADMIN,
+ INSTANCE_MEMBERS,
+ INSTANCE_OWNER,
+ MANUAL_TRIGGER_NODE_NAME,
+ NOTION_NODE_NAME,
+} from '../constants';
import {
WorkflowsPage,
WorkflowPage,
@@ -497,9 +503,77 @@ describe('Projects', { disableAutoLogin: true }, () => {
.filter(':contains("Owned by me")')
.should('not.exist');
- // Move the credential from Project 1 to Project 2
+ // Move the workflow from Project 1 to Project 2
projects.getMenuItems().first().click();
workflowsPage.getters.workflowCards().should('have.length', 2);
+ workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
+ workflowsPage.getters.workflowMoveButton().click();
+
+ projects
+ .getResourceMoveModal()
+ .should('be.visible')
+ .find('button:contains("Move workflow")')
+ .should('be.disabled');
+ projects.getProjectMoveSelect().click();
+ getVisibleSelect()
+ .find('li')
+ .should('have.length', 5)
+ .filter(':contains("Project 2")')
+ .click();
+ projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
+
+ // Move the workflow from Project 2 to a member user
+ projects.getMenuItems().last().click();
+ workflowsPage.getters.workflowCards().should('have.length', 2);
+ workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
+ workflowsPage.getters.workflowMoveButton().click();
+
+ projects
+ .getResourceMoveModal()
+ .should('be.visible')
+ .find('button:contains("Move workflow")')
+ .should('be.disabled');
+ projects.getProjectMoveSelect().click();
+ getVisibleSelect()
+ .find('li')
+ .should('have.length', 5)
+ .filter(`:contains("${INSTANCE_MEMBERS[0].email}")`)
+ .click();
+
+ projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
+ workflowsPage.getters.workflowCards().should('have.length', 1);
+
+ // Move the workflow from member user back to Home
+ projects.getHomeButton().click();
+ workflowsPage.getters
+ .workflowCards()
+ .should('have.length', 3)
+ .filter(':has(.n8n-badge:contains("Project"))')
+ .should('have.length', 2);
+ workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
+ workflowsPage.getters.workflowMoveButton().click();
+
+ projects
+ .getResourceMoveModal()
+ .should('be.visible')
+ .find('button:contains("Move workflow")')
+ .should('be.disabled');
+ projects.getProjectMoveSelect().click();
+ getVisibleSelect()
+ .find('li')
+ .should('have.length', 5)
+ .filter(`:contains("${INSTANCE_OWNER.email}")`)
+ .click();
+
+ projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
+ workflowsPage.getters
+ .workflowCards()
+ .should('have.length', 3)
+ .filter(':contains("Owned by me")')
+ .should('have.length', 1);
+
+ // Move the credential from Project 1 to Project 2
+ projects.getMenuItems().first().click();
projects.getProjectTabCredentials().click();
credentialsPage.getters.credentialCards().should('have.length', 1);
credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
@@ -519,9 +593,79 @@ describe('Projects', { disableAutoLogin: true }, () => {
projects.getResourceMoveModal().find('button:contains("Move credential")').click();
credentialsPage.getters.credentialCards().should('not.have.length');
+
+ // Move the credential from Project 2 to admin user
projects.getMenuItems().last().click();
projects.getProjectTabCredentials().click();
credentialsPage.getters.credentialCards().should('have.length', 2);
+
+ credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
+ credentialsPage.getters.credentialMoveButton().click();
+
+ projects
+ .getResourceMoveModal()
+ .should('be.visible')
+ .find('button:contains("Move credential")')
+ .should('be.disabled');
+ projects.getProjectMoveSelect().click();
+ getVisibleSelect()
+ .find('li')
+ .should('have.length', 5)
+ .filter(`:contains("${INSTANCE_ADMIN.email}")`)
+ .click();
+ projects.getResourceMoveModal().find('button:contains("Move credential")').click();
+ credentialsPage.getters.credentialCards().should('have.length', 1);
+
+ // Move the credential from admin user back to instance owner
+ projects.getHomeButton().click();
+ projects.getProjectTabCredentials().click();
+ credentialsPage.getters.credentialCards().should('have.length', 3);
+
+ credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
+ credentialsPage.getters.credentialMoveButton().click();
+
+ projects
+ .getResourceMoveModal()
+ .should('be.visible')
+ .find('button:contains("Move credential")')
+ .should('be.disabled');
+ projects.getProjectMoveSelect().click();
+ getVisibleSelect()
+ .find('li')
+ .should('have.length', 5)
+ .filter(`:contains("${INSTANCE_OWNER.email}")`)
+ .click();
+ projects.getResourceMoveModal().find('button:contains("Move credential")').click();
+
+ credentialsPage.getters
+ .credentialCards()
+ .should('have.length', 3)
+ .filter(':contains("Owned by me")')
+ .should('have.length', 2);
+
+ // Move the credential from admin user back to its original project (Project 1)
+ credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
+ credentialsPage.getters.credentialMoveButton().click();
+
+ projects
+ .getResourceMoveModal()
+ .should('be.visible')
+ .find('button:contains("Move credential")')
+ .should('be.disabled');
+ projects.getProjectMoveSelect().click();
+ getVisibleSelect()
+ .find('li')
+ .should('have.length', 5)
+ .filter(':contains("Project 1")')
+ .click();
+ projects.getResourceMoveModal().find('button:contains("Move credential")').click();
+
+ projects.getMenuItems().first().click();
+ projects.getProjectTabCredentials().click();
+ credentialsPage.getters
+ .credentialCards()
+ .filter(':contains("Credential in Project 1")')
+ .should('have.length', 1);
});
it('should handle viewer role', () => {
From a2a8c3b7efe5e5f7140cb42c4422e958ba6056f7 Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Tue, 10 Sep 2024 17:38:56 +0200
Subject: [PATCH 12/30] test(editor): Add resource moving component tests
---
.../Projects/ProjectMoveResourceModal.test.ts | 27 ++++--
.../ProjectMoveSuccessToastMessage.test.ts | 87 +++++++++++++++++++
2 files changed, 109 insertions(+), 5 deletions(-)
create mode 100644 packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.test.ts
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.test.ts b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.test.ts
index 93e3249044a9b..0b9eea49bcf3b 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.test.ts
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.test.ts
@@ -1,8 +1,10 @@
-import { createPinia, setActivePinia } from 'pinia';
+import { createTestingPinia } from '@pinia/testing';
import { createComponentRenderer } from '@/__tests__/render';
import { PROJECT_MOVE_RESOURCE_MODAL } from '@/constants';
import ProjectMoveResourceModal from '@/components/Projects/ProjectMoveResourceModal.vue';
import { useTelemetry } from '@/composables/useTelemetry';
+import { mockedStore } from '@/__tests__/utils';
+import { useProjectsStore } from '@/stores/projects.store';
const renderComponent = createComponentRenderer(ProjectMoveResourceModal, {
global: {
@@ -19,25 +21,40 @@ let telemetry: ReturnType;
describe('ProjectMoveResourceModal', () => {
beforeEach(() => {
- setActivePinia(createPinia());
telemetry = useTelemetry();
});
it('should send telemetry when mounted', async () => {
+ const pinia = createTestingPinia();
const telemetryTrackSpy = vi.spyOn(telemetry, 'track');
+ const projectsStore = mockedStore(useProjectsStore);
+ projectsStore.projects = [
+ {
+ id: '1',
+ name: 'My Project',
+ type: 'personal',
+ role: 'project:personalOwner',
+ createdAt: '2021-01-01T00:00:00.000Z',
+ updatedAt: '2021-01-01T00:00:00.000Z',
+ },
+ ];
+
const props = {
modalName: PROJECT_MOVE_RESOURCE_MODAL,
data: {
resourceType: 'workflow',
+ resourceTypeLabel: 'Workflow',
resource: {
id: '1',
+ homeProject: {
+ id: '2',
+ name: 'My Project',
+ },
},
- projectId: '1',
- projectName: 'My Project',
},
};
- renderComponent({ props });
+ renderComponent({ props, pinia });
expect(telemetryTrackSpy).toHaveBeenCalledWith(
'User clicked to move a workflow',
expect.objectContaining({ workflow_id: '1' }),
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.test.ts b/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.test.ts
new file mode 100644
index 0000000000000..47e16ae97a753
--- /dev/null
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.test.ts
@@ -0,0 +1,87 @@
+import { createComponentRenderer } from '@/__tests__/render';
+import ProjectMoveSuccessToastMessage from '@/components/Projects/ProjectMoveSuccessToastMessage.vue';
+import { ResourceType } from '@/utils/projects.utils';
+import { VIEWS } from '@/constants';
+import { ProjectTypes } from '@/types/projects.types';
+
+const renderComponent = createComponentRenderer(ProjectMoveSuccessToastMessage, {
+ global: {
+ stubs: {
+ 'router-link': {
+ template: '',
+ },
+ },
+ },
+});
+
+describe('ProjectMoveSuccessToastMessage', () => {
+ it('should show credentials message when the moved resource is a workflow', async () => {
+ const props = {
+ routeName: VIEWS.PROJECTS_WORKFLOWS,
+ resource: {
+ id: '1',
+ name: 'My Workflow',
+ homeProject: {
+ id: '2',
+ name: 'My Project',
+ },
+ },
+ resourceType: ResourceType.Workflow,
+ resourceTypeLabel: 'Workflow',
+ targetProject: {
+ id: '2',
+ name: 'My Project',
+ },
+ };
+ const { getByText } = renderComponent({ props });
+ expect(getByText(/Please double check any credentials/)).toBeInTheDocument();
+ });
+
+ it('should show link if the target project type is team project', async () => {
+ const props = {
+ routeName: VIEWS.PROJECTS_WORKFLOWS,
+ resource: {
+ id: '1',
+ name: 'My Workflow',
+ homeProject: {
+ id: '2',
+ name: 'My Project',
+ },
+ },
+ resourceType: ResourceType.Workflow,
+ resourceTypeLabel: 'workflow',
+ targetProject: {
+ id: '2',
+ name: 'Team Project',
+ type: ProjectTypes.Team,
+ },
+ };
+ const { getByRole } = renderComponent({ props });
+ expect(getByRole('link')).toBeInTheDocument();
+ });
+
+ it('should show only general text when moved resource is credential and moved to a personal project', async () => {
+ const props = {
+ routeName: VIEWS.PROJECTS_WORKFLOWS,
+ resource: {
+ id: '1',
+ name: 'Notion API',
+ homeProject: {
+ id: '2',
+ name: 'My Project',
+ },
+ },
+ resourceType: ResourceType.Credential,
+ resourceTypeLabel: 'credential',
+ targetProject: {
+ id: '2',
+ name: 'Personal Project',
+ type: ProjectTypes.Personal,
+ },
+ };
+ const { getByText, queryByText, queryByRole } = renderComponent({ props });
+ expect(getByText('Notion API credential was moved to Personal Project.')).toBeInTheDocument();
+ expect(queryByText(/Please double check any credentials/)).not.toBeInTheDocument();
+ expect(queryByRole('link')).not.toBeInTheDocument();
+ });
+});
From b292ba2b0c9d629673886307e07e4eb8b154e6fd Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Thu, 12 Sep 2024 15:42:13 +0200
Subject: [PATCH 13/30] fix(editor): Use the correct list of projects
---
.../CredentialEdit/CredentialSharing.ee.vue | 4 ++--
.../Projects/ProjectMoveResourceModal.vue | 9 +++++++--
.../src/components/WorkflowShareModal.ee.vue | 7 +++++--
.../forms/ResourceFiltersDropdown.vue | 9 +++++----
.../editor-ui/src/stores/projects.store.ts | 18 ++++++++++++++++++
.../src/views/ProjectSettings.test.ts | 4 +---
.../editor-ui/src/views/ProjectSettings.vue | 6 ++++--
.../editor-ui/src/views/WorkflowsView.test.ts | 2 +-
packages/editor-ui/src/views/WorkflowsView.vue | 2 +-
9 files changed, 44 insertions(+), 17 deletions(-)
diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue
index 1c2242195c713..255058ea6eb11 100644
--- a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue
+++ b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue
@@ -62,7 +62,7 @@ const credentialDataHomeProject = computed(() =>
});
const projects = computed(() => {
- return projectsStore.projects.filter(
+ return projectsStore.availableProjects.filter(
(project) =>
project.id !== props.credential?.homeProject?.id &&
project.id !== credentialDataHomeProject.value?.id,
@@ -103,7 +103,7 @@ watch(
);
onMounted(async () => {
- await Promise.all([usersStore.fetchUsers(), projectsStore.getAllProjects()]);
+ await Promise.all([usersStore.fetchUsers(), projectsStore.getAvailableProjects()]);
});
function goToUpgrade() {
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
index e2a4a4a4149da..654941b19d6db 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
@@ -11,6 +11,7 @@ import { useTelemetry } from '@/composables/useTelemetry';
import { ProjectTypes } from '@/types/projects.types';
import ProjectMoveSuccessToastMessage from '@/components/Projects/ProjectMoveSuccessToastMessage.vue';
import { useToast } from '@/composables/useToast';
+import { getResourcePermissions } from '@/permissions';
const props = defineProps<{
modalName: string;
@@ -32,8 +33,12 @@ const processedName = computed(
() => processProjectName(props.data.resource.homeProject?.name ?? '') ?? '',
);
const availableProjects = computed(() =>
- projectsStore.projects
- .filter((p) => p.id !== props.data.resource.homeProject?.id)
+ projectsStore.availableProjects
+ .filter(
+ (p) =>
+ p.id !== props.data.resource.homeProject?.id &&
+ (!p.scopes || getResourcePermissions(p.scopes)[props.data.resourceType].create),
+ )
.sort((a, b) => a.name?.localeCompare(b.name ?? '') ?? 0),
);
const selectedProject = computed(() =>
diff --git a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
index 886bd90003e11..64b58ec974246 100644
--- a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
+++ b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
@@ -111,7 +111,7 @@ export default defineComponent({
return this.workflowsEEStore.getWorkflowOwnerName(`${this.workflow.id}`);
},
projects(): ProjectListItem[] {
- return this.projectsStore.personalProjects.filter(
+ return this.projectsStore.availableProjects.filter(
(project) => project.id !== this.workflow.homeProject?.id,
);
},
@@ -239,7 +239,10 @@ export default defineComponent({
},
async initialize() {
if (this.isSharingEnabled) {
- await Promise.all([this.usersStore.fetchUsers(), this.projectsStore.getAllProjects()]);
+ await Promise.all([
+ this.usersStore.fetchUsers(),
+ this.projectsStore.getAvailableProjects(),
+ ]);
if (this.workflow.id !== PLACEHOLDER_EMPTY_WORKFLOW_ID) {
await this.workflowsStore.fetchWorkflow(this.workflow.id);
diff --git a/packages/editor-ui/src/components/forms/ResourceFiltersDropdown.vue b/packages/editor-ui/src/components/forms/ResourceFiltersDropdown.vue
index d5bd8cc82e33a..470d0b5fd42a2 100644
--- a/packages/editor-ui/src/components/forms/ResourceFiltersDropdown.vue
+++ b/packages/editor-ui/src/components/forms/ResourceFiltersDropdown.vue
@@ -63,10 +63,11 @@ export default defineComponent({
},
},
async beforeMount() {
- await this.projectsStore.getAllProjects();
+ await this.projectsStore.getAvailableProjects();
this.selectedProject =
- this.projectsStore.projects.find((project) => project.id === this.modelValue.homeProject) ??
- null;
+ this.projectsStore.availableProjects.find(
+ (project) => project.id === this.modelValue.homeProject,
+ ) ?? null;
},
methods: {
setKeyValue(key: string, value: unknown) {
@@ -126,7 +127,7 @@ export default defineComponent({
/>
{
const route = useRoute();
@@ -26,6 +28,7 @@ export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
const settingsStore = useSettingsStore();
const workflowsStore = useWorkflowsStore();
const credentialsStore = useCredentialsStore();
+ const usersStore = useUsersStore();
const projects = ref([]);
const myProjects = ref([]);
@@ -38,6 +41,11 @@ export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
});
const projectNavActiveIdState = ref(null);
+ const globalProjectPermissions = computed(
+ () => getResourcePermissions(usersStore.currentUser?.globalScopes).project,
+ );
+ const availableProjects = computed(() => globalProjectPermissions.value.list ? projects.value : myProjects.value);
+
const currentProjectId = computed(
() =>
(route.params?.projectId as string | undefined) ??
@@ -91,6 +99,14 @@ export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
personalProject.value = await projectsApi.getPersonalProject(rootStore.restApiContext);
};
+ const getAvailableProjects = async () => {
+ if (globalProjectPermissions.value.list) {
+ await getAllProjects();
+ } else {
+ await getMyProjects();
+ }
+ };
+
const fetchProject = async (id: string) =>
await projectsApi.getProject(rootStore.restApiContext, id);
@@ -189,6 +205,7 @@ export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
return {
projects,
+ availableProjects,
myProjects,
personalProject,
currentProject,
@@ -206,6 +223,7 @@ export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
getAllProjects,
getMyProjects,
getPersonalProject,
+ getAvailableProjects,
fetchProject,
getProject,
createProject,
diff --git a/packages/editor-ui/src/views/ProjectSettings.test.ts b/packages/editor-ui/src/views/ProjectSettings.test.ts
index 13a8707109a4f..4f843bad9c799 100644
--- a/packages/editor-ui/src/views/ProjectSettings.test.ts
+++ b/packages/editor-ui/src/views/ProjectSettings.test.ts
@@ -51,9 +51,7 @@ describe('ProjectSettings', () => {
settingsStore = useSettingsStore();
vi.spyOn(usersStore, 'fetchUsers').mockImplementation(async () => await Promise.resolve());
- vi.spyOn(projectsStore, 'getAllProjects').mockImplementation(
- async () => await Promise.resolve(),
- );
+ vi.spyOn(projectsStore, 'getAvailableProjects').mockImplementation(async () => {});
vi.spyOn(projectsStore, 'projects', 'get').mockReturnValue(projects);
vi.spyOn(settingsStore, 'settings', 'get').mockReturnValue({
enterprise: {
diff --git a/packages/editor-ui/src/views/ProjectSettings.vue b/packages/editor-ui/src/views/ProjectSettings.vue
index dffc077b48389..4e1d9e634e3c5 100644
--- a/packages/editor-ui/src/views/ProjectSettings.vue
+++ b/packages/editor-ui/src/views/ProjectSettings.vue
@@ -60,7 +60,9 @@ const usersList = computed(() =>
);
const projects = computed(() =>
- projectsStore.projects.filter((project) => project.id !== projectsStore.currentProjectId),
+ projectsStore.availableProjects.filter(
+ (project) => project.id !== projectsStore.currentProjectId,
+ ),
);
const projectRoles = computed(() =>
rolesStore.processedProjectRoles.map((role) => ({
@@ -200,7 +202,7 @@ const onSubmit = async () => {
};
const onDelete = async () => {
- await projectsStore.getAllProjects();
+ await projectsStore.getAvailableProjects();
dialogVisible.value = true;
};
diff --git a/packages/editor-ui/src/views/WorkflowsView.test.ts b/packages/editor-ui/src/views/WorkflowsView.test.ts
index 226f621ee323c..ba5585721519e 100644
--- a/packages/editor-ui/src/views/WorkflowsView.test.ts
+++ b/packages/editor-ui/src/views/WorkflowsView.test.ts
@@ -61,7 +61,7 @@ describe('WorkflowsView', () => {
usersStore = useUsersStore();
projectsStore = useProjectsStore();
- vi.spyOn(projectsStore, 'getAllProjects').mockImplementation(async () => {});
+ vi.spyOn(projectsStore, 'getAvailableProjects').mockImplementation(async () => {});
await settingsStore.getSettings();
await usersStore.fetchUsers();
diff --git a/packages/editor-ui/src/views/WorkflowsView.vue b/packages/editor-ui/src/views/WorkflowsView.vue
index 3bc5a8b914bcb..9bb5a51530baf 100644
--- a/packages/editor-ui/src/views/WorkflowsView.vue
+++ b/packages/editor-ui/src/views/WorkflowsView.vue
@@ -266,7 +266,7 @@ const WorkflowsView = defineComponent({
const filtersToApply: { [key: string]: string | string[] | boolean } = {};
if (homeProject && typeof homeProject === 'string') {
- await this.projectsStore.getAllProjects();
+ await this.projectsStore.getAvailableProjects();
if (this.isValidProjectId(homeProject)) {
filtersToApply.homeProject = homeProject;
}
From 0a01f3f16de056a4b401663b68071ee2fcbecd1e Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Thu, 12 Sep 2024 16:46:23 +0200
Subject: [PATCH 14/30] fix(editor): Fix unit test and lint issue
---
packages/editor-ui/src/stores/projects.store.ts | 4 +++-
packages/editor-ui/src/views/ProjectSettings.test.ts | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/editor-ui/src/stores/projects.store.ts b/packages/editor-ui/src/stores/projects.store.ts
index 5556f91e2bb3d..21b275ef2fea0 100644
--- a/packages/editor-ui/src/stores/projects.store.ts
+++ b/packages/editor-ui/src/stores/projects.store.ts
@@ -44,7 +44,9 @@ export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
const globalProjectPermissions = computed(
() => getResourcePermissions(usersStore.currentUser?.globalScopes).project,
);
- const availableProjects = computed(() => globalProjectPermissions.value.list ? projects.value : myProjects.value);
+ const availableProjects = computed(() =>
+ globalProjectPermissions.value.list ? projects.value : myProjects.value,
+ );
const currentProjectId = computed(
() =>
diff --git a/packages/editor-ui/src/views/ProjectSettings.test.ts b/packages/editor-ui/src/views/ProjectSettings.test.ts
index 4f843bad9c799..b8dde05e60db8 100644
--- a/packages/editor-ui/src/views/ProjectSettings.test.ts
+++ b/packages/editor-ui/src/views/ProjectSettings.test.ts
@@ -52,7 +52,7 @@ describe('ProjectSettings', () => {
vi.spyOn(usersStore, 'fetchUsers').mockImplementation(async () => await Promise.resolve());
vi.spyOn(projectsStore, 'getAvailableProjects').mockImplementation(async () => {});
- vi.spyOn(projectsStore, 'projects', 'get').mockReturnValue(projects);
+ vi.spyOn(projectsStore, 'availableProjects', 'get').mockReturnValue(projects);
vi.spyOn(settingsStore, 'settings', 'get').mockReturnValue({
enterprise: {
projects: {
From 8efdad55167a747876b790d5ea357feab2e540a1 Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Fri, 13 Sep 2024 11:40:15 +0200
Subject: [PATCH 15/30] fix(editor): Fix sharing
---
.../src/components/CredentialEdit/CredentialSharing.ee.vue | 4 ++--
packages/editor-ui/src/components/WorkflowShareModal.ee.vue | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue
index 255058ea6eb11..1c2242195c713 100644
--- a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue
+++ b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue
@@ -62,7 +62,7 @@ const credentialDataHomeProject = computed(() =>
});
const projects = computed(() => {
- return projectsStore.availableProjects.filter(
+ return projectsStore.projects.filter(
(project) =>
project.id !== props.credential?.homeProject?.id &&
project.id !== credentialDataHomeProject.value?.id,
@@ -103,7 +103,7 @@ watch(
);
onMounted(async () => {
- await Promise.all([usersStore.fetchUsers(), projectsStore.getAvailableProjects()]);
+ await Promise.all([usersStore.fetchUsers(), projectsStore.getAllProjects()]);
});
function goToUpgrade() {
diff --git a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
index 64b58ec974246..cddeb54df20b0 100644
--- a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
+++ b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
@@ -111,7 +111,7 @@ export default defineComponent({
return this.workflowsEEStore.getWorkflowOwnerName(`${this.workflow.id}`);
},
projects(): ProjectListItem[] {
- return this.projectsStore.availableProjects.filter(
+ return this.projectsStore.projects.filter(
(project) => project.id !== this.workflow.homeProject?.id,
);
},
@@ -241,7 +241,7 @@ export default defineComponent({
if (this.isSharingEnabled) {
await Promise.all([
this.usersStore.fetchUsers(),
- this.projectsStore.getAvailableProjects(),
+ this.projectsStore.getAllProjects(),
]);
if (this.workflow.id !== PLACEHOLDER_EMPTY_WORKFLOW_ID) {
From a95908ab6cda21970b3c61120bc5d2631028c5fb Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Fri, 13 Sep 2024 12:35:21 +0200
Subject: [PATCH 16/30] fix(editor): Fix linting
---
packages/editor-ui/src/components/WorkflowShareModal.ee.vue | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
index cddeb54df20b0..ea4fbd88dd17b 100644
--- a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
+++ b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
@@ -239,10 +239,7 @@ export default defineComponent({
},
async initialize() {
if (this.isSharingEnabled) {
- await Promise.all([
- this.usersStore.fetchUsers(),
- this.projectsStore.getAllProjects(),
- ]);
+ await Promise.all([this.usersStore.fetchUsers(), this.projectsStore.getAllProjects()]);
if (this.workflow.id !== PLACEHOLDER_EMPTY_WORKFLOW_ID) {
await this.workflowsStore.fetchWorkflow(this.workflow.id);
From 84a00d2640db046a840e975d6fa1c38a84eaabcb Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Fri, 13 Sep 2024 12:41:37 +0200
Subject: [PATCH 17/30] fix(editor): Fix workflow sharing
---
packages/editor-ui/src/components/WorkflowShareModal.ee.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
index ea4fbd88dd17b..886bd90003e11 100644
--- a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
+++ b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
@@ -111,7 +111,7 @@ export default defineComponent({
return this.workflowsEEStore.getWorkflowOwnerName(`${this.workflow.id}`);
},
projects(): ProjectListItem[] {
- return this.projectsStore.projects.filter(
+ return this.projectsStore.personalProjects.filter(
(project) => project.id !== this.workflow.homeProject?.id,
);
},
From 8a8fdd7d2724a41bf544f9e24de6eacae86bf429 Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Fri, 13 Sep 2024 13:07:35 +0200
Subject: [PATCH 18/30] fix(editor): Fix workflow filtering
---
packages/editor-ui/src/views/WorkflowsView.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/editor-ui/src/views/WorkflowsView.vue b/packages/editor-ui/src/views/WorkflowsView.vue
index 9bb5a51530baf..7400c5c51a20e 100644
--- a/packages/editor-ui/src/views/WorkflowsView.vue
+++ b/packages/editor-ui/src/views/WorkflowsView.vue
@@ -258,7 +258,7 @@ const WorkflowsView = defineComponent({
});
},
isValidProjectId(projectId: string) {
- return this.projectsStore.projects.some((project) => project.id === projectId);
+ return this.projectsStore.availableProjects.some((project) => project.id === projectId);
},
async setFiltersFromQueryString() {
const { tags, status, search, homeProject } = this.$route.query;
From 2b2bc625617459c32bb6428aa05af91809d31949 Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Tue, 17 Sep 2024 14:06:18 +0200
Subject: [PATCH 19/30] fix(editor): Fix project display name
---
.../Projects/ProjectMoveResourceModal.vue | 24 +++++------
.../ProjectMoveSuccessToastMessage.vue | 41 +++++++++++--------
2 files changed, 35 insertions(+), 30 deletions(-)
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
index 654941b19d6db..f1272fa99cea3 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
@@ -129,22 +129,18 @@ onMounted(() => {
>{{ props.data.resource.name }}
- {{
- i18n.baseText('projects.move.resource.modal.message.team', {
- interpolate: {
- resourceHomeProjectName: processedName,
- },
- })
- }}
+
+ {{ processedName }}
+
- {{
- i18n.baseText('projects.move.resource.modal.message.personal', {
- interpolate: {
- resourceHomeProjectName: processedName,
- },
- })
- }}
+
+ {{ processedName }}
+
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue b/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue
index 69278d08e3ae6..7be195d693ed5 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue
@@ -1,7 +1,7 @@
{{ props.resourceTypeLabel }}
- {{ props.resource.name }}
- {{ props.targetProject.name }}
+ {{ props.resource.name }}
+ {{ projectName }}
-
-
+
- {{ props.targetProject.name }}
+ {{ projectName }}
-
-
+
+
- {{ props.targetProject.name }}
+ {{ projectName }}
-
-
+
+
From 3fab9fd174fa5c8743f66a52d6be7b5646c59968 Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Tue, 17 Sep 2024 16:02:40 +0200
Subject: [PATCH 20/30] fix(editor): Truncating long project names
---
packages/design-system/src/utils/index.ts | 1 +
.../src/components/Projects/ProjectMoveSuccessToastMessage.vue | 3 ++-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/design-system/src/utils/index.ts b/packages/design-system/src/utils/index.ts
index be6ddc63754b0..d029baaa268e6 100644
--- a/packages/design-system/src/utils/index.ts
+++ b/packages/design-system/src/utils/index.ts
@@ -5,3 +5,4 @@ export * from './typeguards';
export * from './uid';
export * from './valueByPath';
export * from './testUtils';
+export * from './string';
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue b/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue
index 7be195d693ed5..8bf8fe2af4e72 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.vue
@@ -1,5 +1,6 @@
From e5232e0e8b27429ec0661467eb48fcd58bcf0dee Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Tue, 17 Sep 2024 16:48:53 +0200
Subject: [PATCH 21/30] fix(editor): Truncating long node name
---
packages/editor-ui/src/components/InputNodeSelect.vue | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/packages/editor-ui/src/components/InputNodeSelect.vue b/packages/editor-ui/src/components/InputNodeSelect.vue
index c7550a76c6a53..d0ec011e0778c 100644
--- a/packages/editor-ui/src/components/InputNodeSelect.vue
+++ b/packages/editor-ui/src/components/InputNodeSelect.vue
@@ -7,6 +7,7 @@ import { isPresent } from '@/utils/typesUtils';
import type { IConnectedNode, Workflow } from 'n8n-workflow';
import { computed } from 'vue';
import NodeIcon from './NodeIcon.vue';
+import { truncate } from 'n8n-design-system';
type Props = {
nodes: IConnectedNode[];
@@ -100,11 +101,7 @@ function getMultipleNodesText(nodeName: string): string {
}
function title(nodeName: string, length = 30) {
- const truncated = nodeName.substring(0, length);
- if (truncated.length < nodeName.length) {
- return `${truncated}...`;
- }
- return truncated;
+ return truncate(nodeName, length);
}
function subtitle(nodeName: string, depth: number) {
From 2f35e46730953fdd23e60a81e7d0608671444dd9 Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Wed, 18 Sep 2024 14:07:48 +0200
Subject: [PATCH 22/30] fix(editor): Update unit test
---
.../Projects/ProjectMoveSuccessToastMessage.test.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.test.ts b/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.test.ts
index 47e16ae97a753..1900173093186 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.test.ts
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveSuccessToastMessage.test.ts
@@ -15,7 +15,7 @@ const renderComponent = createComponentRenderer(ProjectMoveSuccessToastMessage,
});
describe('ProjectMoveSuccessToastMessage', () => {
- it('should show credentials message when the moved resource is a workflow', async () => {
+ it('should show credentials message if the resource is a workflow', async () => {
const props = {
routeName: VIEWS.PROJECTS_WORKFLOWS,
resource: {
@@ -60,7 +60,7 @@ describe('ProjectMoveSuccessToastMessage', () => {
expect(getByRole('link')).toBeInTheDocument();
});
- it('should show only general text when moved resource is credential and moved to a personal project', async () => {
+ it('should show only general if the resource is credential and moved to a personal project', async () => {
const props = {
routeName: VIEWS.PROJECTS_WORKFLOWS,
resource: {
@@ -80,7 +80,7 @@ describe('ProjectMoveSuccessToastMessage', () => {
},
};
const { getByText, queryByText, queryByRole } = renderComponent({ props });
- expect(getByText('Notion API credential was moved to Personal Project.')).toBeInTheDocument();
+ expect(getByText(/credential was moved to /)).toBeInTheDocument();
expect(queryByText(/Please double check any credentials/)).not.toBeInTheDocument();
expect(queryByRole('link')).not.toBeInTheDocument();
});
From 81427f4233e7f93cd1ed25aee9b02e9b6c118af4 Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Wed, 18 Sep 2024 14:22:58 +0200
Subject: [PATCH 23/30] fix(editor): Add no available projects state to move
modal
---
.../Projects/ProjectMoveResourceModal.test.ts | 26 ++++++++++++++++++-
.../Projects/ProjectMoveResourceModal.vue | 7 ++++-
.../src/plugins/i18n/locales/en.json | 1 +
3 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.test.ts b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.test.ts
index 0b9eea49bcf3b..b70d843b6d7b2 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.test.ts
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.test.ts
@@ -29,7 +29,7 @@ describe('ProjectMoveResourceModal', () => {
const telemetryTrackSpy = vi.spyOn(telemetry, 'track');
const projectsStore = mockedStore(useProjectsStore);
- projectsStore.projects = [
+ projectsStore.availableProjects = [
{
id: '1',
name: 'My Project',
@@ -60,4 +60,28 @@ describe('ProjectMoveResourceModal', () => {
expect.objectContaining({ workflow_id: '1' }),
);
});
+
+ it('should show no available projects message', async () => {
+ const pinia = createTestingPinia();
+
+ const projectsStore = mockedStore(useProjectsStore);
+ projectsStore.availableProjects = [];
+
+ const props = {
+ modalName: PROJECT_MOVE_RESOURCE_MODAL,
+ data: {
+ resourceType: 'workflow',
+ resourceTypeLabel: 'Workflow',
+ resource: {
+ id: '1',
+ homeProject: {
+ id: '2',
+ name: 'My Project',
+ },
+ },
+ },
+ };
+ const { getByText } = renderComponent({ props, pinia });
+ expect(getByText(/Currently there are not any projects or users available/)).toBeVisible();
+ });
});
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
index f1272fa99cea3..95a97607ce68e 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
@@ -146,7 +146,7 @@ onMounted(() => {
-
+
{
>
+
{{
+ i18n.baseText('projects.move.resource.modal.message.noProjects', {
+ interpolate: { resourceTypeLabel: props.data.resourceTypeLabel },
+ })
+ }}
diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json
index e6bb420678b8c..ad2cfeeffd1fe 100644
--- a/packages/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/editor-ui/src/plugins/i18n/locales/en.json
@@ -2523,6 +2523,7 @@
"projects.move.resource.modal.message.note": "Note",
"projects.move.resource.modal.message.sharingNote": "{note}: Moving will remove any existing sharing for this {resourceTypeLabel}.",
"projects.move.resource.modal.message.sharingInfo": "(Currently shared with {numberOfProjects} project) | (Currently shared with {numberOfProjects} projects)",
+ "projects.move.resource.modal.message.noProjects": "Currently there are not any projects or users available for you to move this {resourceTypeLabel} to.",
"projects.move.resource.modal.button": "Move {resourceTypeLabel}",
"projects.move.resource.error.title": "Error moving {resourceName} {resourceTypeLabel}",
"projects.move.resource.success.title": "Successfully moved {resourceTypeLabel}",
From 677d1ae052648c26dfef6cb9407f98056de5b26f Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Wed, 18 Sep 2024 14:44:43 +0200
Subject: [PATCH 24/30] fix(editor): Truncate project name in cards
---
packages/design-system/src/directives/n8n-truncate.test.ts | 6 +++---
packages/design-system/src/utils/string.test.ts | 4 ++--
packages/design-system/src/utils/string.ts | 2 +-
.../editor-ui/src/components/Projects/ProjectCardBadge.vue | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/packages/design-system/src/directives/n8n-truncate.test.ts b/packages/design-system/src/directives/n8n-truncate.test.ts
index 89cd771283517..309cab456374e 100644
--- a/packages/design-system/src/directives/n8n-truncate.test.ts
+++ b/packages/design-system/src/directives/n8n-truncate.test.ts
@@ -24,7 +24,7 @@ describe('Directive n8n-truncate', () => {
},
},
);
- expect(html()).toBe('This is a very long text that...
');
+ expect(html()).toBe('This is a very long text that ...
');
});
it('should truncate text to 30 chars in case of wrong argument', async () => {
@@ -48,7 +48,7 @@ describe('Directive n8n-truncate', () => {
},
},
);
- expect(html()).toBe('This is a very long text that...
');
+ expect(html()).toBe('This is a very long text that ...
');
});
it('should truncate text to given length', async () => {
@@ -72,6 +72,6 @@ describe('Directive n8n-truncate', () => {
},
},
);
- expect(html()).toBe('This is a very long text...
');
+ expect(html()).toBe('This is a very long text ...
');
});
});
diff --git a/packages/design-system/src/utils/string.test.ts b/packages/design-system/src/utils/string.test.ts
index 6f65775f9ac67..0517d260d4e98 100644
--- a/packages/design-system/src/utils/string.test.ts
+++ b/packages/design-system/src/utils/string.test.ts
@@ -4,13 +4,13 @@ describe('Utils string', () => {
describe('truncate', () => {
it('should truncate text to 30 chars by default', () => {
expect(truncate('This is a very long text that should be truncated')).toBe(
- 'This is a very long text that...',
+ 'This is a very long text that ...',
);
});
it('should truncate text to given length', () => {
expect(truncate('This is a very long text that should be truncated', 25)).toBe(
- 'This is a very long text...',
+ 'This is a very long text ...',
);
});
});
diff --git a/packages/design-system/src/utils/string.ts b/packages/design-system/src/utils/string.ts
index 9170b57c00733..1c2b2aecfde09 100644
--- a/packages/design-system/src/utils/string.ts
+++ b/packages/design-system/src/utils/string.ts
@@ -1,2 +1,2 @@
export const truncate = (text: string, length = 30): string =>
- text.length > length ? text.slice(0, length).trim() + '...' : text;
+ text.length > length ? text.slice(0, length) + '...' : text;
diff --git a/packages/editor-ui/src/components/Projects/ProjectCardBadge.vue b/packages/editor-ui/src/components/Projects/ProjectCardBadge.vue
index e0ff41f5a85f7..ef675aa23261c 100644
--- a/packages/editor-ui/src/components/Projects/ProjectCardBadge.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectCardBadge.vue
@@ -119,7 +119,7 @@ const badgeTooltip = computed(() => {
- {{ badgeText }}
+ {{ badgeText }}
From 8901a7ccfcd933bdf6c0e8ea669cf9508eaf508c Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Wed, 18 Sep 2024 15:39:17 +0200
Subject: [PATCH 25/30] fix(editor): Fix unit test
---
.../editor-ui/src/components/Projects/ProjectCardBadge.test.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/editor-ui/src/components/Projects/ProjectCardBadge.test.ts b/packages/editor-ui/src/components/Projects/ProjectCardBadge.test.ts
index f3cf103386bfb..add92bd45c3bd 100644
--- a/packages/editor-ui/src/components/Projects/ProjectCardBadge.test.ts
+++ b/packages/editor-ui/src/components/Projects/ProjectCardBadge.test.ts
@@ -1,5 +1,6 @@
import { createComponentRenderer } from '@/__tests__/render';
import ProjectCardBadge from '@/components/Projects/ProjectCardBadge.vue';
+import { truncate } from 'n8n-design-system';
const renderComponent = createComponentRenderer(ProjectCardBadge);
@@ -56,6 +57,6 @@ describe('ProjectCardBadge', () => {
},
},
});
- expect(getByText(result)).toBeVisible();
+ expect(getByText(truncate(result, 20))).toBeVisible();
});
});
From e2fc7d31e8207d970e22e4a9d5ddd0600779458d Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Wed, 18 Sep 2024 16:39:10 +0200
Subject: [PATCH 26/30] fix(editor): Add select placeholder
---
.../Projects/ProjectMoveResourceModal.vue | 14 +++++++++++++-
.../editor-ui/src/plugins/i18n/locales/en.json | 1 +
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
index 95a97607ce68e..27d1204f2594d 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
@@ -28,6 +28,7 @@ const toast = useToast();
const projectsStore = useProjectsStore();
const telemetry = useTelemetry();
+const filter = ref('');
const projectId = ref(null);
const processedName = computed(
() => processProjectName(props.data.resource.homeProject?.name ?? '') ?? '',
@@ -36,6 +37,7 @@ const availableProjects = computed(() =>
projectsStore.availableProjects
.filter(
(p) =>
+ p.name?.toLowerCase().includes(filter.value.toLowerCase()) &&
p.id !== props.data.resource.homeProject?.id &&
(!p.scopes || getResourcePermissions(p.scopes)[props.data.resourceType].create),
)
@@ -62,6 +64,10 @@ const closeModal = () => {
uiStore.closeModal(props.modalName);
};
+const setFilter = (query: string) => {
+ filter.value = query;
+};
+
const moveResource = async () => {
if (!selectedProject.value) return;
try {
@@ -92,6 +98,7 @@ const moveResource = async () => {
targetProject: selectedProject.value,
}),
type: 'success',
+ duration: 5000,
});
} catch (error) {
toast.showError(
@@ -150,10 +157,15 @@ onMounted(() => {
+
+
+
Date: Wed, 18 Sep 2024 16:41:21 +0200
Subject: [PATCH 27/30] fix(editor): Increase toast timeout
---
.../src/components/Projects/ProjectMoveResourceModal.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
index 27d1204f2594d..1f30e1d1b3cb6 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
@@ -98,7 +98,7 @@ const moveResource = async () => {
targetProject: selectedProject.value,
}),
type: 'success',
- duration: 5000,
+ duration: 8000,
});
} catch (error) {
toast.showError(
From c02411d2acb5c4fc93eb8dafa106beec6a7f411d Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Tue, 24 Sep 2024 18:01:52 +0200
Subject: [PATCH 28/30] fix(editor): Allow changing foreign credential in a
node if the workflow is in a team project
---
.../editor-ui/src/components/NodeSettings.vue | 27 +++++++++++++------
1 file changed, 19 insertions(+), 8 deletions(-)
diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue
index 3f4ee2833ae03..1525610e8f7ca 100644
--- a/packages/editor-ui/src/components/NodeSettings.vue
+++ b/packages/editor-ui/src/components/NodeSettings.vue
@@ -51,6 +51,7 @@ import { useToast } from '@/composables/useToast';
import { useI18n } from '@/composables/useI18n';
import { useTelemetry } from '@/composables/useTelemetry';
import { importCurlEventBus, ndvEventBus } from '@/event-bus';
+import { ProjectTypes } from '@/types/projects.types';
const props = withDefaults(
defineProps<{
@@ -117,17 +118,29 @@ const hiddenIssuesInputs = ref([]);
const nodeSettings = ref([]);
const subConnections = ref | null>(null);
-const isReadOnly = computed(() => props.readOnly || props.foreignCredentials.length > 0);
-
+const currentWorkflowInstance = computed(() => workflowsStore.getCurrentWorkflow());
+const currentWorkflow = computed(() =>
+ workflowsStore.getWorkflowById(currentWorkflowInstance.value.id),
+);
+const hasForeignCredential = computed(() => props.foreignCredentials.length > 0);
+const isHomeProjectTeam = computed(
+ () => currentWorkflow.value.homeProject?.type === ProjectTypes.Team,
+);
+const isReadOnly = computed(
+ () => props.readOnly || (hasForeignCredential.value && !isHomeProjectTeam.value),
+);
const node = computed(() => ndvStore.activeNode);
const isTriggerNode = computed(() => !!node.value && nodeTypesStore.isTriggerNode(node.value.type));
const isExecutable = computed(() => {
if (props.nodeType && node.value) {
- const workflow = workflowsStore.getCurrentWorkflow();
- const workflowNode = workflow.getNode(node.value.name);
- const inputs = NodeHelpers.getNodeInputs(workflow, workflowNode!, props.nodeType);
+ const workflowNode = currentWorkflowInstance.value.getNode(node.value.name);
+ const inputs = NodeHelpers.getNodeInputs(
+ currentWorkflowInstance.value,
+ workflowNode!,
+ props.nodeType,
+ );
const inputNames = NodeHelpers.getConnectionTypes(inputs);
if (!inputNames.includes(NodeConnectionType.Main) && !isTriggerNode.value) {
@@ -195,8 +208,6 @@ const outputPanelEditMode = computed(() => ndvStore.outputPanelEditMode);
const isCommunityNode = computed(() => !!node.value && isCommunityPackageName(node.value.type));
-const hasForeignCredential = computed(() => props.foreignCredentials.length > 0);
-
const usedCredentials = computed(() =>
Object.values(workflowsStore.usedCredentials).filter((credential) =>
Object.values(node.value?.credentials || []).find(
@@ -1017,7 +1028,7 @@ onBeforeUnmount(() => {
{
cy.enableFeature('projectRole:viewer');
cy.signinAsOwner();
From 85d89a3344d55ebbf840e8e7b42ee6797dc8a3da Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Thu, 26 Sep 2024 17:04:43 +0200
Subject: [PATCH 30/30] test(editor): Enable all tests
---
cypress/e2e/39-projects.cy.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cypress/e2e/39-projects.cy.ts b/cypress/e2e/39-projects.cy.ts
index 73799b2b8bf5a..4e3bb583df970 100644
--- a/cypress/e2e/39-projects.cy.ts
+++ b/cypress/e2e/39-projects.cy.ts
@@ -668,7 +668,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
.should('have.length', 1);
});
- it.only('should allow to change inaccessible credential when the workflow was moved to a team project', () => {
+ it('should allow to change inaccessible credential when the workflow was moved to a team project', () => {
cy.signinAsOwner();
cy.visit(workflowsPage.url);
@@ -735,7 +735,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
workflowsPage.getters.workflowCards().should('have.length', 1);
workflowsPage.getters.workflowCards().first().click();
- // Change the inaccessible credential
+ // Check if the credential can be changed
workflowPage.getters.canvasNodeByName(NOTION_NODE_NAME).should('be.visible').dblclick();
ndv.getters.credentialInput().find('input').should('be.enabled');
});