From fe63b31fdcaeb4bff16def09c125592cca3a185d Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Tue, 21 May 2024 15:58:18 +0200 Subject: [PATCH 1/8] fix(editor): Fix project sharing dropdown layout --- .../features/projects/components/ProjectSharingInfo.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/editor-ui/src/features/projects/components/ProjectSharingInfo.vue b/packages/editor-ui/src/features/projects/components/ProjectSharingInfo.vue index 4c08ae684c7a0..d68af3539d718 100644 --- a/packages/editor-ui/src/features/projects/components/ProjectSharingInfo.vue +++ b/packages/editor-ui/src/features/projects/components/ProjectSharingInfo.vue @@ -15,7 +15,7 @@ const processedName = computed(() => splitName(props.project.name ?? ''));
-
+

{{ processedName.firstName }} {{ processedName.lastName }}

@@ -54,4 +54,9 @@ const processedName = computed(() => splitName(props.project.name ?? '')); line-height: var(--font-line-height-loose); } } + +.text { + display: flex; + flex-direction: column; +} From 21731e7e6b204ee0e75dfa16085df8aec2cf292c Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Tue, 21 May 2024 17:53:10 +0200 Subject: [PATCH 2/8] fix(editor): Updating project sharing no data text --- .../src/components/forms/ResourceFiltersDropdown.vue | 1 + .../features/projects/components/ProjectDeleteDialog.vue | 7 ++++++- .../src/features/projects/components/ProjectSharing.vue | 6 +++++- packages/editor-ui/src/plugins/i18n/locales/en.json | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/src/components/forms/ResourceFiltersDropdown.vue b/packages/editor-ui/src/components/forms/ResourceFiltersDropdown.vue index b1724ab1a7e1f..851230bd71819 100644 --- a/packages/editor-ui/src/components/forms/ResourceFiltersDropdown.vue +++ b/packages/editor-ui/src/components/forms/ResourceFiltersDropdown.vue @@ -32,6 +32,7 @@ class="pt-2xs" :projects="projectsStore.projects" :placeholder="$locale.baseText('forms.resourceFiltersDropdown.owner.placeholder')" + :empty-options-text="$locale.baseText('projects.sharing.noMatchingProjects')" @update:model-value="setKeyValue('homeProject', ($event as ProjectSharingData).id)" /> diff --git a/packages/editor-ui/src/features/projects/components/ProjectDeleteDialog.vue b/packages/editor-ui/src/features/projects/components/ProjectDeleteDialog.vue index 05aea4a47e308..4df4d912d4382 100644 --- a/packages/editor-ui/src/features/projects/components/ProjectDeleteDialog.vue +++ b/packages/editor-ui/src/features/projects/components/ProjectDeleteDialog.vue @@ -75,7 +75,12 @@ const onDelete = () => { {{ locale.baseText('projects.settings.delete.question.transfer.title') }} - +
(); @@ -34,6 +35,9 @@ const selectPlaceholder = computed( ? locale.baseText('projects.sharing.placeholder') : locale.baseText('projects.sharing.placeholder.single')), ); +const noDataText = computed( + () => props.emptyOptionsText ?? locale.baseText('projects.sharing.noMatchingUsers'), +); const filteredProjects = computed(() => props.projects .filter( @@ -101,7 +105,7 @@ watch( :filter-method="setFilter" :placeholder="selectPlaceholder" :default-first-option="true" - :no-data-text="locale.baseText('projects.sharing.noMatchingProjects')" + :no-data-text="noDataText" size="large" :disabled="props.readonly" @update:model-value="onProjectSelected" diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 9153d85de699f..71dbc4a4882d6 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -2433,6 +2433,7 @@ "projects.settings.role.upgrade.title": "Upgrade to unlock additional roles", "projects.settings.role.upgrade.message": "You're currently limited to {limit} on the {planName} plan and can only assign the admin role to users within this project. To create more projects and unlock additional roles, upgrade your plan.", "projects.sharing.noMatchingProjects": "There are no available projects", + "projects.sharing.noMatchingUsers": "No matching users", "projects.sharing.placeholder": "Add projects...", "projects.sharing.placeholder.single": "Select project", "projects.error.title": "Project error", From 04713f3c1dfc41c7e677e1dc849aa284d281f2b5 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Tue, 21 May 2024 19:15:11 +0200 Subject: [PATCH 3/8] fix(editor): Fix project menu item highlight --- .../projects/components/ProjectNavigation.vue | 6 +----- .../src/features/projects/projects.store.ts | 15 +++++++++++++-- packages/editor-ui/src/views/NodeView.vue | 4 ++++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/editor-ui/src/features/projects/components/ProjectNavigation.vue b/packages/editor-ui/src/features/projects/components/ProjectNavigation.vue index 17c61b06d1142..05899053e3053 100644 --- a/packages/editor-ui/src/features/projects/components/ProjectNavigation.vue +++ b/packages/editor-ui/src/features/projects/components/ProjectNavigation.vue @@ -56,10 +56,6 @@ const activeTab = computed(() => { return routes.includes(route.name as VIEWS) ? 'home' : undefined; }); -const isActiveProject = (projectId: string) => - route?.params?.projectId === projectId || projectsStore.currentProjectId === projectId - ? projectId - : undefined; const getProjectMenuItem = (project: ProjectListItem) => ({ id: project.id, label: project.name, @@ -146,7 +142,7 @@ onMounted(async () => { :item="getProjectMenuItem(project)" :compact="props.collapsed" :handle-select="projectClicked" - :active-tab="isActiveProject(project.id)" + :active-tab="projectsStore.projectNavActiveId" mode="tabs" data-test-id="project-menu-item" /> diff --git a/packages/editor-ui/src/features/projects/projects.store.ts b/packages/editor-ui/src/features/projects/projects.store.ts index 415c7a229404f..b44e8eced8ccb 100644 --- a/packages/editor-ui/src/features/projects/projects.store.ts +++ b/packages/editor-ui/src/features/projects/projects.store.ts @@ -27,11 +27,12 @@ export const useProjectsStore = defineStore('projects', () => { team: 0, public: 0, }); + const projectNavActiveIdState = ref(null); const currentProjectId = computed( () => - (route.params?.projectId as string | undefined) || - (route.query?.projectId as string | undefined) || + (route.params?.projectId as string | undefined) ?? + (route.query?.projectId as string | undefined) ?? currentProject.value?.id, ); const isProjectHome = computed(() => route.path.includes('home')); @@ -56,6 +57,13 @@ export const useProjectsStore = defineStore('projects', () => { hasPermission(['rbac'], { rbac: { scope: 'project:create' } }), ); + const projectNavActiveId = computed({ + get: () => route?.params?.projectId ?? projectNavActiveIdState.value, + set: (value: string | string[] | null) => { + projectNavActiveIdState.value = value; + }, + }); + const setCurrentProject = (project: Project | null) => { currentProject.value = project; }; @@ -113,6 +121,8 @@ export const useProjectsStore = defineStore('projects', () => { watch( route, async (newRoute) => { + projectNavActiveId.value = null; + if (newRoute?.path?.includes('home')) { setCurrentProject(null); } @@ -140,6 +150,7 @@ export const useProjectsStore = defineStore('projects', () => { canCreateProjects, hasPermissionToCreateProjects, teamProjectsAvailable, + projectNavActiveId, setCurrentProject, getAllProjects, getMyProjects, diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 74c7044ed21e4..e29781cbe8d07 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -3726,6 +3726,10 @@ export default defineComponent({ await this.openWorkflow(workflow); await this.checkAndInitDebugMode(); + if (this.projectsStore.currentProjectId) { + this.projectsStore.projectNavActiveId = this.projectsStore.currentProjectId; + } + if (workflow.meta?.onboardingId) { this.$telemetry.track( `User opened workflow from onboarding template with ID ${workflow.meta.onboardingId}`, From cc3ec4448d6fdd98a02a7b3b1d9d992e917d985d Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 22 May 2024 11:08:58 +0200 Subject: [PATCH 4/8] fix(editor): Update credential creation toast message --- packages/editor-ui/src/plugins/i18n/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 71dbc4a4882d6..40f37023ea3d7 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -554,7 +554,7 @@ "credentials.noResults.withSearch.switchToShared.preamble": "some credentials may be", "credentials.noResults.withSearch.switchToShared.link": "hidden", "credentials.create.personal.toast.title": "Credential successfully created", - "credentials.create.personal.toast.text": "This credential is currently private to you. View sharing options.", + "credentials.create.personal.toast.text": "This credential is currently private to you.", "credentials.create.project.toast.title": "Credential successfully created in {projectName}", "credentials.create.project.toast.text": "All members from {projectName} will have access to this credential.", "credentials.shareModal.info.members": "This credential is owned by the {projectName} project which currently has {members} with access to this credential.", From 1251291fb7130862f5287b5a8cf693211ebae96a Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 22 May 2024 12:00:55 +0200 Subject: [PATCH 5/8] test: Extend projects e2e test to test highlighted menu items --- cypress/e2e/39-projects.cy.ts | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/39-projects.cy.ts b/cypress/e2e/39-projects.cy.ts index 5cf1ac1fdcb2c..d4aec07bcec38 100644 --- a/cypress/e2e/39-projects.cy.ts +++ b/cypress/e2e/39-projects.cy.ts @@ -16,7 +16,7 @@ describe('Projects', () => { cy.changeQuota('maxTeamProjects', -1); }); - it('should handle workflows and credentials', () => { + it('should handle workflows and credentials and menu items', () => { cy.signin(INSTANCE_ADMIN); cy.visit(workflowsPage.url); workflowsPage.getters.workflowCards().should('not.have.length'); @@ -147,5 +147,32 @@ describe('Projects', () => { cy.wait('@credentialsList').then((interception) => { expect(interception.request.url).not.to.contain('filter'); }); + + let menuItems = cy.getByTestId('menu-item'); + + menuItems.filter('[class*=active_]').should('have.length', 1); + menuItems.filter(':contains("Home")[class*=active_]').should('exist'); + + projects.getMenuItems().first().click(); + + menuItems = cy.getByTestId('menu-item'); + + menuItems.filter('[class*=active_]').should('have.length', 1); + menuItems.filter(':contains("Development")[class*=active_]').should('exist'); + + workflowsPage.getters.workflowCards().first().click(); + + menuItems = cy.getByTestId('menu-item'); + + menuItems.filter('[class*=active_]').should('have.length', 1); + menuItems.filter(':contains("Development")[class*=active_]').should('exist'); + + cy.getByTestId('menu-item').filter(':contains("Variables")').click(); + cy.getByTestId('unavailable-resources-list').should('be.visible'); + + menuItems = cy.getByTestId('menu-item'); + + menuItems.filter('[class*=active_]').should('have.length', 1); + menuItems.filter(':contains("Variables")[class*=active_]').should('exist'); }); }); From d290d80270738a14bef3159d1bc9a157a86a9876 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 22 May 2024 14:36:29 +0200 Subject: [PATCH 6/8] fix(editor): Fix project menu item highlight --- cypress/e2e/39-projects.cy.ts | 46 ++++++++++++++++++- .../editor-ui/src/__tests__/data/projects.ts | 5 +- .../CredentialEdit/CredentialSharing.ee.vue | 3 +- .../src/components/WorkflowSettings.vue | 3 +- .../src/components/WorkflowShareModal.ee.vue | 3 +- .../projects/components/ProjectCardBadge.vue | 9 ++-- .../projects/components/ProjectNavigation.vue | 19 +------- .../src/features/projects/projects.store.ts | 16 ++++++- .../src/features/projects/projects.types.ts | 5 +- .../src/features/projects/projects.utils.ts | 6 +++ packages/editor-ui/src/views/NodeView.vue | 9 ++-- 11 files changed, 93 insertions(+), 31 deletions(-) diff --git a/cypress/e2e/39-projects.cy.ts b/cypress/e2e/39-projects.cy.ts index d4aec07bcec38..a3758b1fdaa84 100644 --- a/cypress/e2e/39-projects.cy.ts +++ b/cypress/e2e/39-projects.cy.ts @@ -1,15 +1,23 @@ import { INSTANCE_ADMIN, INSTANCE_MEMBERS } from '../constants'; -import { WorkflowsPage, WorkflowPage, CredentialsModal, CredentialsPage } from '../pages'; +import { + WorkflowsPage, + WorkflowPage, + CredentialsModal, + CredentialsPage, + WorkflowExecutionsTab, +} from '../pages'; import * as projects from '../composables/projects'; const workflowsPage = new WorkflowsPage(); const workflowPage = new WorkflowPage(); const credentialsPage = new CredentialsPage(); const credentialsModal = new CredentialsModal(); +const executionsTab = new WorkflowExecutionsTab(); describe('Projects', () => { beforeEach(() => { cy.resetDatabase(); + cy.enableFeature('sharing'); cy.enableFeature('advancedPermissions'); cy.enableFeature('projectRole:admin'); cy.enableFeature('projectRole:editor'); @@ -160,8 +168,26 @@ describe('Projects', () => { menuItems.filter('[class*=active_]').should('have.length', 1); menuItems.filter(':contains("Development")[class*=active_]').should('exist'); + cy.intercept('GET', '/rest/workflows/*').as('loadWorkflow'); workflowsPage.getters.workflowCards().first().click(); + cy.wait('@loadWorkflow'); + menuItems = cy.getByTestId('menu-item'); + + menuItems.filter('[class*=active_]').should('have.length', 1); + menuItems.filter(':contains("Development")[class*=active_]').should('exist'); + + cy.intercept('GET', '/rest/executions*').as('loadExecutions'); + executionsTab.actions.switchToExecutionsTab(); + + cy.wait('@loadExecutions'); + menuItems = cy.getByTestId('menu-item'); + + menuItems.filter('[class*=active_]').should('have.length', 1); + menuItems.filter(':contains("Development")[class*=active_]').should('exist'); + + executionsTab.actions.switchToEditorTab(); + menuItems = cy.getByTestId('menu-item'); menuItems.filter('[class*=active_]').should('have.length', 1); @@ -174,5 +200,23 @@ describe('Projects', () => { menuItems.filter('[class*=active_]').should('have.length', 1); menuItems.filter(':contains("Variables")[class*=active_]').should('exist'); + + projects.getHomeButton().click(); + menuItems = cy.getByTestId('menu-item'); + + menuItems.filter('[class*=active_]').should('have.length', 1); + menuItems.filter(':contains("Home")[class*=active_]').should('exist'); + + workflowsPage.getters.workflowCards().should('have.length', 2).first().click(); + + cy.wait('@loadWorkflow'); + cy.getByTestId('execute-workflow-button').should('be.visible'); + + menuItems = cy.getByTestId('menu-item'); + menuItems.filter(':contains("Home")[class*=active_]').should('not.exist'); + + menuItems = cy.getByTestId('menu-item'); + menuItems.filter('[class*=active_]').should('have.length', 1); + menuItems.filter(':contains("Development")[class*=active_]').should('exist'); }); }); diff --git a/packages/editor-ui/src/__tests__/data/projects.ts b/packages/editor-ui/src/__tests__/data/projects.ts index d0e0666f45c11..9f87d7f781e97 100644 --- a/packages/editor-ui/src/__tests__/data/projects.ts +++ b/packages/editor-ui/src/__tests__/data/projects.ts @@ -4,11 +4,14 @@ import type { ProjectSharingData, ProjectType, } from '@/features/projects/projects.types'; +import { ProjectTypes } from '@/features/projects/projects.utils'; export const createProjectSharingData = (projectType?: ProjectType): ProjectSharingData => ({ id: faker.string.uuid(), name: faker.lorem.words({ min: 1, max: 3 }), - type: projectType || 'personal', + type: projectType ?? ProjectTypes.Personal, + createdAt: faker.date.past().toISOString(), + updatedAt: faker.date.recent().toISOString(), }); export const createProjectListItem = (projectType?: ProjectType): ProjectListItem => { diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue index 388101969cfaa..d94364e7a36b6 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue @@ -96,6 +96,7 @@ import type { CredentialScope } from '@n8n/permissions'; import type { EventBus } from 'n8n-design-system/utils'; import { useRolesStore } from '@/stores/roles.store'; import type { RoleMap } from '@/types/roles.types'; +import { ProjectTypes } from '@/features/projects/projects.utils'; export default defineComponent({ name: 'CredentialSharing', @@ -178,7 +179,7 @@ export default defineComponent({ ); }, isHomeTeamProject(): boolean { - return this.homeProject?.type === 'team'; + return this.homeProject?.type === ProjectTypes.Team; }, numberOfMembersInHomeTeamProject(): number { return this.teamProject?.relations.length ?? 0; diff --git a/packages/editor-ui/src/components/WorkflowSettings.vue b/packages/editor-ui/src/components/WorkflowSettings.vue index ecaa56de3e799..8d7ac218ba43a 100644 --- a/packages/editor-ui/src/components/WorkflowSettings.vue +++ b/packages/editor-ui/src/components/WorkflowSettings.vue @@ -384,6 +384,7 @@ import type { WorkflowScope } from '@n8n/permissions'; import { getWorkflowPermissions } from '@/permissions'; import { useExternalHooks } from '@/composables/useExternalHooks'; import { useSourceControlStore } from '@/stores/sourceControl.store'; +import { ProjectTypes } from '@/features/projects/projects.utils'; export default defineComponent({ name: 'WorkflowSettings', @@ -604,7 +605,7 @@ export default defineComponent({ { key: 'workflowsFromSameOwner', value: this.$locale.baseText( - this.workflow.homeProject?.type === 'personal' + this.workflow.homeProject?.type === ProjectTypes.Personal ? 'workflowSettings.callerPolicy.options.workflowsFromPersonalProject' : 'workflowSettings.callerPolicy.options.workflowsFromTeamProject', { diff --git a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue index 489a46c7316f7..6557492c59b2a 100644 --- a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue +++ b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue @@ -152,6 +152,7 @@ import type { } from '@/features/projects/projects.types'; import { useRolesStore } from '@/stores/roles.store'; import type { RoleMap } from '@/types/roles.types'; +import { ProjectTypes } from '@/features/projects/projects.utils'; export default defineComponent({ name: 'WorkflowShareModal', @@ -238,7 +239,7 @@ export default defineComponent({ ); }, isHomeTeamProject(): boolean { - return this.workflow.homeProject?.type === 'team'; + return this.workflow.homeProject?.type === ProjectTypes.Team; }, numberOfMembersInHomeTeamProject(): number { return this.teamProject?.relations.length ?? 0; diff --git a/packages/editor-ui/src/features/projects/components/ProjectCardBadge.vue b/packages/editor-ui/src/features/projects/components/ProjectCardBadge.vue index 7c9143e1fc143..4f967891ab9af 100644 --- a/packages/editor-ui/src/features/projects/components/ProjectCardBadge.vue +++ b/packages/editor-ui/src/features/projects/components/ProjectCardBadge.vue @@ -1,7 +1,7 @@