From 1cf48cc3019c1cf27e2f3c9affd18426237e9064 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Fri, 9 Aug 2024 16:31:02 +0200 Subject: [PATCH] fix(editor): Enable credential sharing between all types of projects (#10233) --- cypress/composables/projects.ts | 20 ++--- cypress/e2e/17-sharing.cy.ts | 73 ++++++++++++++++++- cypress/e2e/39-projects.cy.ts | 2 +- .../CredentialEdit/CredentialEdit.vue | 15 +++- .../CredentialEdit/CredentialSharing.ee.vue | 69 ++++++------------ .../src/components/DeleteUserModal.vue | 4 +- .../components/Projects/ProjectCardBadge.vue | 12 +++ .../components/Projects/ProjectSharing.vue | 6 +- .../src/plugins/i18n/locales/en.json | 29 ++++---- .../editor-ui/src/stores/credentials.store.ts | 16 +++- .../src/views/ProjectSettings.test.ts | 10 ++- .../editor-ui/src/views/ProjectSettings.vue | 3 +- .../{__tests__ => }/SettingsUsersView.test.ts | 10 ++- 13 files changed, 173 insertions(+), 96 deletions(-) rename packages/editor-ui/src/views/{__tests__ => }/SettingsUsersView.test.ts (95%) diff --git a/cypress/composables/projects.ts b/cypress/composables/projects.ts index 6fa2c6f502a3e..ed280b462f328 100644 --- a/cypress/composables/projects.ts +++ b/cypress/composables/projects.ts @@ -5,7 +5,8 @@ const credentialsModal = new CredentialsModal(); export const getHomeButton = () => cy.getByTestId('project-home-menu-item'); export const getMenuItems = () => cy.getByTestId('project-menu-item'); -export const getAddProjectButton = () => cy.getByTestId('add-project-menu-item'); +export const getAddProjectButton = () => + cy.getByTestId('add-project-menu-item').should('contain', 'Add project').should('be.visible'); export const getProjectTabs = () => cy.getByTestId('project-tabs').find('a'); export const getProjectTabWorkflows = () => getProjectTabs().filter('a[href$="/workflows"]'); export const getProjectTabCredentials = () => getProjectTabs().filter('a[href$="/credentials"]'); @@ -28,7 +29,7 @@ export const getResourceMoveConfirmModal = () => export const getProjectMoveSelect = () => cy.getByTestId('project-move-resource-modal-select'); export function createProject(name: string) { - getAddProjectButton().should('be.visible').click(); + getAddProjectButton().click(); getProjectNameInput() .should('be.visible') @@ -46,7 +47,7 @@ export function createWorkflow(fixtureKey: string, name: string) { workflowPage.actions.zoomToFit(); } -export function createCredential(name: string) { +export function createCredential(name: string, closeModal = true) { credentialsModal.getters.newCredentialModal().should('be.visible'); credentialsModal.getters.newCredentialTypeSelect().should('be.visible'); credentialsModal.getters.newCredentialTypeOption('Notion API').click(); @@ -54,13 +55,8 @@ export function createCredential(name: string) { credentialsModal.getters.connectionParameter('Internal Integration Secret').type('1234567890'); credentialsModal.actions.setName(name); credentialsModal.actions.save(); - credentialsModal.actions.close(); -} -export const actions = { - createProject: (name: string) => { - getAddProjectButton().click(); - getProjectSettingsNameInput().type(name); - getProjectSettingsSaveButton().click(); - }, -}; + if (closeModal) { + credentialsModal.actions.close(); + } +} diff --git a/cypress/e2e/17-sharing.cy.ts b/cypress/e2e/17-sharing.cy.ts index 54c5e6efe202c..ab1ee3955b1ac 100644 --- a/cypress/e2e/17-sharing.cy.ts +++ b/cypress/e2e/17-sharing.cy.ts @@ -7,7 +7,7 @@ import { WorkflowSharingModal, WorkflowsPage, } from '../pages'; -import { getVisibleSelect } from '../utils'; +import { getVisibleDropdown, getVisibleSelect } from '../utils'; import * as projects from '../composables/projects'; /** @@ -192,6 +192,73 @@ describe('Sharing', { disableAutoLogin: true }, () => { credentialsModal.actions.saveSharing(); credentialsModal.actions.close(); }); + + it('credentials should work between team and personal projects', () => { + cy.resetDatabase(); + cy.enableFeature('sharing'); + cy.enableFeature('advancedPermissions'); + cy.enableFeature('projectRole:admin'); + cy.enableFeature('projectRole:editor'); + cy.changeQuota('maxTeamProjects', -1); + + cy.signinAsOwner(); + cy.visit('/'); + + projects.createProject('Development'); + + projects.getHomeButton().click(); + workflowsPage.getters.newWorkflowButtonCard().click(); + projects.createWorkflow('Test_workflow_1.json', 'Test workflow'); + + projects.getHomeButton().click(); + projects.getProjectTabCredentials().click(); + credentialsPage.getters.emptyListCreateCredentialButton().click(); + projects.createCredential('Notion API'); + + credentialsPage.getters.credentialCard('Notion API').click(); + credentialsModal.actions.changeTab('Sharing'); + credentialsModal.getters.usersSelect().click(); + getVisibleSelect() + .find('li') + .should('have.length', 4) + .filter(':contains("Development")') + .should('have.length', 1) + .click(); + credentialsModal.getters.saveButton().click(); + credentialsModal.actions.close(); + + projects.getProjectTabWorkflows().click(); + workflowsPage.getters.workflowCardActions('Test workflow').click(); + getVisibleDropdown().find('li').contains('Share').click(); + + workflowSharingModal.getters.usersSelect().filter(':visible').click(); + getVisibleSelect().find('li').should('have.length', 3).first().click(); + workflowSharingModal.getters.saveButton().click(); + + projects.getMenuItems().first().click(); + workflowsPage.getters.newWorkflowButtonCard().click(); + projects.createWorkflow('Test_workflow_1.json', 'Test workflow 2'); + workflowPage.actions.openShareModal(); + workflowSharingModal.getters.usersSelect().should('not.exist'); + + cy.get('body').type('{esc}'); + + projects.getMenuItems().first().click(); + projects.getProjectTabCredentials().click(); + credentialsPage.getters.createCredentialButton().click(); + projects.createCredential('Notion API 2', false); + credentialsModal.actions.changeTab('Sharing'); + credentialsModal.getters.usersSelect().click(); + getVisibleSelect().find('li').should('have.length', 4).first().click(); + credentialsModal.getters.saveButton().click(); + credentialsModal.actions.close(); + + credentialsPage.getters + .credentialCards() + .should('have.length', 2) + .filter(':contains("Owned by me")') + .should('have.length', 1); + }); }); describe('Credential Usage in Cross Shared Workflows', () => { @@ -217,13 +284,13 @@ describe('Credential Usage in Cross Shared Workflows', () => { credentialsModal.actions.createNewCredential('Notion API'); // Create a notion credential in one project - projects.actions.createProject('Development'); + projects.createProject('Development'); projects.getProjectTabCredentials().click(); credentialsPage.getters.emptyListCreateCredentialButton().click(); credentialsModal.actions.createNewCredential('Notion API'); // Create a notion credential in another project - projects.actions.createProject('Test'); + projects.createProject('Test'); projects.getProjectTabCredentials().click(); credentialsPage.getters.emptyListCreateCredentialButton().click(); credentialsModal.actions.createNewCredential('Notion API'); diff --git a/cypress/e2e/39-projects.cy.ts b/cypress/e2e/39-projects.cy.ts index 94a6384233c2c..cbf7324e4b6b3 100644 --- a/cypress/e2e/39-projects.cy.ts +++ b/cypress/e2e/39-projects.cy.ts @@ -237,7 +237,7 @@ describe('Projects', { disableAutoLogin: true }, () => { cy.signinAsMember(1); cy.visit(workflowsPage.url); - projects.getAddProjectButton().should('not.exist'); + cy.getByTestId('add-project-menu-item').should('not.exist'); projects.getMenuItems().should('not.exist'); }); diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue index fd539fed7554f..bfeab0737ceb5 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue @@ -446,6 +446,11 @@ const showSaveButton = computed(() => { const showSharingContent = computed(() => activeTab.value === 'sharing' && !!credentialType.value); +const homeProject = computed(() => { + const { currentProject, personalProject } = projectsStore; + return currentProject ?? personalProject; +}); + onMounted(async () => { requiredCredentials.value = isCredentialModalState(uiStore.modalsById[CREDENTIAL_EDIT_MODAL_KEY]) && @@ -456,14 +461,12 @@ onMounted(async () => { credentialTypeName: defaultCredentialTypeName.value, }); - const { currentProject, personalProject } = projectsStore; - const scopes = currentProject?.scopes ?? personalProject?.scopes ?? []; - const homeProject = currentProject ?? personalProject ?? {}; + const scopes = homeProject.value?.scopes ?? []; credentialData.value = { ...credentialData.value, scopes, - homeProject, + ...(homeProject.value ? { homeProject: homeProject.value } : {}), }; } else { await loadCurrentCredential(); @@ -793,6 +796,10 @@ async function saveCredential(): Promise { .sharedWithProjects as ProjectSharingData[]; } + if (credentialData.value.homeProject) { + credentialDetails.homeProject = credentialData.value.homeProject as ProjectSharingData; + } + let credential: ICredentialsResponse | null = null; const isNewCredential = props.mode === 'new' && !credentialId.value; diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue index 133b00ca4d46e..0d19cc3b4b191 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue @@ -1,7 +1,7 @@ @@ -89,7 +65,7 @@ import { useUsageStore } from '@/stores/usage.store'; import { EnterpriseEditionFeature } from '@/constants'; import ProjectSharing from '@/components/Projects/ProjectSharing.vue'; import { useProjectsStore } from '@/stores/projects.store'; -import type { ProjectListItem, ProjectSharingData, Project } from '@/types/projects.types'; +import type { ProjectListItem, ProjectSharingData } from '@/types/projects.types'; import { ProjectTypes } from '@/types/projects.types'; import type { ICredentialDataDecryptedObject } from 'n8n-workflow'; import type { PermissionsMap } from '@/permissions'; @@ -97,6 +73,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 { splitName } from '@/utils/projects.utils'; export default defineComponent({ name: 'CredentialSharing', @@ -134,7 +111,6 @@ export default defineComponent({ data() { return { sharedWithProjects: [...(this.credential?.sharedWithProjects ?? [])] as ProjectSharingData[], - teamProject: null as Project | null, }; }, computed: { @@ -159,7 +135,8 @@ export default defineComponent({ return this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]; }, credentialOwnerName(): string { - return this.credentialsStore.getCredentialOwnerNameById(`${this.credentialId}`); + const { firstName, lastName, email } = splitName(this.credential?.homeProject?.name ?? ''); + return firstName || lastName ? `${firstName}${lastName ? ' ' + lastName : ''}` : email ?? ''; }, credentialDataHomeProject(): ProjectSharingData | undefined { const credentialContainsProjectSharingData = ( @@ -182,7 +159,7 @@ export default defineComponent({ }); }, projects(): ProjectListItem[] { - return this.projectsStore.personalProjects.filter( + return this.projectsStore.projects.filter( (project) => project.id !== this.credential?.homeProject?.id && project.id !== this.credentialDataHomeProject?.id, @@ -194,9 +171,6 @@ export default defineComponent({ isHomeTeamProject(): boolean { return this.homeProject?.type === ProjectTypes.Team; }, - numberOfMembersInHomeTeamProject(): number { - return this.teamProject?.relations.length ?? 0; - }, credentialRoleTranslations(): Record { return { 'credential:user': this.$locale.baseText('credentialEdit.credentialSharing.role.user'), @@ -210,6 +184,11 @@ export default defineComponent({ licensed, })); }, + sharingSelectPlaceholder() { + return this.projectsStore.teamProjects.length + ? this.$locale.baseText('projects.sharing.select.placeholder.project') + : this.$locale.baseText('projects.sharing.select.placeholder.user'); + }, }, watch: { sharedWithProjects: { @@ -221,10 +200,6 @@ export default defineComponent({ }, async mounted() { await Promise.all([this.usersStore.fetchUsers(), this.projectsStore.getAllProjects()]); - - if (this.homeProject && this.isHomeTeamProject) { - this.teamProject = await this.projectsStore.fetchProject(this.homeProject.id); - } }, methods: { goToUpgrade() { diff --git a/packages/editor-ui/src/components/DeleteUserModal.vue b/packages/editor-ui/src/components/DeleteUserModal.vue index 7516466a340b6..2fdb0ad73468f 100644 --- a/packages/editor-ui/src/components/DeleteUserModal.vue +++ b/packages/editor-ui/src/components/DeleteUserModal.vue @@ -3,7 +3,7 @@ :name="modalName" :title="title" :center="true" - width="460px" + width="520" :event-bus="modalBus" @enter="onSubmit" > @@ -147,7 +147,7 @@ export default defineComponent({ return false; }, projects(): ProjectListItem[] { - return this.projectsStore.personalProjects.filter( + return this.projectsStore.projects.filter( (project) => project.name !== `${this.userToDelete?.firstName} ${this.userToDelete?.lastName} <${this.userToDelete?.email}>`, diff --git a/packages/editor-ui/src/components/Projects/ProjectCardBadge.vue b/packages/editor-ui/src/components/Projects/ProjectCardBadge.vue index 832c329f26284..6bcd1aade75b2 100644 --- a/packages/editor-ui/src/components/Projects/ProjectCardBadge.vue +++ b/packages/editor-ui/src/components/Projects/ProjectCardBadge.vue @@ -20,6 +20,7 @@ const enum ProjectState { Owned = 'owned', Personal = 'personal', Team = 'team', + SharedTeam = 'shared-team', Unknown = 'unknown', } @@ -44,6 +45,9 @@ const projectState = computed(() => { } return ProjectState.Personal; } else if (props.resource.homeProject?.type === ProjectTypes.Team) { + if (props.resource.sharedWithProjects?.length) { + return ProjectState.SharedTeam; + } return ProjectState.Team; } return ProjectState.Unknown; @@ -65,6 +69,7 @@ const badgeIcon = computed(() => { case ProjectState.SharedOwned: return 'user-friends'; case ProjectState.Team: + case ProjectState.SharedTeam: return 'archive'; default: return ''; @@ -99,6 +104,13 @@ const badgeTooltip = computed(() => { name: badgeText.value, }, }); + case ProjectState.SharedTeam: + return i18n.baseText('projects.badge.tooltip.sharedTeam', { + interpolate: { + resourceTypeLabel: props.resourceTypeLabel, + name: badgeText.value, + }, + }); default: return ''; } diff --git a/packages/editor-ui/src/components/Projects/ProjectSharing.vue b/packages/editor-ui/src/components/Projects/ProjectSharing.vue index 0166f43615057..b8a3ba9fefce0 100644 --- a/packages/editor-ui/src/components/Projects/ProjectSharing.vue +++ b/packages/editor-ui/src/components/Projects/ProjectSharing.vue @@ -29,11 +29,7 @@ const emit = defineEmits<{ const selectedProject = ref(Array.isArray(model.value) ? '' : model.value?.id ?? ''); const filter = ref(''); const selectPlaceholder = computed( - () => - props.placeholder ?? - (Array.isArray(model.value) - ? locale.baseText('projects.sharing.placeholder') - : locale.baseText('projects.sharing.placeholder.single')), + () => props.placeholder ?? locale.baseText('projects.sharing.select.placeholder'), ); const noDataText = computed( () => props.emptyOptionsText ?? locale.baseText('projects.sharing.noMatchingUsers'), diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index e4b312f77ff4b..772f4c2d5833e 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -532,7 +532,8 @@ "credentialEdit.oAuthButton.connectMyAccount": "Connect my account", "credentialEdit.oAuthButton.signInWithGoogle": "Sign in with Google", "credentialEdit.credentialSharing.info.owner": "Sharing a credential allows people to use it in their workflows. They cannot access credential details.", - "credentialEdit.credentialSharing.info.sharee": "Only {credentialOwnerName} can change who this credential is shared with", + "credentialEdit.credentialSharing.info.sharee.team": "Only users with credential sharing permission can change who this credential is shared with", + "credentialEdit.credentialSharing.info.sharee.personal": "Only {credentialOwnerName} or users with credential sharing permission can change who this credential is shared with", "credentialEdit.credentialSharing.info.sharee.fallback": "the owner", "credentialEdit.credentialSharing.list.delete": "Remove", "credentialEdit.credentialSharing.list.delete.confirm.title": "Remove access?", @@ -593,8 +594,6 @@ "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.", - "credentials.shareModal.info.members.number": "{number} member | {number} members", "dataDisplay.needHelp": "Need help?", "dataDisplay.nodeDocumentation": "Node Documentation", "dataDisplay.openDocumentationFor": "Open {nodeTypeDisplayName} documentation", @@ -1653,9 +1652,9 @@ "settings.users.setupToInviteUsers": "To invite users, set up your own account", "settings.users.setupToInviteUsersInfo": "Invited users won’t be able to see workflows and credentials of other users unless you upgrade. More info

", "settings.users.smtpToAddUsersWarning": "Set up SMTP before adding users (so that n8n can send them invitation emails). Instructions", - "settings.users.transferWorkflowsAndCredentials": "Transfer their workflows and credentials to another user", - "settings.users.transferWorkflowsAndCredentials.user": "User to transfer to", - "settings.users.transferWorkflowsAndCredentials.placeholder": "Select user", + "settings.users.transferWorkflowsAndCredentials": "Transfer their workflows and credentials to another user or project", + "settings.users.transferWorkflowsAndCredentials.user": "User or project to transfer to", + "settings.users.transferWorkflowsAndCredentials.placeholder": "Select project or user", "settings.users.transferredToUser": "Data transferred to {projectName}", "settings.users.userDeleted": "User deleted", "settings.users.userDeletedError": "Problem while deleting user", @@ -2459,8 +2458,8 @@ "projects.settings.role.editor": "Editor", "projects.settings.delete.title": "Delete {projectName}", "projects.settings.delete.message": "What should we do with the project data?", - "projects.settings.delete.question.transfer.label": "Transfer its workflows and credentials to another project", - "projects.settings.delete.question.transfer.title": "Project to transfer to", + "projects.settings.delete.question.transfer.label": "Transfer its workflows and credentials to another project or user", + "projects.settings.delete.question.transfer.title": "Project or user to transfer to", "projects.settings.delete.question.wipe.label": "Delete its workflows and credentials", "projects.settings.delete.question.wipe.title": "Type \"delete all data\" to confirm", "projects.settings.delete.question.wipe.placeholder": "delete all data", @@ -2473,9 +2472,10 @@ "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.sharing.noMatchingUsers": "No matching users or projects", + "projects.sharing.select.placeholder": "Select project or user", + "projects.sharing.select.placeholder.user": "Share with user(s)", + "projects.sharing.select.placeholder.project": "Share with projects or users", "projects.error.title": "Project error", "projects.create.limit": "{num} project | {num} projects", "projects.create.limitReached": "You have reached the {planName} plan limit of {limit}. Upgrade your plan to unlock more projects. {link}", @@ -2492,10 +2492,11 @@ "projects.move.resource.success.title": "Successfully moved {resourceTypeLabel}", "projects.move.resource.success.message": "{resourceName} {resourceTypeLabel} was moved to {targetProjectName} {link}", "projects.move.resource.success.link": "View {targetProjectName}", - "projects.badge.tooltip.sharedOwned": "This {resourceTypeLabel} is owned by you and shared with one or more other users", - "projects.badge.tooltip.sharedPersonal": "This {resourceTypeLabel} is owned by {name} and shared with one or more other users", + "projects.badge.tooltip.sharedOwned": "This {resourceTypeLabel} is owned by you and shared with one or more projects or users", + "projects.badge.tooltip.sharedPersonal": "This {resourceTypeLabel} is owned by {name} and shared with one or more projects or users", "projects.badge.tooltip.personal": "This {resourceTypeLabel} is owned by {name}", - "projects.badge.tooltip.team": "This {resourceTypeLabel} is owned by the {name} project. All users in this project have access to this.", + "projects.badge.tooltip.team": "This {resourceTypeLabel} is owned and accessible by the {name} project.", + "projects.badge.tooltip.sharedTeam": "This {resourceTypeLabel} is owned and accessible by the {name} project and shared with one or more projects or users", "mfa.setup.invalidAuthenticatorCode": "{code} is not a valid number", "mfa.setup.invalidCode": "Two-factor code failed. Please try again.", "mfa.code.modal.title": "Two-factor authentication", diff --git a/packages/editor-ui/src/stores/credentials.store.ts b/packages/editor-ui/src/stores/credentials.store.ts index e6759466145c9..e5be582ea2b4c 100644 --- a/packages/editor-ui/src/stores/credentials.store.ts +++ b/packages/editor-ui/src/stores/credentials.store.ts @@ -185,9 +185,15 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => { return (credential: ICredentialsResponse | IUsedCredential | undefined): string => { const { firstName, lastName, email } = splitName(credential?.homeProject?.name ?? ''); - return credential?.homeProject?.name - ? `${firstName} ${lastName} (${email})` - : i18n.baseText('credentialEdit.credentialSharing.info.sharee.fallback'); + if (credential?.homeProject?.name) { + if (lastName && email) { + return `${firstName} ${lastName} (${email})`; + } else { + return firstName; + } + } else { + return i18n.baseText('credentialEdit.credentialSharing.info.sharee.fallback'); + } }; }); @@ -314,6 +320,10 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => { projectId, ); + if (data?.homeProject && !credential.homeProject) { + credential.homeProject = data.homeProject as ProjectSharingData; + } + if (settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]) { upsertCredential(credential); if (data.sharedWithProjects) { diff --git a/packages/editor-ui/src/views/ProjectSettings.test.ts b/packages/editor-ui/src/views/ProjectSettings.test.ts index 594db154d5339..13a8707109a4f 100644 --- a/packages/editor-ui/src/views/ProjectSettings.test.ts +++ b/packages/editor-ui/src/views/ProjectSettings.test.ts @@ -11,6 +11,7 @@ import { useUsersStore } from '@/stores/users.store'; import { createProjectListItem } from '@/__tests__/data/projects'; import { useSettingsStore } from '@/stores/settings.store'; import type { IN8nUISettings } from 'n8n-workflow'; +import { ProjectTypes } from '@/types/projects.types'; vi.mock('vue-router', () => { const params = {}; @@ -28,7 +29,12 @@ vi.mock('vue-router', () => { const renderComponent = createComponentRenderer(ProjectSettings); -const teamProjects = Array.from({ length: 3 }, () => createProjectListItem('team')); +const projects = [ + ProjectTypes.Personal, + ProjectTypes.Personal, + ProjectTypes.Team, + ProjectTypes.Team, +].map(createProjectListItem); let router: ReturnType; let projectsStore: ReturnType; @@ -48,7 +54,7 @@ describe('ProjectSettings', () => { vi.spyOn(projectsStore, 'getAllProjects').mockImplementation( async () => await Promise.resolve(), ); - vi.spyOn(projectsStore, 'teamProjects', 'get').mockReturnValue(teamProjects); + vi.spyOn(projectsStore, 'projects', 'get').mockReturnValue(projects); vi.spyOn(settingsStore, 'settings', 'get').mockReturnValue({ enterprise: { projects: { diff --git a/packages/editor-ui/src/views/ProjectSettings.vue b/packages/editor-ui/src/views/ProjectSettings.vue index 31db1d66890a3..b9e3a77314bb6 100644 --- a/packages/editor-ui/src/views/ProjectSettings.vue +++ b/packages/editor-ui/src/views/ProjectSettings.vue @@ -59,7 +59,7 @@ const usersList = computed(() => ); const projects = computed(() => - projectsStore.teamProjects.filter((project) => project.id !== projectsStore.currentProjectId), + projectsStore.projects.filter((project) => project.id !== projectsStore.currentProjectId), ); const projectRoles = computed(() => rolesStore.processedProjectRoles.map((role) => ({ @@ -302,6 +302,7 @@ onMounted(() => { class="mr-2xs" :model-value="user?.role || projectRoles[0].role" size="small" + data-test-id="projects-settings-user-role-select" @update:model-value="onRoleAction(user, $event)" > ; let projectsStore: ReturnType; @@ -60,7 +66,7 @@ describe('SettingsUsersView', () => { vi.spyOn(projectsStore, 'getAllProjects').mockImplementation( async () => await Promise.resolve(), ); - vi.spyOn(projectsStore, 'personalProjects', 'get').mockReturnValue(personalProjects); + vi.spyOn(projectsStore, 'projects', 'get').mockReturnValue(projects); usersStore.currentUserId = loggedInUser.id; });