Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(editor): Open Community+ enrollment modal only for the instance owner #11292

Merged
1 change: 1 addition & 0 deletions packages/@n8n/permissions/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions packages/@n8n/permissions/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -48,6 +49,7 @@ export type Scope =
| AnnotationTagScope
| AuditLogsScope
| BannerScope
| CommunityScope
| CommunityPackageScope
| CredentialScope
| ExternalSecretProviderScope
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/permissions/global-roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const GLOBAL_OWNER_SCOPES: Scope[] = [
'credential:list',
'credential:share',
'credential:move',
'community:register',
'communityPackage:install',
'communityPackage:uninstall',
'communityPackage:update',
Expand Down
40 changes: 25 additions & 15 deletions packages/editor-ui/src/components/PersonalizationModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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,
Expand Down Expand Up @@ -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) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/editor-ui/src/permissions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe('permissions', () => {
annotationTag: {},
auditLogs: {},
banner: {},
community: {},
communityPackage: {},
credential: {},
externalSecretsProvider: {},
Expand Down Expand Up @@ -62,6 +63,7 @@ describe('permissions', () => {
annotationTag: {},
auditLogs: {},
banner: {},
community: {},
communityPackage: {},
credential: {
create: true,
Expand Down
1 change: 1 addition & 0 deletions packages/editor-ui/src/stores/rbac.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const useRBACStore = defineStore(STORES.RBAC, () => {
eventBusDestination: {},
auditLogs: {},
banner: {},
community: {},
communityPackage: {},
ldap: {},
license: {},
Expand Down
7 changes: 7 additions & 0 deletions packages/editor-ui/src/views/SettingsUsageAndPlan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);

Expand All @@ -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';
Expand All @@ -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();
Expand Down
9 changes: 8 additions & 1 deletion packages/editor-ui/src/views/SettingsUsageAndPlan.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/editor-ui/src/views/SetupView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
Loading