mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
composable to handle logic
This commit is contained in:
parent
463d101f35
commit
0adfd33e55
|
@ -0,0 +1,132 @@
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import { setActivePinia } from 'pinia';
|
||||||
|
import { useProjectsStore } from '@/stores/projects.store';
|
||||||
|
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||||
|
import { mockedStore } from '@/__tests__/utils';
|
||||||
|
import { VIEWS } from '@/constants';
|
||||||
|
import type { Project, ProjectListItem } from '@/types/projects.types';
|
||||||
|
|
||||||
|
import { useGlobalEntityCreation } from './useGlobalEntityCreation';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setActivePinia(createTestingPinia());
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useGlobalEntityCreation', () => {
|
||||||
|
it('should not contain projects for community', () => {
|
||||||
|
const projectsStore = mockedStore(useProjectsStore);
|
||||||
|
|
||||||
|
const personalProjectId = 'personal-project';
|
||||||
|
projectsStore.canCreateProjects = false;
|
||||||
|
projectsStore.personalProject = { id: personalProjectId } as Project;
|
||||||
|
const { menu } = useGlobalEntityCreation();
|
||||||
|
|
||||||
|
expect(menu.value[0]).toStrictEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
route: { name: VIEWS.NEW_WORKFLOW, query: { projectId: personalProjectId } },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(menu.value[1]).toStrictEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
route: {
|
||||||
|
name: VIEWS.CREDENTIALS,
|
||||||
|
params: { projectId: personalProjectId, credentialId: 'create' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('single project', () => {
|
||||||
|
const currentProjectId = 'current-project';
|
||||||
|
|
||||||
|
it('should use currentProject', () => {
|
||||||
|
const projectsStore = mockedStore(useProjectsStore);
|
||||||
|
|
||||||
|
projectsStore.canCreateProjects = true;
|
||||||
|
projectsStore.currentProject = { id: currentProjectId } as Project;
|
||||||
|
|
||||||
|
const { menu } = useGlobalEntityCreation(false);
|
||||||
|
|
||||||
|
expect(menu.value[0]).toStrictEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
route: { name: VIEWS.NEW_WORKFLOW, query: { projectId: currentProjectId } },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(menu.value[1]).toStrictEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
route: {
|
||||||
|
name: VIEWS.PROJECTS_CREDENTIALS,
|
||||||
|
params: { projectId: currentProjectId, credentialId: 'create' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be disabled in readOnly', () => {
|
||||||
|
const projectsStore = mockedStore(useProjectsStore);
|
||||||
|
|
||||||
|
projectsStore.canCreateProjects = true;
|
||||||
|
projectsStore.currentProject = { id: currentProjectId } as Project;
|
||||||
|
|
||||||
|
const sourceControl = mockedStore(useSourceControlStore);
|
||||||
|
sourceControl.preferences.branchReadOnly = true;
|
||||||
|
|
||||||
|
const { menu } = useGlobalEntityCreation(false);
|
||||||
|
|
||||||
|
expect(menu.value[0]).toStrictEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
disabled: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(menu.value[1]).toStrictEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
disabled: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be disabled based in scopes', () => {
|
||||||
|
const projectsStore = mockedStore(useProjectsStore);
|
||||||
|
|
||||||
|
projectsStore.canCreateProjects = true;
|
||||||
|
projectsStore.currentProject = { id: currentProjectId, scopes: [] } as unknown as Project;
|
||||||
|
|
||||||
|
const { menu } = useGlobalEntityCreation(false);
|
||||||
|
|
||||||
|
expect(menu.value[0]).toStrictEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
disabled: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(menu.value[1]).toStrictEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
disabled: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('global', () => {
|
||||||
|
it('should show personal + all team projects', () => {
|
||||||
|
const projectsStore = mockedStore(useProjectsStore);
|
||||||
|
|
||||||
|
const personalProjectId = 'personal-project';
|
||||||
|
projectsStore.canCreateProjects = true;
|
||||||
|
projectsStore.personalProject = { id: personalProjectId } as Project;
|
||||||
|
projectsStore.myProjects = [
|
||||||
|
{ id: '1', name: '1', type: 'team' },
|
||||||
|
{ id: '2', name: '2', type: 'public' },
|
||||||
|
{ id: '3', name: '3', type: 'team' },
|
||||||
|
] as ProjectListItem[];
|
||||||
|
|
||||||
|
const { menu } = useGlobalEntityCreation(true);
|
||||||
|
|
||||||
|
expect(menu.value[0].submenu?.length).toBe(4);
|
||||||
|
expect(menu.value[1].submenu?.length).toBe(4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
209
packages/editor-ui/src/composables/useGlobalEntityCreation.ts
Normal file
209
packages/editor-ui/src/composables/useGlobalEntityCreation.ts
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
import { computed, toValue, type ComputedRef, type Ref } from 'vue';
|
||||||
|
import { VIEWS } from '@/constants';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import { sortByProperty } from '@/utils/sortUtils';
|
||||||
|
import { useToast } from '@/composables/useToast';
|
||||||
|
import { useProjectsStore } from '@/stores/projects.store';
|
||||||
|
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||||
|
import { getResourcePermissions } from '@/permissions';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
import type { Scope } from '@n8n/permissions';
|
||||||
|
import type { RouteLocationRaw } from 'vue-router';
|
||||||
|
|
||||||
|
type BaseItem = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
icon?: string;
|
||||||
|
route?: RouteLocationRaw;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Item = BaseItem & {
|
||||||
|
submenu?: BaseItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGlobalEntityCreation = (
|
||||||
|
multipleProjects: Ref<boolean> | ComputedRef<boolean> | boolean = true,
|
||||||
|
) => {
|
||||||
|
const CREATE_PROJECT_ID = 'create-project';
|
||||||
|
|
||||||
|
const projectsStore = useProjectsStore();
|
||||||
|
const sourceControlStore = useSourceControlStore();
|
||||||
|
const router = useRouter();
|
||||||
|
const i18n = useI18n();
|
||||||
|
const toast = useToast();
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
const displayProjects = computed(() =>
|
||||||
|
sortByProperty(
|
||||||
|
'name',
|
||||||
|
projectsStore.myProjects.filter((p) => p.type === 'team'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const disabledWorkflow = (scopes: Scope[] = []): boolean =>
|
||||||
|
sourceControlStore.preferences.branchReadOnly ||
|
||||||
|
!getResourcePermissions(scopes).workflow.create;
|
||||||
|
|
||||||
|
const disabledCredential = (scopes: Scope[] = []): boolean =>
|
||||||
|
sourceControlStore.preferences.branchReadOnly ||
|
||||||
|
!getResourcePermissions(scopes).credential.create;
|
||||||
|
|
||||||
|
const menu = computed<Item[]>(() => {
|
||||||
|
// Community
|
||||||
|
if (!projectsStore.canCreateProjects) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'workflow',
|
||||||
|
title: 'Workflow',
|
||||||
|
route: {
|
||||||
|
name: VIEWS.NEW_WORKFLOW,
|
||||||
|
query: {
|
||||||
|
projectId: projectsStore.personalProject?.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'credential',
|
||||||
|
title: 'Credential',
|
||||||
|
route: {
|
||||||
|
name: VIEWS.CREDENTIALS,
|
||||||
|
params: {
|
||||||
|
projectId: projectsStore.personalProject?.id,
|
||||||
|
credentialId: 'create',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// single project
|
||||||
|
if (!toValue(multipleProjects)) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'workflow',
|
||||||
|
title: 'Workflow',
|
||||||
|
disabled: disabledWorkflow(projectsStore.currentProject?.scopes),
|
||||||
|
route: {
|
||||||
|
name: VIEWS.NEW_WORKFLOW,
|
||||||
|
query: {
|
||||||
|
projectId: projectsStore.currentProject?.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'credential',
|
||||||
|
title: 'Credential',
|
||||||
|
disabled: disabledCredential(projectsStore.currentProject?.scopes),
|
||||||
|
route: {
|
||||||
|
name: VIEWS.PROJECTS_CREDENTIALS,
|
||||||
|
params: {
|
||||||
|
projectId: projectsStore.currentProject?.id,
|
||||||
|
credentialId: 'create',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// global
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'workflow',
|
||||||
|
title: 'Workflow',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
id: 'workflow-title',
|
||||||
|
title: 'Create in',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'workflow-personal',
|
||||||
|
title: i18n.baseText('projects.menu.personal'),
|
||||||
|
icon: 'user',
|
||||||
|
disabled: disabledWorkflow(projectsStore.personalProject?.scopes),
|
||||||
|
route: {
|
||||||
|
name: VIEWS.NEW_WORKFLOW,
|
||||||
|
query: { projectId: projectsStore.personalProject?.id },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...displayProjects.value.map((project) => ({
|
||||||
|
id: `workflow-${project.id}`,
|
||||||
|
title: project.name ?? '',
|
||||||
|
icon: 'layer-group',
|
||||||
|
disabled: disabledWorkflow(project.scopes),
|
||||||
|
route: {
|
||||||
|
name: VIEWS.NEW_WORKFLOW,
|
||||||
|
query: { projectId: project.id },
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'credential',
|
||||||
|
title: 'Credential',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
id: 'credential-title',
|
||||||
|
title: 'Create in',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'credential-personal',
|
||||||
|
title: i18n.baseText('projects.menu.personal'),
|
||||||
|
icon: 'user',
|
||||||
|
disabled: disabledCredential(projectsStore.personalProject?.scopes),
|
||||||
|
route: {
|
||||||
|
name: VIEWS.PROJECTS_CREDENTIALS,
|
||||||
|
params: { projectId: projectsStore.personalProject?.id, credentialId: 'create' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...displayProjects.value.map((project) => ({
|
||||||
|
id: `credential-${project.id}`,
|
||||||
|
title: project.name ?? '',
|
||||||
|
icon: 'layer-group',
|
||||||
|
disabled: disabledCredential(project.scopes),
|
||||||
|
route: {
|
||||||
|
name: VIEWS.PROJECTS_CREDENTIALS,
|
||||||
|
params: { projectId: project.id, credentialId: 'create' },
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: CREATE_PROJECT_ID,
|
||||||
|
title: 'Project',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const createProject = async () => {
|
||||||
|
try {
|
||||||
|
const newProject = await projectsStore.createProject({
|
||||||
|
name: i18n.baseText('projects.settings.newProjectName'),
|
||||||
|
});
|
||||||
|
await router.push({ name: VIEWS.PROJECT_SETTINGS, params: { projectId: newProject.id } });
|
||||||
|
toast.showMessage({
|
||||||
|
title: i18n.baseText('projects.settings.save.successful.title', {
|
||||||
|
interpolate: { projectName: newProject.name ?? '' },
|
||||||
|
}),
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
toast.showError(error, i18n.baseText('projects.error.title'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = (id: string) => {
|
||||||
|
if (id !== CREATE_PROJECT_ID) return;
|
||||||
|
|
||||||
|
if (projectsStore.canCreateProjects) {
|
||||||
|
void createProject();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pageRedirectionHelper.goToUpgrade('rbac', 'upgrade-rbac');
|
||||||
|
};
|
||||||
|
|
||||||
|
return { menu, handleSelect };
|
||||||
|
};
|
Loading…
Reference in a new issue