mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-12 15:44:06 -08:00
fix(editor): Open Community+ enrollment modal only for the instance owner (#11292)
This commit is contained in:
parent
cd15e959c7
commit
76724c3be6
|
@ -3,6 +3,7 @@ export const RESOURCES = {
|
|||
annotationTag: [...DEFAULT_OPERATIONS] as const,
|
||||
auditLogs: ['manage'] as const,
|
||||
banner: ['dismiss'] as const,
|
||||
community: ['register'] as const,
|
||||
communityPackage: ['install', 'uninstall', 'update', 'list', 'manage'] as const,
|
||||
credential: ['share', 'move', ...DEFAULT_OPERATIONS] as const,
|
||||
externalSecretsProvider: ['sync', ...DEFAULT_OPERATIONS] as const,
|
||||
|
|
|
@ -13,6 +13,7 @@ export type WildcardScope = `${Resource}:*` | '*';
|
|||
export type AnnotationTagScope = ResourceScope<'annotationTag'>;
|
||||
export type AuditLogsScope = ResourceScope<'auditLogs', 'manage'>;
|
||||
export type BannerScope = ResourceScope<'banner', 'dismiss'>;
|
||||
export type CommunityScope = ResourceScope<'community', 'register'>;
|
||||
export type CommunityPackageScope = ResourceScope<
|
||||
'communityPackage',
|
||||
'install' | 'uninstall' | 'update' | 'list' | 'manage'
|
||||
|
@ -48,6 +49,7 @@ export type Scope =
|
|||
| AnnotationTagScope
|
||||
| AuditLogsScope
|
||||
| BannerScope
|
||||
| CommunityScope
|
||||
| CommunityPackageScope
|
||||
| CredentialScope
|
||||
| ExternalSecretProviderScope
|
||||
|
|
|
@ -15,6 +15,7 @@ export const GLOBAL_OWNER_SCOPES: Scope[] = [
|
|||
'credential:list',
|
||||
'credential:share',
|
||||
'credential:move',
|
||||
'community:register',
|
||||
'communityPackage:install',
|
||||
'communityPackage:uninstall',
|
||||
'communityPackage:update',
|
||||
|
|
|
@ -93,6 +93,7 @@ import { useExternalHooks } from '@/composables/useExternalHooks';
|
|||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { getResourcePermissions } from '@/permissions';
|
||||
|
||||
const SURVEY_VERSION = 'v4';
|
||||
|
||||
|
@ -110,7 +111,9 @@ const uiStore = useUIStore();
|
|||
|
||||
const formValues = ref<Record<string, string>>({});
|
||||
const isSaving = ref(false);
|
||||
|
||||
const userPermissions = computed(() =>
|
||||
getResourcePermissions(usersStore.currentUser?.globalScopes),
|
||||
);
|
||||
const survey = computed<IFormInputs>(() => [
|
||||
{
|
||||
name: COMPANY_TYPE_KEY,
|
||||
|
@ -548,23 +551,30 @@ const onSave = () => {
|
|||
formBus.emit('submit');
|
||||
};
|
||||
|
||||
const closeCallback = () => {
|
||||
const isPartOfOnboardingExperiment =
|
||||
posthogStore.getVariant(MORE_ONBOARDING_OPTIONS_EXPERIMENT.name) ===
|
||||
MORE_ONBOARDING_OPTIONS_EXPERIMENT.control;
|
||||
// In case the redirect to homepage for new users didn't happen
|
||||
// we try again after closing the modal
|
||||
if (route.name !== VIEWS.HOMEPAGE && !isPartOfOnboardingExperiment) {
|
||||
void router.replace({ name: VIEWS.HOMEPAGE });
|
||||
}
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
modalBus.emit('close');
|
||||
uiStore.openModalWithData({
|
||||
name: COMMUNITY_PLUS_ENROLLMENT_MODAL,
|
||||
data: {
|
||||
closeCallback: () => {
|
||||
const isPartOfOnboardingExperiment =
|
||||
posthogStore.getVariant(MORE_ONBOARDING_OPTIONS_EXPERIMENT.name) ===
|
||||
MORE_ONBOARDING_OPTIONS_EXPERIMENT.control;
|
||||
// In case the redirect to homepage for new users didn't happen
|
||||
// we try again after closing the modal
|
||||
if (route.name !== VIEWS.HOMEPAGE && !isPartOfOnboardingExperiment) {
|
||||
void router.replace({ name: VIEWS.HOMEPAGE });
|
||||
}
|
||||
|
||||
if (userPermissions.value.community.register) {
|
||||
uiStore.openModalWithData({
|
||||
name: COMMUNITY_PLUS_ENROLLMENT_MODAL,
|
||||
data: {
|
||||
closeCallback,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
closeCallback();
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async (values: IPersonalizationLatestVersion) => {
|
||||
|
|
|
@ -8,6 +8,7 @@ describe('permissions', () => {
|
|||
annotationTag: {},
|
||||
auditLogs: {},
|
||||
banner: {},
|
||||
community: {},
|
||||
communityPackage: {},
|
||||
credential: {},
|
||||
externalSecretsProvider: {},
|
||||
|
@ -62,6 +63,7 @@ describe('permissions', () => {
|
|||
annotationTag: {},
|
||||
auditLogs: {},
|
||||
banner: {},
|
||||
community: {},
|
||||
communityPackage: {},
|
||||
credential: {
|
||||
create: true,
|
||||
|
|
|
@ -27,6 +27,7 @@ export const useRBACStore = defineStore(STORES.RBAC, () => {
|
|||
eventBusDestination: {},
|
||||
auditLogs: {},
|
||||
banner: {},
|
||||
community: {},
|
||||
communityPackage: {},
|
||||
ldap: {},
|
||||
license: {},
|
||||
|
|
|
@ -6,6 +6,8 @@ import { useUsageStore } from '@/stores/usage.store';
|
|||
import SettingsUsageAndPlan from '@/views/SettingsUsageAndPlan.vue';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { COMMUNITY_PLUS_ENROLLMENT_MODAL } from '@/constants';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import type { IUser } from '@/Interface';
|
||||
|
||||
vi.mock('vue-router', () => {
|
||||
return {
|
||||
|
@ -23,6 +25,7 @@ vi.mock('vue-router', () => {
|
|||
|
||||
let usageStore: ReturnType<typeof mockedStore<typeof useUsageStore>>;
|
||||
let uiStore: ReturnType<typeof mockedStore<typeof useUIStore>>;
|
||||
let usersStore: ReturnType<typeof mockedStore<typeof useUsersStore>>;
|
||||
|
||||
const renderComponent = createComponentRenderer(SettingsUsageAndPlan);
|
||||
|
||||
|
@ -31,6 +34,7 @@ describe('SettingsUsageAndPlan', () => {
|
|||
createTestingPinia();
|
||||
usageStore = mockedStore(useUsageStore);
|
||||
uiStore = mockedStore(useUIStore);
|
||||
usersStore = mockedStore(useUsersStore);
|
||||
|
||||
usageStore.viewPlansUrl = 'https://subscription.n8n.io';
|
||||
usageStore.managePlanUrl = 'https://subscription.n8n.io';
|
||||
|
@ -49,6 +53,9 @@ describe('SettingsUsageAndPlan', () => {
|
|||
it('should not show badge but unlock notice', async () => {
|
||||
usageStore.isLoading = false;
|
||||
usageStore.planName = 'Community';
|
||||
usersStore.currentUser = {
|
||||
globalScopes: ['community:register'],
|
||||
} as IUser;
|
||||
const { getByRole, container } = renderComponent();
|
||||
expect(getByRole('heading', { level: 3 })).toHaveTextContent('Community');
|
||||
expect(container.querySelector('.n8n-badge')).toBeNull();
|
||||
|
|
|
@ -11,11 +11,14 @@ import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
|||
import { hasPermission } from '@/utils/rbac/permissions';
|
||||
import N8nInfoTip from 'n8n-design-system/components/N8nInfoTip';
|
||||
import { COMMUNITY_PLUS_ENROLLMENT_MODAL } from '@/constants';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import { getResourcePermissions } from '@/permissions';
|
||||
|
||||
const usageStore = useUsageStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const uiStore = useUIStore();
|
||||
const usersStore = useUsersStore();
|
||||
const toast = useToast();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
|
@ -48,6 +51,10 @@ const isCommunityEditionRegistered = computed(
|
|||
() => usageStore.planName.toLowerCase() === 'registered community',
|
||||
);
|
||||
|
||||
const canUserRegisterCommunityPlus = computed(
|
||||
() => getResourcePermissions(usersStore.currentUser?.globalScopes).community.register,
|
||||
);
|
||||
|
||||
const showActivationSuccess = () => {
|
||||
toast.showMessage({
|
||||
type: 'success',
|
||||
|
@ -173,7 +180,7 @@ const openCommunityRegisterModal = () => {
|
|||
</span>
|
||||
</n8n-heading>
|
||||
|
||||
<N8nNotice v-if="isCommunity" class="mt-0" theme="warning">
|
||||
<N8nNotice v-if="isCommunity && canUserRegisterCommunityPlus" class="mt-0" theme="warning">
|
||||
<i18n-t keypath="settings.usageAndPlan.callOut">
|
||||
<template #link>
|
||||
<N8nButton
|
||||
|
|
|
@ -103,7 +103,7 @@ const onSubmit = async (values: { [key: string]: string | boolean }) => {
|
|||
if (isPartOfOnboardingExperiment) {
|
||||
await router.push({ name: VIEWS.WORKFLOWS });
|
||||
} else {
|
||||
await router.push({ name: VIEWS.NEW_WORKFLOW });
|
||||
await router.push({ name: VIEWS.HOMEPAGE });
|
||||
}
|
||||
} else {
|
||||
await router.push({ name: VIEWS.USERS_SETTINGS });
|
||||
|
|
Loading…
Reference in a new issue