mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
fix(editor): Add disabled state with tooltip on project creation buttons if user lacks permission (#13867)
This commit is contained in:
committed by
GitHub
parent
c7bcdc544d
commit
e33d0d7466
@@ -294,6 +294,7 @@ const {
|
|||||||
createCredentialsAppendSlotName,
|
createCredentialsAppendSlotName,
|
||||||
projectsLimitReachedMessage,
|
projectsLimitReachedMessage,
|
||||||
upgradeLabel,
|
upgradeLabel,
|
||||||
|
hasPermissionToCreateProjects,
|
||||||
} = useGlobalEntityCreation();
|
} = useGlobalEntityCreation();
|
||||||
onClickOutside(createBtn as Ref<VueInstance>, () => {
|
onClickOutside(createBtn as Ref<VueInstance>, () => {
|
||||||
createBtn.value?.close();
|
createBtn.value?.close();
|
||||||
@@ -385,7 +386,14 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
|
|||||||
placement="right"
|
placement="right"
|
||||||
:content="projectsLimitReachedMessage"
|
:content="projectsLimitReachedMessage"
|
||||||
>
|
>
|
||||||
|
<N8nIcon
|
||||||
|
v-if="!hasPermissionToCreateProjects"
|
||||||
|
style="margin-left: auto; margin-right: 5px"
|
||||||
|
icon="lock"
|
||||||
|
size="xsmall"
|
||||||
|
/>
|
||||||
<N8nButton
|
<N8nButton
|
||||||
|
v-else
|
||||||
:size="'mini'"
|
:size="'mini'"
|
||||||
style="margin-left: auto"
|
style="margin-left: auto"
|
||||||
type="tertiary"
|
type="tertiary"
|
||||||
|
|||||||
@@ -195,4 +195,22 @@ describe('ProjectsNavigation', () => {
|
|||||||
expect(getByTestId('project-plus-button')).toBeVisible();
|
expect(getByTestId('project-plus-button')).toBeVisible();
|
||||||
expect(getByTestId('add-first-project-button')).toBeVisible();
|
expect(getByTestId('add-first-project-button')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show project plus button and add first project button in disabled state if user does not have permission', async () => {
|
||||||
|
projectsStore.teamProjectsLimit = -1;
|
||||||
|
projectsStore.hasPermissionToCreateProjects = false;
|
||||||
|
|
||||||
|
const { getByTestId } = renderComponent({
|
||||||
|
props: {
|
||||||
|
collapsed: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const plusButton = getByTestId('project-plus-button');
|
||||||
|
const addFirstProjectButton = getByTestId('add-first-project-button');
|
||||||
|
|
||||||
|
expect(plusButton).toBeVisible();
|
||||||
|
expect(plusButton).toBeDisabled();
|
||||||
|
expect(addFirstProjectButton).toBeVisible();
|
||||||
|
expect(addFirstProjectButton).toBeDisabled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -83,15 +83,21 @@ const showAddFirstProject = computed(
|
|||||||
bold
|
bold
|
||||||
>
|
>
|
||||||
<span>{{ locale.baseText('projects.menu.title') }}</span>
|
<span>{{ locale.baseText('projects.menu.title') }}</span>
|
||||||
|
<N8nTooltip
|
||||||
|
placement="right"
|
||||||
|
:disabled="projectsStore.hasPermissionToCreateProjects"
|
||||||
|
:content="locale.baseText('projects.create.permissionDenied')"
|
||||||
|
>
|
||||||
<N8nButton
|
<N8nButton
|
||||||
v-if="projectsStore.canCreateProjects"
|
v-if="projectsStore.canCreateProjects"
|
||||||
icon="plus"
|
icon="plus"
|
||||||
text
|
text
|
||||||
data-test-id="project-plus-button"
|
data-test-id="project-plus-button"
|
||||||
:disabled="isCreatingProject"
|
:disabled="isCreatingProject || !projectsStore.hasPermissionToCreateProjects"
|
||||||
:class="$style.plusBtn"
|
:class="$style.plusBtn"
|
||||||
@click="globalEntityCreation.createProject"
|
@click="globalEntityCreation.createProject"
|
||||||
/>
|
/>
|
||||||
|
</N8nTooltip>
|
||||||
</N8nText>
|
</N8nText>
|
||||||
<ElMenu
|
<ElMenu
|
||||||
v-if="projectsStore.isTeamProjectFeatureEnabled || isFoldersFeatureEnabled"
|
v-if="projectsStore.isTeamProjectFeatureEnabled || isFoldersFeatureEnabled"
|
||||||
@@ -118,6 +124,11 @@ const showAddFirstProject = computed(
|
|||||||
data-test-id="project-menu-item"
|
data-test-id="project-menu-item"
|
||||||
/>
|
/>
|
||||||
</ElMenu>
|
</ElMenu>
|
||||||
|
<N8nTooltip
|
||||||
|
placement="right"
|
||||||
|
:disabled="projectsStore.hasPermissionToCreateProjects"
|
||||||
|
:content="locale.baseText('projects.create.permissionDenied')"
|
||||||
|
>
|
||||||
<N8nButton
|
<N8nButton
|
||||||
v-if="showAddFirstProject"
|
v-if="showAddFirstProject"
|
||||||
:class="[
|
:class="[
|
||||||
@@ -126,7 +137,7 @@ const showAddFirstProject = computed(
|
|||||||
[$style.collapsed]: props.collapsed,
|
[$style.collapsed]: props.collapsed,
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
:disabled="isCreatingProject"
|
:disabled="isCreatingProject || !projectsStore.hasPermissionToCreateProjects"
|
||||||
type="secondary"
|
type="secondary"
|
||||||
icon="plus"
|
icon="plus"
|
||||||
data-test-id="add-first-project-button"
|
data-test-id="add-first-project-button"
|
||||||
@@ -134,6 +145,7 @@ const showAddFirstProject = computed(
|
|||||||
>
|
>
|
||||||
{{ locale.baseText('projects.menu.addFirstProject') }}
|
{{ locale.baseText('projects.menu.addFirstProject') }}
|
||||||
</N8nButton>
|
</N8nButton>
|
||||||
|
</N8nTooltip>
|
||||||
<hr v-if="projectsStore.isTeamProjectFeatureEnabled" class="mb-m" />
|
<hr v-if="projectsStore.isTeamProjectFeatureEnabled" class="mb-m" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -99,6 +99,17 @@ describe('useGlobalEntityCreation', () => {
|
|||||||
expect(menu.value[0].submenu?.length).toBe(4);
|
expect(menu.value[0].submenu?.length).toBe(4);
|
||||||
expect(menu.value[1].submenu?.length).toBe(4);
|
expect(menu.value[1].submenu?.length).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('disables project creation item if user has no rbac permission', () => {
|
||||||
|
const projectsStore = mockedStore(useProjectsStore);
|
||||||
|
projectsStore.canCreateProjects = true;
|
||||||
|
projectsStore.isTeamProjectFeatureEnabled = true;
|
||||||
|
projectsStore.hasPermissionToCreateProjects = false;
|
||||||
|
|
||||||
|
const { menu, projectsLimitReachedMessage } = useGlobalEntityCreation();
|
||||||
|
expect(menu.value[2].disabled).toBeTruthy();
|
||||||
|
expect(projectsLimitReachedMessage.value).toContain('Your current role does not allow you');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('handleSelect()', () => {
|
describe('handleSelect()', () => {
|
||||||
@@ -115,6 +126,7 @@ describe('useGlobalEntityCreation', () => {
|
|||||||
const projectsStore = mockedStore(useProjectsStore);
|
const projectsStore = mockedStore(useProjectsStore);
|
||||||
projectsStore.isTeamProjectFeatureEnabled = true;
|
projectsStore.isTeamProjectFeatureEnabled = true;
|
||||||
projectsStore.canCreateProjects = true;
|
projectsStore.canCreateProjects = true;
|
||||||
|
projectsStore.hasPermissionToCreateProjects = true;
|
||||||
projectsStore.createProject.mockResolvedValueOnce({ name: 'test', id: '1' } as Project);
|
projectsStore.createProject.mockResolvedValueOnce({ name: 'test', id: '1' } as Project);
|
||||||
|
|
||||||
const { handleSelect } = useGlobalEntityCreation();
|
const { handleSelect } = useGlobalEntityCreation();
|
||||||
@@ -132,6 +144,7 @@ describe('useGlobalEntityCreation', () => {
|
|||||||
const projectsStore = mockedStore(useProjectsStore);
|
const projectsStore = mockedStore(useProjectsStore);
|
||||||
projectsStore.isTeamProjectFeatureEnabled = true;
|
projectsStore.isTeamProjectFeatureEnabled = true;
|
||||||
projectsStore.canCreateProjects = true;
|
projectsStore.canCreateProjects = true;
|
||||||
|
projectsStore.hasPermissionToCreateProjects = true;
|
||||||
projectsStore.createProject.mockRejectedValueOnce(new Error('error'));
|
projectsStore.createProject.mockRejectedValueOnce(new Error('error'));
|
||||||
|
|
||||||
const { handleSelect } = useGlobalEntityCreation();
|
const { handleSelect } = useGlobalEntityCreation();
|
||||||
@@ -162,6 +175,7 @@ describe('useGlobalEntityCreation', () => {
|
|||||||
const projectsStore = mockedStore(useProjectsStore);
|
const projectsStore = mockedStore(useProjectsStore);
|
||||||
projectsStore.isTeamProjectFeatureEnabled = true;
|
projectsStore.isTeamProjectFeatureEnabled = true;
|
||||||
projectsStore.teamProjectsLimit = 10;
|
projectsStore.teamProjectsLimit = 10;
|
||||||
|
projectsStore.hasPermissionToCreateProjects = true;
|
||||||
|
|
||||||
settingsStore.isCloudDeployment = true;
|
settingsStore.isCloudDeployment = true;
|
||||||
const { projectsLimitReachedMessage, upgradeLabel } = useGlobalEntityCreation();
|
const { projectsLimitReachedMessage, upgradeLabel } = useGlobalEntityCreation();
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export const useGlobalEntityCreation = () => {
|
|||||||
{
|
{
|
||||||
id: CREATE_PROJECT_ID,
|
id: CREATE_PROJECT_ID,
|
||||||
title: 'Project',
|
title: 'Project',
|
||||||
disabled: !projectsStore.canCreateProjects,
|
disabled: !projectsStore.canCreateProjects || !projectsStore.hasPermissionToCreateProjects,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
@@ -192,7 +192,7 @@ export const useGlobalEntityCreation = () => {
|
|||||||
const handleSelect = (id: string) => {
|
const handleSelect = (id: string) => {
|
||||||
if (id !== CREATE_PROJECT_ID) return;
|
if (id !== CREATE_PROJECT_ID) return;
|
||||||
|
|
||||||
if (projectsStore.canCreateProjects) {
|
if (projectsStore.canCreateProjects && projectsStore.hasPermissionToCreateProjects) {
|
||||||
void createProject();
|
void createProject();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -215,6 +215,10 @@ export const useGlobalEntityCreation = () => {
|
|||||||
return i18n.baseText('projects.create.limitReached.self');
|
return i18n.baseText('projects.create.limitReached.self');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!projectsStore.hasPermissionToCreateProjects) {
|
||||||
|
return i18n.baseText('projects.create.permissionDenied');
|
||||||
|
}
|
||||||
|
|
||||||
return i18n.baseText('projects.create.limitReached', {
|
return i18n.baseText('projects.create.limitReached', {
|
||||||
adjustToNumber: projectsStore.teamProjectsLimit,
|
adjustToNumber: projectsStore.teamProjectsLimit,
|
||||||
interpolate: {
|
interpolate: {
|
||||||
@@ -226,6 +230,7 @@ export const useGlobalEntityCreation = () => {
|
|||||||
const createProjectAppendSlotName = computed(() => `item.append.${CREATE_PROJECT_ID}`);
|
const createProjectAppendSlotName = computed(() => `item.append.${CREATE_PROJECT_ID}`);
|
||||||
const createWorkflowsAppendSlotName = computed(() => `item.append.${WORKFLOWS_MENU_ID}`);
|
const createWorkflowsAppendSlotName = computed(() => `item.append.${WORKFLOWS_MENU_ID}`);
|
||||||
const createCredentialsAppendSlotName = computed(() => `item.append.${CREDENTIALS_MENU_ID}`);
|
const createCredentialsAppendSlotName = computed(() => `item.append.${CREDENTIALS_MENU_ID}`);
|
||||||
|
const hasPermissionToCreateProjects = projectsStore.hasPermissionToCreateProjects;
|
||||||
|
|
||||||
const upgradeLabel = computed(() => {
|
const upgradeLabel = computed(() => {
|
||||||
if (settingsStore.isCloudDeployment) {
|
if (settingsStore.isCloudDeployment) {
|
||||||
@@ -246,6 +251,7 @@ export const useGlobalEntityCreation = () => {
|
|||||||
createWorkflowsAppendSlotName,
|
createWorkflowsAppendSlotName,
|
||||||
createCredentialsAppendSlotName,
|
createCredentialsAppendSlotName,
|
||||||
projectsLimitReachedMessage,
|
projectsLimitReachedMessage,
|
||||||
|
hasPermissionToCreateProjects,
|
||||||
upgradeLabel,
|
upgradeLabel,
|
||||||
createProject,
|
createProject,
|
||||||
isCreatingProject,
|
isCreatingProject,
|
||||||
|
|||||||
@@ -2689,6 +2689,7 @@
|
|||||||
"projects.create.limitReached.cloud": "You have reached the {planName} plan limit of {limit}. Upgrade your plan to unlock more projects.",
|
"projects.create.limitReached.cloud": "You have reached the {planName} plan limit of {limit}. Upgrade your plan to unlock more projects.",
|
||||||
"projects.create.limitReached.self": "Upgrade to unlock projects for more granular control over sharing, access and organisation of workflows",
|
"projects.create.limitReached.self": "Upgrade to unlock projects for more granular control over sharing, access and organisation of workflows",
|
||||||
"projects.create.limitReached.link": "View plans",
|
"projects.create.limitReached.link": "View plans",
|
||||||
|
"projects.create.permissionDenied": "Your current role does not allow you to create projects",
|
||||||
"projects.move.resource.modal.title": "Choose a project or user to move this {resourceTypeLabel} to",
|
"projects.move.resource.modal.title": "Choose a project or user to move this {resourceTypeLabel} to",
|
||||||
"projects.move.resource.modal.message": "\"{resourceName}\" is currently {inPersonalProject}{inTeamProject}",
|
"projects.move.resource.modal.message": "\"{resourceName}\" is currently {inPersonalProject}{inTeamProject}",
|
||||||
"projects.move.resource.modal.message.team": "in the \"{resourceHomeProjectName}\" project.",
|
"projects.move.resource.modal.message.team": "in the \"{resourceHomeProjectName}\" project.",
|
||||||
|
|||||||
Reference in New Issue
Block a user