mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(editor): Display custom roles in the project role dropdown (#18983)
Co-authored-by: Andreas Fitzek <andreas.fitzek@n8n.io>
This commit is contained in:
committed by
GitHub
parent
d0ffd6e659
commit
bf198f8263
@@ -1,4 +1,4 @@
|
|||||||
import type { AllRolesMap } from '@n8n/permissions';
|
import { type AllRolesMap, PROJECT_OWNER_ROLE_SLUG } from '@n8n/permissions';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import * as rolesApi from '@n8n/rest-api-client/api/roles';
|
import * as rolesApi from '@n8n/rest-api-client/api/roles';
|
||||||
@@ -20,11 +20,11 @@ export const useRolesStore = defineStore('roles', () => {
|
|||||||
|
|
||||||
const processedProjectRoles = computed<AllRolesMap['project']>(() =>
|
const processedProjectRoles = computed<AllRolesMap['project']>(() =>
|
||||||
roles.value.project
|
roles.value.project
|
||||||
.filter((role) => projectRoleOrderMap.value.has(role.slug))
|
.filter((role) => role.slug !== PROJECT_OWNER_ROLE_SLUG)
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
(projectRoleOrderMap.value.get(a.slug) ?? 0) -
|
(projectRoleOrderMap.value.get(a.slug) ?? Number.MAX_SAFE_INTEGER) -
|
||||||
(projectRoleOrderMap.value.get(b.slug) ?? 0),
|
(projectRoleOrderMap.value.get(b.slug) ?? Number.MAX_SAFE_INTEGER),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { createProjectListItem } from '@/__tests__/data/projects';
|
|||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import type { FrontendSettings } from '@n8n/api-types';
|
import type { FrontendSettings } from '@n8n/api-types';
|
||||||
import { ProjectTypes } from '@/types/projects.types';
|
import { ProjectTypes } from '@/types/projects.types';
|
||||||
|
import { useRolesStore } from '@/stores/roles.store';
|
||||||
|
|
||||||
vi.mock('vue-router', () => {
|
vi.mock('vue-router', () => {
|
||||||
const params = {};
|
const params = {};
|
||||||
@@ -40,6 +41,7 @@ let router: ReturnType<typeof useRouter>;
|
|||||||
let projectsStore: ReturnType<typeof useProjectsStore>;
|
let projectsStore: ReturnType<typeof useProjectsStore>;
|
||||||
let usersStore: ReturnType<typeof useUsersStore>;
|
let usersStore: ReturnType<typeof useUsersStore>;
|
||||||
let settingsStore: ReturnType<typeof useSettingsStore>;
|
let settingsStore: ReturnType<typeof useSettingsStore>;
|
||||||
|
let rolesStore: ReturnType<typeof useRolesStore>;
|
||||||
|
|
||||||
describe('ProjectSettings', () => {
|
describe('ProjectSettings', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -49,6 +51,7 @@ describe('ProjectSettings', () => {
|
|||||||
projectsStore = useProjectsStore();
|
projectsStore = useProjectsStore();
|
||||||
usersStore = useUsersStore();
|
usersStore = useUsersStore();
|
||||||
settingsStore = useSettingsStore();
|
settingsStore = useSettingsStore();
|
||||||
|
rolesStore = useRolesStore();
|
||||||
|
|
||||||
vi.spyOn(usersStore, 'fetchUsers').mockImplementation(async () => await Promise.resolve());
|
vi.spyOn(usersStore, 'fetchUsers').mockImplementation(async () => await Promise.resolve());
|
||||||
vi.spyOn(projectsStore, 'getAvailableProjects').mockImplementation(async () => {});
|
vi.spyOn(projectsStore, 'getAvailableProjects').mockImplementation(async () => {});
|
||||||
@@ -66,12 +69,49 @@ describe('ProjectSettings', () => {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
} as FrontendSettings);
|
} as FrontendSettings);
|
||||||
|
vi.spyOn(rolesStore, 'processedProjectRoles', 'get').mockReturnValue([
|
||||||
|
{
|
||||||
|
slug: 'project:admin',
|
||||||
|
displayName: 'Project Admin',
|
||||||
|
description: 'Can manage project settings',
|
||||||
|
licensed: true,
|
||||||
|
roleType: 'project',
|
||||||
|
scopes: ['project:read', 'project:write', 'project:delete'],
|
||||||
|
systemRole: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'project:editor',
|
||||||
|
displayName: 'Project Editor',
|
||||||
|
description: 'Can edit project settings',
|
||||||
|
licensed: true,
|
||||||
|
roleType: 'project',
|
||||||
|
scopes: ['project:read', 'project:write'],
|
||||||
|
systemRole: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'project:custom',
|
||||||
|
displayName: 'Custom',
|
||||||
|
description: 'Can do some custom actions',
|
||||||
|
licensed: true,
|
||||||
|
roleType: 'project',
|
||||||
|
scopes: ['workflow:list'],
|
||||||
|
systemRole: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
projectsStore.setCurrentProject({
|
projectsStore.setCurrentProject({
|
||||||
id: '123',
|
id: '123',
|
||||||
type: 'team',
|
type: 'team',
|
||||||
name: 'Test Project',
|
name: 'Test Project',
|
||||||
icon: { type: 'icon', value: 'folder' },
|
icon: { type: 'icon', value: 'folder' },
|
||||||
relations: [],
|
relations: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
lastName: 'Doe',
|
||||||
|
firstName: 'John',
|
||||||
|
role: 'project:admin',
|
||||||
|
email: 'admin@example.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
scopes: [],
|
scopes: [],
|
||||||
@@ -132,4 +172,15 @@ describe('ProjectSettings', () => {
|
|||||||
expect(deleteProjectSpy).toHaveBeenCalledWith('123', undefined);
|
expect(deleteProjectSpy).toHaveBeenCalledWith('123', undefined);
|
||||||
expect(router.push).toHaveBeenCalledWith({ name: VIEWS.HOMEPAGE });
|
expect(router.push).toHaveBeenCalledWith({ name: VIEWS.HOMEPAGE });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show role dropdown', async () => {
|
||||||
|
const { getByTestId } = renderComponent();
|
||||||
|
const roleDropdown = getByTestId('projects-settings-user-role-select');
|
||||||
|
expect(roleDropdown).toBeVisible();
|
||||||
|
const roleDropdownItems = await getDropdownItems(roleDropdown);
|
||||||
|
expect(roleDropdownItems).toHaveLength(3);
|
||||||
|
expect(roleDropdownItems[0]).toHaveTextContent('Admin');
|
||||||
|
expect(roleDropdownItems[1]).toHaveTextContent('Editor');
|
||||||
|
expect(roleDropdownItems[2]).toHaveTextContent('Custom');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ const projects = computed(() =>
|
|||||||
const projectRoles = computed(() =>
|
const projectRoles = computed(() =>
|
||||||
rolesStore.processedProjectRoles.map((role) => ({
|
rolesStore.processedProjectRoles.map((role) => ({
|
||||||
...role,
|
...role,
|
||||||
displayName: projectRoleTranslations.value[role.slug],
|
displayName: projectRoleTranslations.value[role.slug] ?? role.displayName,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
const firstLicensedRole = computed(() => projectRoles.value.find((role) => role.licensed)?.slug);
|
const firstLicensedRole = computed(() => projectRoles.value.find((role) => role.licensed)?.slug);
|
||||||
|
|||||||
Reference in New Issue
Block a user