Skip to content

Commit

Permalink
feat: Opt in to additional features on community for existing users (n…
Browse files Browse the repository at this point in the history
  • Loading branch information
cstuncsik authored Oct 9, 2024
1 parent c68782c commit c2adfc8
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 16 deletions.
3 changes: 1 addition & 2 deletions packages/cli/src/license/license.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ type LicenseError = Error & { errorId?: keyof typeof LicenseErrors };

export const LicenseErrors = {
SCHEMA_VALIDATION: 'Activation key is in the wrong format',
RESERVATION_EXHAUSTED:
'Activation key has been used too many times. Please contact sales@n8n.io if you would like to extend it',
RESERVATION_EXHAUSTED: 'Activation key has been used too many times',
RESERVATION_EXPIRED: 'Activation key has expired',
NOT_FOUND: 'Activation key not found',
RESERVATION_CONFLICT: 'Activation key not found',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ describe('CommunityPlusEnrollmentModal', () => {
createTestingPinia();
});

it('should not throw error opened only with the name', () => {
const props = {
modalName: COMMUNITY_PLUS_ENROLLMENT_MODAL,
};

expect(() => renderComponent({ props })).not.toThrow();
});

it('should test enrolling', async () => {
const closeCallbackSpy = vi.fn();
const usageStore = mockedStore(useUsageStore);
Expand Down Expand Up @@ -168,4 +176,18 @@ describe('CommunityPlusEnrollmentModal', () => {
const emailInput = getByRole('textbox');
expect(emailInput).toHaveValue('test@n8n.io');
});

it('should not throw error if no close callback provided', async () => {
const consoleErrorSpy = vi.spyOn(console, 'error');
const props = {
modalName: COMMUNITY_PLUS_ENROLLMENT_MODAL,
};

const { getByRole } = renderComponent({ props });
const skipButton = getByRole('button', { name: 'Skip' });
expect(skipButton).toBeVisible();

await userEvent.click(skipButton);
expect(consoleErrorSpy).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { useUsersStore } from '@/stores/users.store';
const props = defineProps<{
modalName: string;
data: {
closeCallback: () => void;
data?: {
closeCallback?: () => void;
};
}>();
Expand Down Expand Up @@ -51,7 +51,7 @@ const modalBus = createEventBus();
const closeModal = () => {
telemetry.track('User skipped community plus');
modalBus.emit('close');
props.data.closeCallback();
props.data?.closeCallback?.();
};
const confirm = async () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/editor-ui/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1788,6 +1788,8 @@
"settings.usageAndPlan.title": "Usage and plan",
"settings.usageAndPlan.description": "You’re on the {name} {type}",
"settings.usageAndPlan.plan": "Plan",
"settings.usageAndPlan.callOut": "{link} selected paid features for free (forever)",
"settings.usageAndPlan.callOut.link": "Unlock",
"settings.usageAndPlan.edition": "Edition",
"settings.usageAndPlan.error": "@:_reusableBaseText.error",
"settings.usageAndPlan.activeWorkflows": "Active workflows",
Expand Down Expand Up @@ -2662,7 +2664,7 @@
"feedback.positive": "I found this helpful",
"feedback.negative": "I didn't find this helpful",
"communityPlusModal.badge": "Time limited offer",
"communityPlusModal.title": "Unlock select paid features for free (forever)",
"communityPlusModal.title": "Get paid features for free (forever)",
"communityPlusModal.error.title": "License request failed",
"communityPlusModal.success.title": "Request sent",
"communityPlusModal.success.message": "License key will be sent to {email}",
Expand Down
23 changes: 21 additions & 2 deletions packages/editor-ui/src/views/SettingsUsageAndPlan.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { createTestingPinia } from '@pinia/testing';
import userEvent from '@testing-library/user-event';
import { createComponentRenderer } from '@/__tests__/render';
import { mockedStore } from '@/__tests__/utils';
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';

vi.mock('vue-router', () => {
return {
Expand All @@ -19,13 +22,18 @@ vi.mock('vue-router', () => {
});

let usageStore: ReturnType<typeof mockedStore<typeof useUsageStore>>;
let uiStore: ReturnType<typeof mockedStore<typeof useUIStore>>;

const renderComponent = createComponentRenderer(SettingsUsageAndPlan);

describe('SettingsUsageAndPlan', () => {
beforeEach(() => {
createTestingPinia();
usageStore = mockedStore(useUsageStore);
uiStore = mockedStore(useUIStore);

usageStore.viewPlansUrl = 'https://subscription.n8n.io';
usageStore.managePlanUrl = 'https://subscription.n8n.io';
});

it('should not throw errors when rendering', async () => {
Expand All @@ -38,10 +46,21 @@ describe('SettingsUsageAndPlan', () => {
expect(getByRole('heading').nextElementSibling).toBeNull();
});

it('should not show badge but unlock notice', async () => {
usageStore.isLoading = false;
usageStore.planName = 'Community';
const { getByRole, container } = renderComponent();
expect(getByRole('heading', { level: 3 })).toHaveTextContent('Community');
expect(container.querySelector('.n8n-badge')).toBeNull();

expect(getByRole('button', { name: 'Unlock' })).toBeVisible();

await userEvent.click(getByRole('button', { name: 'Unlock' }));
expect(uiStore.openModal).toHaveBeenCalledWith(COMMUNITY_PLUS_ENROLLMENT_MODAL);
});

it('should show community registered badge', async () => {
usageStore.isLoading = false;
usageStore.viewPlansUrl = 'https://subscription.n8n.io';
usageStore.managePlanUrl = 'https://subscription.n8n.io';
usageStore.planName = 'Registered Community';
const { getByRole, container } = renderComponent();
expect(getByRole('heading', { level: 3 })).toHaveTextContent('Community Edition');
Expand Down
33 changes: 25 additions & 8 deletions packages/editor-ui/src/views/SettingsUsageAndPlan.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { useUIStore } from '@/stores/ui.store';
import { useToast } from '@/composables/useToast';
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';
const usageStore = useUsageStore();
const route = useRoute();
Expand Down Expand Up @@ -40,6 +42,8 @@ const badgedPlanName = computed(() => {
};
});
const isCommunity = computed(() => usageStore.planName.toLowerCase() === 'community');
const isCommunityEditionRegistered = computed(
() => usageStore.planName.toLowerCase() === 'registered community',
);
Expand Down Expand Up @@ -133,6 +137,10 @@ const onDialogClosed = () => {
const onDialogOpened = () => {
activationKeyInput.value?.focus();
};
const openCommunityRegisterModal = () => {
uiStore.openModal(COMMUNITY_PLUS_ENROLLMENT_MODAL);
};
</script>

<template>
Expand All @@ -143,18 +151,16 @@ const onDialogOpened = () => {
<div v-if="!usageStore.isLoading">
<n8n-heading tag="h3" :class="$style.title" size="large">
<i18n-t keypath="settings.usageAndPlan.description" tag="span">
<template #name>{{
badgedPlanName.badge ? badgedPlanName.name : usageStore.planName
}}</template>
<template #name>{{ badgedPlanName.name ?? usageStore.planName }}</template>
<template #type>
<span v-if="usageStore.planId">{{
locale.baseText('settings.usageAndPlan.plan')
}}</span>
<span v-else>{{ locale.baseText('settings.usageAndPlan.edition') }}</span>
</template>
</i18n-t>
<span :class="$style.titleTooltip">
<N8nTooltip v-if="badgedPlanName.badge" placement="top">
<span v-if="badgedPlanName.badge && badgedPlanName.name" :class="$style.titleTooltip">
<N8nTooltip placement="top">
<template #content>
<i18n-t
v-if="isCommunityEditionRegistered"
Expand All @@ -167,6 +173,19 @@ const onDialogOpened = () => {
</span>
</n8n-heading>

<N8nNotice v-if="isCommunity" class="mt-0" theme="warning">
<i18n-t keypath="settings.usageAndPlan.callOut">
<template #link>
<N8nButton
class="pl-0 pr-0"
text
:label="locale.baseText('settings.usageAndPlan.callOut.link')"
@click="openCommunityRegisterModal"
/>
</template>
</i18n-t>
</N8nNotice>

<div :class="$style.quota">
<n8n-text size="medium" color="text-light">
{{ locale.baseText('settings.usageAndPlan.activeWorkflows') }}
Expand Down Expand Up @@ -194,9 +213,7 @@ const onDialogOpened = () => {
</div>
</div>

<n8n-info-tip>{{
locale.baseText('settings.usageAndPlan.activeWorkflows.hint')
}}</n8n-info-tip>
<N8nInfoTip>{{ locale.baseText('settings.usageAndPlan.activeWorkflows.hint') }}</N8nInfoTip>

<div :class="$style.buttons">
<n8n-button
Expand Down

0 comments on commit c2adfc8

Please sign in to comment.