refactor(editor): Remove Trial logic in personalization modal and port to script setup (#10649)

Co-authored-by: Csaba Tuncsik <csaba.tuncsik@gmail.com>
This commit is contained in:
Raúl Gómez Morales 2024-09-04 08:04:35 +02:00 committed by GitHub
parent 36177b0943
commit f114035a6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 648 additions and 754 deletions

View file

@ -0,0 +1,157 @@
import userEvent from '@testing-library/user-event';
import { createComponentRenderer } from '@/__tests__/render';
import { getDropdownItems, mockedStore } from '@/__tests__/utils';
import { createUser } from '@/__tests__/data/users';
import { useSettingsStore } from '@/stores/settings.store';
import PersonalizationModal from '@/components/PersonalizationModal.vue';
import { useUsersStore } from '@/stores/users.store';
import { createTestingPinia } from '@pinia/testing';
import {
COMPANY_TYPE_KEY,
EMAIL_KEY,
COMPANY_INDUSTRY_EXTENDED_KEY,
OTHER_COMPANY_INDUSTRY_EXTENDED_KEY,
MARKETING_AUTOMATION_GOAL_KEY,
OTHER_MARKETING_AUTOMATION_GOAL_KEY,
ROLE_KEY,
ROLE_OTHER_KEY,
DEVOPS_AUTOMATION_GOAL_OTHER_KEY,
DEVOPS_AUTOMATION_GOAL_KEY,
} from '@/constants';
const renderModal = createComponentRenderer(PersonalizationModal, {
global: {
stubs: {
Modal: {
template: `
<div>
<slot name="header" />
<slot name="title" />
<slot name="content" />
<slot name="footer" />
</div>
`,
},
},
},
});
describe('PersonalizationModal', () => {
it('mounts', () => {
const { getByTitle } = renderModal({ pinia: createTestingPinia() });
expect(getByTitle('Customize n8n to you')).toBeInTheDocument();
});
it('shows user input when needed for desktop deployment', () => {
const pinia = createTestingPinia();
const usersStore = mockedStore(useUsersStore);
usersStore.currentUser = createUser({ firstName: undefined });
const settingsStore = mockedStore(useSettingsStore);
settingsStore.isDesktopDeployment = true;
const { getByTestId } = renderModal({ pinia });
expect(getByTestId(EMAIL_KEY)).toBeInTheDocument();
});
describe('Company field', () => {
it('allows completion of company related fields', async () => {
const { getByTestId } = renderModal({ pinia: createTestingPinia() });
const companyTypeSelect = getByTestId(COMPANY_TYPE_KEY);
const otherTypeOfCompanyOption = [...(await getDropdownItems(companyTypeSelect))].find(
(node) => node.textContent === 'Other',
) as Element;
await userEvent.click(otherTypeOfCompanyOption);
const industrySelect = getByTestId(COMPANY_INDUSTRY_EXTENDED_KEY);
expect(industrySelect).toBeInTheDocument();
const otherIndustryOption = [...(await getDropdownItems(industrySelect))].find(
(node) => node.textContent === 'Other (please specify)',
) as Element;
await userEvent.click(otherIndustryOption);
expect(getByTestId(OTHER_COMPANY_INDUSTRY_EXTENDED_KEY)).toBeInTheDocument();
});
it('shows only company and source select when not used for work', async () => {
const { getByTestId, baseElement } = renderModal({ pinia: createTestingPinia() });
const companyTypeSelect = getByTestId(COMPANY_TYPE_KEY);
const nonWorkOption = [...(await getDropdownItems(companyTypeSelect))].find(
(node) => node.textContent === "I'm not using n8n for work",
) as Element;
await userEvent.click(nonWorkOption);
expect(baseElement.querySelectorAll('input').length).toBe(2);
});
});
it('allows completion of role related fields', async () => {
const { getByTestId, queryByTestId } = renderModal({ pinia: createTestingPinia() });
const roleSelect = getByTestId(ROLE_KEY);
const roleItems = [...(await getDropdownItems(roleSelect))];
const devOps = roleItems.find((node) => node.textContent === 'Devops') as Element;
const engineering = roleItems.find((node) => node.textContent === 'Engineering') as Element;
const it = roleItems.find((node) => node.textContent === 'IT') as Element;
const other = roleItems.find(
(node) => node.textContent === 'Other (please specify)',
) as Element;
await userEvent.click(devOps);
const automationGoalSelect = getByTestId(DEVOPS_AUTOMATION_GOAL_KEY);
expect(automationGoalSelect).toBeInTheDocument();
await userEvent.click(engineering);
expect(automationGoalSelect).toBeInTheDocument();
await userEvent.click(it);
expect(automationGoalSelect).toBeInTheDocument();
const otherGoalsItem = [...(await getDropdownItems(automationGoalSelect))].find(
(node) => node.textContent === 'Other',
) as Element;
await userEvent.click(otherGoalsItem);
expect(getByTestId(DEVOPS_AUTOMATION_GOAL_OTHER_KEY)).toBeInTheDocument();
await userEvent.click(other);
expect(queryByTestId(DEVOPS_AUTOMATION_GOAL_KEY)).not.toBeInTheDocument();
expect(getByTestId(ROLE_OTHER_KEY)).toBeInTheDocument();
});
it('allows completion of marketing and sales related fields', async () => {
const { getByTestId } = renderModal({ pinia: createTestingPinia() });
const companyTypeSelect = getByTestId(COMPANY_TYPE_KEY);
const anyWorkOption = [...(await getDropdownItems(companyTypeSelect))].find(
(node) => node.textContent !== "I'm not using n8n for work",
) as Element;
await userEvent.click(anyWorkOption);
const roleSelect = getByTestId(ROLE_KEY);
const salesAndMarketingOption = [...(await getDropdownItems(roleSelect))].find(
(node) => node.textContent === 'Sales and Marketing',
) as Element;
await userEvent.click(salesAndMarketingOption);
const salesAndMarketingSelect = getByTestId(MARKETING_AUTOMATION_GOAL_KEY);
const otherItem = [...(await getDropdownItems(salesAndMarketingSelect))].find(
(node) => node.textContent === 'Other',
) as Element;
await userEvent.click(otherItem);
expect(getByTestId(OTHER_MARKETING_AUTOMATION_GOAL_KEY)).toBeInTheDocument();
});
});

View file

@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { computed, ref } from 'vue';
import { mapStores } from 'pinia';
import { import {
COMPANY_SIZE_100_499, COMPANY_SIZE_100_499,
COMPANY_SIZE_1000_OR_MORE, COMPANY_SIZE_1000_OR_MORE,
@ -86,129 +85,76 @@ import {
import { useToast } from '@/composables/useToast'; import { useToast } from '@/composables/useToast';
import Modal from '@/components/Modal.vue'; import Modal from '@/components/Modal.vue';
import type { IFormInputs, IPersonalizationLatestVersion } from '@/Interface'; import type { IFormInputs, IPersonalizationLatestVersion } from '@/Interface';
import { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import { useRootStore } from '@/stores/root.store'; import { useRootStore } from '@/stores/root.store';
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
import { createEventBus, createFormEventBus } from 'n8n-design-system/utils'; import { createEventBus, createFormEventBus } from 'n8n-design-system/utils';
import { usePostHog } from '@/stores/posthog.store'; import { usePostHog } from '@/stores/posthog.store';
import { useExternalHooks } from '@/composables/useExternalHooks'; import { useExternalHooks } from '@/composables/useExternalHooks';
import { useUsageStore } from '@/stores/usage.store'; import { useI18n } from '@/composables/useI18n';
import { useMessage } from '@/composables/useMessage'; import { useRoute, useRouter } from 'vue-router';
const SURVEY_VERSION = 'v4'; const SURVEY_VERSION = 'v4';
export default defineComponent({
name: 'PersonalizationModal',
components: { Modal },
props: {
teleported: {
type: Boolean,
default: true,
},
},
setup() {
const externalHooks = useExternalHooks(); const externalHooks = useExternalHooks();
const modalBus = createEventBus();
const formBus = createFormEventBus();
const { showError } = useToast();
const i18n = useI18n();
const rootStore = useRootStore();
const settingsStore = useSettingsStore();
const usersStore = useUsersStore();
const posthogStore = usePostHog();
const route = useRoute();
const router = useRouter();
return { const formValues = ref<Record<string, string>>({});
externalHooks, const isSaving = ref(false);
...useToast(),
...useMessage(),
};
},
data() {
return {
formValues: {} as Record<string, string>,
isSaving: false,
PERSONALIZATION_MODAL_KEY,
otherWorkAreaFieldVisible: false,
otherCompanyIndustryFieldVisible: false,
showAllIndustryQuestions: true,
registerForEnterpriseTrial: false,
modalBus: createEventBus(),
formBus: createFormEventBus(),
domainBlocklist: [] as string[],
};
},
computed: {
...mapStores(
useRootStore,
useSettingsStore,
useUIStore,
useUsersStore,
useUsageStore,
usePostHog,
),
currentUser() {
return this.usersStore.currentUser;
},
canRegisterForEnterpriseTrial() {
if (
this.settingsStore.isCloudDeployment ||
this.domainBlocklist.length === 0 ||
!this.currentUser?.email
) {
return false;
}
const isSizeEligible = [COMPANY_SIZE_500_999, COMPANY_SIZE_1000_OR_MORE].includes( const survey = computed<IFormInputs>(() => [
this.formValues[COMPANY_SIZE_KEY],
);
const emailParts = this.currentUser.email.split('@');
const emailDomain = emailParts[emailParts.length - 1];
const isEmailEligible = !this.domainBlocklist.find(
(blocklistedDomain) => emailDomain === blocklistedDomain,
);
return isSizeEligible && isEmailEligible;
},
survey() {
const survey: IFormInputs = [
{ {
name: EMAIL_KEY, name: EMAIL_KEY,
properties: { properties: {
label: this.$locale.baseText('personalizationModal.yourEmailAddress'), label: i18n.baseText('personalizationModal.yourEmailAddress'),
type: 'text', type: 'text',
placeholder: this.$locale.baseText('personalizationModal.email'), placeholder: i18n.baseText('personalizationModal.email'),
}, },
shouldDisplay: () => shouldDisplay: () => settingsStore.isDesktopDeployment && !usersStore.currentUser?.firstName,
this.settingsStore.isDesktopDeployment && !this.usersStore.currentUser?.firstName,
}, },
{ {
name: COMPANY_TYPE_KEY, name: COMPANY_TYPE_KEY,
properties: { properties: {
label: this.$locale.baseText('personalizationModal.whatBestDescribesYourCompany'), label: i18n.baseText('personalizationModal.whatBestDescribesYourCompany'),
type: 'select', type: 'select',
placeholder: this.$locale.baseText('personalizationModal.select'), placeholder: i18n.baseText('personalizationModal.select'),
options: [ options: [
{ {
label: this.$locale.baseText('personalizationModal.saas'), label: i18n.baseText('personalizationModal.saas'),
value: SAAS_COMPANY_TYPE, value: SAAS_COMPANY_TYPE,
}, },
{ {
label: this.$locale.baseText('personalizationModal.eCommerce'), label: i18n.baseText('personalizationModal.eCommerce'),
value: ECOMMERCE_COMPANY_TYPE, value: ECOMMERCE_COMPANY_TYPE,
}, },
{ {
label: this.$locale.baseText('personalizationModal.digitalAgencyOrConsultant'), label: i18n.baseText('personalizationModal.digitalAgencyOrConsultant'),
value: DIGITAL_AGENCY_COMPANY_TYPE, value: DIGITAL_AGENCY_COMPANY_TYPE,
}, },
{ {
label: this.$locale.baseText('personalizationModal.systemsIntegrator'), label: i18n.baseText('personalizationModal.systemsIntegrator'),
value: SYSTEMS_INTEGRATOR_COMPANY_TYPE, value: SYSTEMS_INTEGRATOR_COMPANY_TYPE,
}, },
{ {
value: EDUCATION_TYPE, value: EDUCATION_TYPE,
label: this.$locale.baseText('personalizationModal.education'), label: i18n.baseText('personalizationModal.education'),
}, },
{ {
label: this.$locale.baseText('personalizationModal.other'), label: i18n.baseText('personalizationModal.other'),
value: OTHER_COMPANY_TYPE, value: OTHER_COMPANY_TYPE,
}, },
{ {
label: this.$locale.baseText('personalizationModal.imNotUsingN8nForWork'), label: i18n.baseText('personalizationModal.imNotUsingN8nForWork'),
value: PERSONAL_COMPANY_TYPE, value: PERSONAL_COMPANY_TYPE,
}, },
], ],
@ -218,64 +164,64 @@ export default defineComponent({
name: COMPANY_INDUSTRY_EXTENDED_KEY, name: COMPANY_INDUSTRY_EXTENDED_KEY,
properties: { properties: {
type: 'multi-select', type: 'multi-select',
label: this.$locale.baseText('personalizationModal.whichIndustriesIsYourCompanyIn'), label: i18n.baseText('personalizationModal.whichIndustriesIsYourCompanyIn'),
placeholder: this.$locale.baseText('personalizationModal.select'), placeholder: i18n.baseText('personalizationModal.select'),
options: [ options: [
{ {
value: FINANCE_INSURANCE_INDUSTRY, value: FINANCE_INSURANCE_INDUSTRY,
label: this.$locale.baseText('personalizationModal.financeOrInsurance'), label: i18n.baseText('personalizationModal.financeOrInsurance'),
}, },
{ {
value: GOVERNMENT_INDUSTRY, value: GOVERNMENT_INDUSTRY,
label: this.$locale.baseText('personalizationModal.government'), label: i18n.baseText('personalizationModal.government'),
}, },
{ {
value: HEALTHCARE_INDUSTRY, value: HEALTHCARE_INDUSTRY,
label: this.$locale.baseText('personalizationModal.healthcare'), label: i18n.baseText('personalizationModal.healthcare'),
}, },
{ {
value: IT_INDUSTRY, value: IT_INDUSTRY,
label: this.$locale.baseText('personalizationModal.it'), label: i18n.baseText('personalizationModal.it'),
}, },
{ {
value: LEGAL_INDUSTRY, value: LEGAL_INDUSTRY,
label: this.$locale.baseText('personalizationModal.legal'), label: i18n.baseText('personalizationModal.legal'),
}, },
{ {
value: MSP_INDUSTRY, value: MSP_INDUSTRY,
label: this.$locale.baseText('personalizationModal.managedServiceProvider'), label: i18n.baseText('personalizationModal.managedServiceProvider'),
}, },
{ {
value: MARKETING_INDUSTRY, value: MARKETING_INDUSTRY,
label: this.$locale.baseText('personalizationModal.marketing'), label: i18n.baseText('personalizationModal.marketing'),
}, },
{ {
value: MEDIA_INDUSTRY, value: MEDIA_INDUSTRY,
label: this.$locale.baseText('personalizationModal.media'), label: i18n.baseText('personalizationModal.media'),
}, },
{ {
value: MANUFACTURING_INDUSTRY, value: MANUFACTURING_INDUSTRY,
label: this.$locale.baseText('personalizationModal.manufacturing'), label: i18n.baseText('personalizationModal.manufacturing'),
}, },
{ {
value: PHYSICAL_RETAIL_OR_SERVICES, value: PHYSICAL_RETAIL_OR_SERVICES,
label: this.$locale.baseText('personalizationModal.physicalRetailOrServices'), label: i18n.baseText('personalizationModal.physicalRetailOrServices'),
}, },
{ {
value: REAL_ESTATE_OR_CONSTRUCTION, value: REAL_ESTATE_OR_CONSTRUCTION,
label: this.$locale.baseText('personalizationModal.realEstateOrConstruction'), label: i18n.baseText('personalizationModal.realEstateOrConstruction'),
}, },
{ {
value: SECURITY_INDUSTRY, value: SECURITY_INDUSTRY,
label: this.$locale.baseText('personalizationModal.security'), label: i18n.baseText('personalizationModal.security'),
}, },
{ {
value: TELECOMS_INDUSTRY, value: TELECOMS_INDUSTRY,
label: this.$locale.baseText('personalizationModal.telecoms'), label: i18n.baseText('personalizationModal.telecoms'),
}, },
{ {
value: OTHER_INDUSTRY_OPTION, value: OTHER_INDUSTRY_OPTION,
label: this.$locale.baseText('personalizationModal.otherPleaseSpecify'), label: i18n.baseText('personalizationModal.otherPleaseSpecify'),
}, },
], ],
}, },
@ -287,7 +233,7 @@ export default defineComponent({
{ {
name: OTHER_COMPANY_INDUSTRY_EXTENDED_KEY, name: OTHER_COMPANY_INDUSTRY_EXTENDED_KEY,
properties: { properties: {
placeholder: this.$locale.baseText('personalizationModal.specifyYourCompanysIndustry'), placeholder: i18n.baseText('personalizationModal.specifyYourCompanysIndustry'),
}, },
shouldDisplay(values): boolean { shouldDisplay(values): boolean {
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY]; const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
@ -305,44 +251,44 @@ export default defineComponent({
name: ROLE_KEY, name: ROLE_KEY,
properties: { properties: {
type: 'select', type: 'select',
label: this.$locale.baseText('personalizationModal.whichRoleBestDescribesYou'), label: i18n.baseText('personalizationModal.whichRoleBestDescribesYou'),
placeholder: this.$locale.baseText('personalizationModal.select'), placeholder: i18n.baseText('personalizationModal.select'),
options: [ options: [
{ {
value: ROLE_BUSINESS_OWNER, value: ROLE_BUSINESS_OWNER,
label: this.$locale.baseText('personalizationModal.businessOwner'), label: i18n.baseText('personalizationModal.businessOwner'),
}, },
{ {
value: ROLE_CUSTOMER_SUPPORT, value: ROLE_CUSTOMER_SUPPORT,
label: this.$locale.baseText('personalizationModal.customerSupport'), label: i18n.baseText('personalizationModal.customerSupport'),
}, },
{ {
value: ROLE_DATA_SCIENCE, value: ROLE_DATA_SCIENCE,
label: this.$locale.baseText('personalizationModal.dataScience'), label: i18n.baseText('personalizationModal.dataScience'),
}, },
{ {
value: ROLE_DEVOPS, value: ROLE_DEVOPS,
label: this.$locale.baseText('personalizationModal.devops'), label: i18n.baseText('personalizationModal.devops'),
}, },
{ {
value: ROLE_IT, value: ROLE_IT,
label: this.$locale.baseText('personalizationModal.it'), label: i18n.baseText('personalizationModal.it'),
}, },
{ {
value: ROLE_ENGINEERING, value: ROLE_ENGINEERING,
label: this.$locale.baseText('personalizationModal.engineering'), label: i18n.baseText('personalizationModal.engineering'),
}, },
{ {
value: ROLE_SALES_AND_MARKETING, value: ROLE_SALES_AND_MARKETING,
label: this.$locale.baseText('personalizationModal.salesAndMarketing'), label: i18n.baseText('personalizationModal.salesAndMarketing'),
}, },
{ {
value: ROLE_SECURITY, value: ROLE_SECURITY,
label: this.$locale.baseText('personalizationModal.security'), label: i18n.baseText('personalizationModal.security'),
}, },
{ {
value: ROLE_OTHER, value: ROLE_OTHER,
label: this.$locale.baseText('personalizationModal.otherPleaseSpecify'), label: i18n.baseText('personalizationModal.otherPleaseSpecify'),
}, },
], ],
}, },
@ -354,7 +300,7 @@ export default defineComponent({
{ {
name: ROLE_OTHER_KEY, name: ROLE_OTHER_KEY,
properties: { properties: {
placeholder: this.$locale.baseText('personalizationModal.specifyYourRole'), placeholder: i18n.baseText('personalizationModal.specifyYourRole'),
}, },
shouldDisplay(values): boolean { shouldDisplay(values): boolean {
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY]; const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
@ -366,42 +312,40 @@ export default defineComponent({
name: DEVOPS_AUTOMATION_GOAL_KEY, name: DEVOPS_AUTOMATION_GOAL_KEY,
properties: { properties: {
type: 'multi-select', type: 'multi-select',
label: this.$locale.baseText('personalizationModal.whatAreYouLookingToAutomate'), label: i18n.baseText('personalizationModal.whatAreYouLookingToAutomate'),
placeholder: this.$locale.baseText('personalizationModal.select'), placeholder: i18n.baseText('personalizationModal.select'),
options: [ options: [
{ {
value: DEVOPS_AUTOMATION_CI_CD_GOAL, value: DEVOPS_AUTOMATION_CI_CD_GOAL,
label: this.$locale.baseText('personalizationModal.cicd'), label: i18n.baseText('personalizationModal.cicd'),
}, },
{ {
value: DEVOPS_AUTOMATION_CLOUD_INFRASTRUCTURE_ORCHESTRATION_GOAL, value: DEVOPS_AUTOMATION_CLOUD_INFRASTRUCTURE_ORCHESTRATION_GOAL,
label: this.$locale.baseText( label: i18n.baseText('personalizationModal.cloudInfrastructureOrchestration'),
'personalizationModal.cloudInfrastructureOrchestration',
),
}, },
{ {
value: DEVOPS_AUTOMATION_DATA_SYNCING_GOAL, value: DEVOPS_AUTOMATION_DATA_SYNCING_GOAL,
label: this.$locale.baseText('personalizationModal.dataSynching'), label: i18n.baseText('personalizationModal.dataSynching'),
}, },
{ {
value: DEVOPS_INCIDENT_RESPONSE_GOAL, value: DEVOPS_INCIDENT_RESPONSE_GOAL,
label: this.$locale.baseText('personalizationModal.incidentResponse'), label: i18n.baseText('personalizationModal.incidentResponse'),
}, },
{ {
value: DEVOPS_MONITORING_AND_ALERTING_GOAL, value: DEVOPS_MONITORING_AND_ALERTING_GOAL,
label: this.$locale.baseText('personalizationModal.monitoringAndAlerting'), label: i18n.baseText('personalizationModal.monitoringAndAlerting'),
}, },
{ {
value: DEVOPS_REPORTING_GOAL, value: DEVOPS_REPORTING_GOAL,
label: this.$locale.baseText('personalizationModal.reporting'), label: i18n.baseText('personalizationModal.reporting'),
}, },
{ {
value: DEVOPS_TICKETING_SYSTEMS_INTEGRATIONS_GOAL, value: DEVOPS_TICKETING_SYSTEMS_INTEGRATIONS_GOAL,
label: this.$locale.baseText('personalizationModal.ticketingSystemsIntegrations'), label: i18n.baseText('personalizationModal.ticketingSystemsIntegrations'),
}, },
{ {
value: OTHER_AUTOMATION_GOAL, value: OTHER_AUTOMATION_GOAL,
label: this.$locale.baseText('personalizationModal.other'), label: i18n.baseText('personalizationModal.other'),
}, },
], ],
}, },
@ -417,7 +361,7 @@ export default defineComponent({
{ {
name: DEVOPS_AUTOMATION_GOAL_OTHER_KEY, name: DEVOPS_AUTOMATION_GOAL_OTHER_KEY,
properties: { properties: {
placeholder: this.$locale.baseText('personalizationModal.specifyYourAutomationGoal'), placeholder: i18n.baseText('personalizationModal.specifyYourAutomationGoal'),
}, },
shouldDisplay(values): boolean { shouldDisplay(values): boolean {
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY]; const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
@ -435,35 +379,35 @@ export default defineComponent({
name: MARKETING_AUTOMATION_GOAL_KEY, name: MARKETING_AUTOMATION_GOAL_KEY,
properties: { properties: {
type: 'multi-select', type: 'multi-select',
label: this.$locale.baseText('personalizationModal.specifySalesMarketingGoal'), label: i18n.baseText('personalizationModal.specifySalesMarketingGoal'),
placeholder: this.$locale.baseText('personalizationModal.select'), placeholder: i18n.baseText('personalizationModal.select'),
options: [ options: [
{ {
label: this.$locale.baseText('personalizationModal.leadGeneration'), label: i18n.baseText('personalizationModal.leadGeneration'),
value: MARKETING_AUTOMATION_LEAD_GENERATION_GOAL, value: MARKETING_AUTOMATION_LEAD_GENERATION_GOAL,
}, },
{ {
label: this.$locale.baseText('personalizationModal.customerCommunication'), label: i18n.baseText('personalizationModal.customerCommunication'),
value: MARKETING_AUTOMATION_CUSTOMER_COMMUNICATION, value: MARKETING_AUTOMATION_CUSTOMER_COMMUNICATION,
}, },
{ {
label: this.$locale.baseText('personalizationModal.customerActions'), label: i18n.baseText('personalizationModal.customerActions'),
value: MARKETING_AUTOMATION_ACTIONS, value: MARKETING_AUTOMATION_ACTIONS,
}, },
{ {
label: this.$locale.baseText('personalizationModal.adCampaign'), label: i18n.baseText('personalizationModal.adCampaign'),
value: MARKETING_AUTOMATION_AD_CAMPAIGN, value: MARKETING_AUTOMATION_AD_CAMPAIGN,
}, },
{ {
label: this.$locale.baseText('personalizationModal.reporting'), label: i18n.baseText('personalizationModal.reporting'),
value: MARKETING_AUTOMATION_REPORTING, value: MARKETING_AUTOMATION_REPORTING,
}, },
{ {
label: this.$locale.baseText('personalizationModal.dataSynching'), label: i18n.baseText('personalizationModal.dataSynching'),
value: MARKETING_AUTOMATION_DATA_SYNCHING, value: MARKETING_AUTOMATION_DATA_SYNCHING,
}, },
{ {
label: this.$locale.baseText('personalizationModal.other'), label: i18n.baseText('personalizationModal.other'),
value: MARKETING_AUTOMATION_OTHER, value: MARKETING_AUTOMATION_OTHER,
}, },
], ],
@ -477,9 +421,7 @@ export default defineComponent({
{ {
name: OTHER_MARKETING_AUTOMATION_GOAL_KEY, name: OTHER_MARKETING_AUTOMATION_GOAL_KEY,
properties: { properties: {
placeholder: this.$locale.baseText( placeholder: i18n.baseText('personalizationModal.specifyOtherSalesAndMarketingGoal'),
'personalizationModal.specifyOtherSalesAndMarketingGoal',
),
}, },
shouldDisplay(values): boolean { shouldDisplay(values): boolean {
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY]; const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
@ -497,19 +439,19 @@ export default defineComponent({
name: AUTOMATION_BENEFICIARY_KEY, name: AUTOMATION_BENEFICIARY_KEY,
properties: { properties: {
type: 'select', type: 'select',
label: this.$locale.baseText('personalizationModal.specifyAutomationBeneficiary'), label: i18n.baseText('personalizationModal.specifyAutomationBeneficiary'),
placeholder: this.$locale.baseText('personalizationModal.select'), placeholder: i18n.baseText('personalizationModal.select'),
options: [ options: [
{ {
label: this.$locale.baseText('personalizationModal.myself'), label: i18n.baseText('personalizationModal.myself'),
value: AUTOMATION_BENEFICIARY_SELF, value: AUTOMATION_BENEFICIARY_SELF,
}, },
{ {
label: this.$locale.baseText('personalizationModal.myTeam'), label: i18n.baseText('personalizationModal.myTeam'),
value: AUTOMATION_BENEFICIARY_MY_TEAM, value: AUTOMATION_BENEFICIARY_MY_TEAM,
}, },
{ {
label: this.$locale.baseText('personalizationModal.otherTeams'), label: i18n.baseText('personalizationModal.otherTeams'),
value: AUTOMATION_BENEFICIARY_OTHER_TEAMS, value: AUTOMATION_BENEFICIARY_OTHER_TEAMS,
}, },
], ],
@ -523,31 +465,31 @@ export default defineComponent({
name: COMPANY_SIZE_KEY, name: COMPANY_SIZE_KEY,
properties: { properties: {
type: 'select', type: 'select',
label: this.$locale.baseText('personalizationModal.howBigIsYourCompany'), label: i18n.baseText('personalizationModal.howBigIsYourCompany'),
placeholder: this.$locale.baseText('personalizationModal.select'), placeholder: i18n.baseText('personalizationModal.select'),
options: [ options: [
{ {
label: this.$locale.baseText('personalizationModal.lessThan20People'), label: i18n.baseText('personalizationModal.lessThan20People'),
value: COMPANY_SIZE_20_OR_LESS, value: COMPANY_SIZE_20_OR_LESS,
}, },
{ {
label: `20-99 ${this.$locale.baseText('personalizationModal.people')}`, label: `20-99 ${i18n.baseText('personalizationModal.people')}`,
value: COMPANY_SIZE_20_99, value: COMPANY_SIZE_20_99,
}, },
{ {
label: `100-499 ${this.$locale.baseText('personalizationModal.people')}`, label: `100-499 ${i18n.baseText('personalizationModal.people')}`,
value: COMPANY_SIZE_100_499, value: COMPANY_SIZE_100_499,
}, },
{ {
label: `500-999 ${this.$locale.baseText('personalizationModal.people')}`, label: `500-999 ${i18n.baseText('personalizationModal.people')}`,
value: COMPANY_SIZE_500_999, value: COMPANY_SIZE_500_999,
}, },
{ {
label: `1000+ ${this.$locale.baseText('personalizationModal.people')}`, label: `1000+ ${i18n.baseText('personalizationModal.people')}`,
value: COMPANY_SIZE_1000_OR_MORE, value: COMPANY_SIZE_1000_OR_MORE,
}, },
{ {
label: this.$locale.baseText('personalizationModal.imNotUsingN8nForWork'), label: i18n.baseText('personalizationModal.imNotUsingN8nForWork'),
value: COMPANY_SIZE_PERSONAL_USE, value: COMPANY_SIZE_PERSONAL_USE,
}, },
], ],
@ -561,8 +503,8 @@ export default defineComponent({
name: REPORTED_SOURCE_KEY, name: REPORTED_SOURCE_KEY,
properties: { properties: {
type: 'select', type: 'select',
label: this.$locale.baseText('personalizationModal.howDidYouHearAboutN8n'), label: i18n.baseText('personalizationModal.howDidYouHearAboutN8n'),
placeholder: this.$locale.baseText('personalizationModal.select'), placeholder: i18n.baseText('personalizationModal.select'),
options: [ options: [
{ {
label: 'Google', label: 'Google',
@ -581,19 +523,19 @@ export default defineComponent({
value: REPORTED_SOURCE_YOUTUBE, value: REPORTED_SOURCE_YOUTUBE,
}, },
{ {
label: this.$locale.baseText('personalizationModal.friendWordOfMouth'), label: i18n.baseText('personalizationModal.friendWordOfMouth'),
value: REPORTED_SOURCE_FRIEND, value: REPORTED_SOURCE_FRIEND,
}, },
{ {
label: this.$locale.baseText('personalizationModal.podcast'), label: i18n.baseText('personalizationModal.podcast'),
value: REPORTED_SOURCE_PODCAST, value: REPORTED_SOURCE_PODCAST,
}, },
{ {
label: this.$locale.baseText('personalizationModal.event'), label: i18n.baseText('personalizationModal.event'),
value: REPORTED_SOURCE_EVENT, value: REPORTED_SOURCE_EVENT,
}, },
{ {
label: this.$locale.baseText('personalizationModal.otherPleaseSpecify'), label: i18n.baseText('personalizationModal.otherPleaseSpecify'),
value: REPORTED_SOURCE_OTHER, value: REPORTED_SOURCE_OTHER,
}, },
], ],
@ -602,101 +544,54 @@ export default defineComponent({
{ {
name: REPORTED_SOURCE_OTHER_KEY, name: REPORTED_SOURCE_OTHER_KEY,
properties: { properties: {
placeholder: this.$locale.baseText('personalizationModal.specifyReportedSource'), placeholder: i18n.baseText('personalizationModal.specifyReportedSource'),
}, },
shouldDisplay(values): boolean { shouldDisplay(values): boolean {
const reportedSource = (values as IPersonalizationLatestVersion)[REPORTED_SOURCE_KEY]; const reportedSource = (values as IPersonalizationLatestVersion)[REPORTED_SOURCE_KEY];
return reportedSource === REPORTED_SOURCE_OTHER; return reportedSource === REPORTED_SOURCE_OTHER;
}, },
}, },
]; ]);
return survey; const onSave = () => {
}, formBus.emit('submit');
}, };
mounted() {
void this.loadDomainBlocklist(); const closeDialog = () => {
}, modalBus.emit('close');
methods: {
closeDialog() {
this.modalBus.emit('close');
const isPartOfOnboardingExperiment = const isPartOfOnboardingExperiment =
this.posthogStore.getVariant(MORE_ONBOARDING_OPTIONS_EXPERIMENT.name) === posthogStore.getVariant(MORE_ONBOARDING_OPTIONS_EXPERIMENT.name) ===
MORE_ONBOARDING_OPTIONS_EXPERIMENT.control; MORE_ONBOARDING_OPTIONS_EXPERIMENT.control;
// In case the redirect to homepage for new users didn't happen // In case the redirect to homepage for new users didn't happen
// we try again after closing the modal // we try again after closing the modal
if (this.$route.name !== VIEWS.HOMEPAGE && !isPartOfOnboardingExperiment) { if (route.name !== VIEWS.HOMEPAGE && !isPartOfOnboardingExperiment) {
void this.$router.replace({ name: VIEWS.HOMEPAGE }); void router.replace({ name: VIEWS.HOMEPAGE });
} }
}, };
async loadDomainBlocklist() {
try { const onSubmit = async (values: IPersonalizationLatestVersion) => {
this.domainBlocklist = (await import('email-providers/common.json')).default; isSaving.value = true;
} catch (error) {}
},
onSave() {
this.formBus.emit('submit');
},
async onSubmit(values: IPersonalizationLatestVersion): Promise<void> {
this.isSaving = true;
try { try {
const survey: IPersonalizationLatestVersion = { const completedSurvey: IPersonalizationLatestVersion = {
...values, ...values,
version: SURVEY_VERSION, version: SURVEY_VERSION,
personalization_survey_submitted_at: new Date().toISOString(), personalization_survey_submitted_at: new Date().toISOString(),
personalization_survey_n8n_version: this.rootStore.versionCli, personalization_survey_n8n_version: rootStore.versionCli,
}; };
await this.externalHooks.run('personalizationModal.onSubmit', survey); await externalHooks.run('personalizationModal.onSubmit', completedSurvey);
await this.usersStore.submitPersonalizationSurvey(survey); await usersStore.submitPersonalizationSurvey(completedSurvey);
this.posthogStore.setMetadata(survey, 'user'); posthogStore.setMetadata(completedSurvey, 'user');
if (Object.keys(values).length === 0) {
this.closeDialog();
}
} catch (e) { } catch (e) {
this.showError(e, 'Error while submitting results'); showError(e, 'Error while submitting results');
} finally {
isSaving.value = false;
closeDialog();
} }
};
let licenseRequestSucceeded = false;
try {
if (this.registerForEnterpriseTrial && this.canRegisterForEnterpriseTrial) {
await this.usageStore.requestEnterpriseLicenseTrial();
licenseRequestSucceeded = true;
this.$telemetry.track('User registered for self serve trial', {
email: this.usersStore.currentUser?.email,
instance_id: this.rootStore.instanceId,
});
}
} catch (e) {
this.showError(
e,
this.$locale.baseText('personalizationModal.registerEmailForTrial.error'),
);
}
this.isSaving = false;
this.closeDialog();
if (licenseRequestSucceeded) {
await this.alert(
this.$locale.baseText('personalizationModal.registerEmailForTrial.success.message'),
{
title: this.$locale.baseText(
'personalizationModal.registerEmailForTrial.success.title',
),
confirmButtonText: this.$locale.baseText(
'personalizationModal.registerEmailForTrial.success.button',
),
},
);
}
},
},
});
</script> </script>
<template> <template>
@ -720,24 +615,10 @@ export default defineComponent({
:inputs="survey" :inputs="survey"
:column-view="true" :column-view="true"
:event-bus="formBus" :event-bus="formBus"
:teleported="teleported" :teleported="true"
tag-size="small" tag-size="small"
@submit="onSubmit" @submit="onSubmit"
/> />
<n8n-card v-if="canRegisterForEnterpriseTrial">
<n8n-checkbox v-model="registerForEnterpriseTrial">
<i18n-t keypath="personalizationModal.registerEmailForTrial">
<template #trial>
<strong>
{{ $locale.baseText('personalizationModal.registerEmailForTrial.enterprise') }}
</strong>
</template>
</i18n-t>
<n8n-text size="small" tag="div" color="text-light">
{{ $locale.baseText('personalizationModal.registerEmailForTrial.notice') }}
</n8n-text>
</n8n-checkbox>
</n8n-card>
</div> </div>
</template> </template>
<template #footer> <template #footer>

View file

@ -1,144 +0,0 @@
import PersonalizationModal from '@/components/PersonalizationModal.vue';
import { createTestingPinia } from '@pinia/testing';
import userEvent from '@testing-library/user-event';
import { PERSONALIZATION_MODAL_KEY, ROLE, STORES, VIEWS } from '@/constants';
import { cleanupAppModals, createAppModals, retry } from '@/__tests__/utils';
import { createComponentRenderer } from '@/__tests__/render';
import { fireEvent } from '@testing-library/vue';
import { useUsersStore } from '@/stores/users.store';
import { useUsageStore } from '@/stores/usage.store';
const pinia = createTestingPinia({
initialState: {
[STORES.UI]: {
modalsById: {
[PERSONALIZATION_MODAL_KEY]: { open: true },
},
},
[STORES.SETTINGS]: {
settings: {
templates: {
host: '',
},
},
},
[STORES.USERS]: {
usersById: {
123: {
email: 'john@doe.com',
firstName: 'John',
lastName: 'Doe',
isDefaultUser: false,
isPendingUser: false,
role: ROLE.Owner,
mfaEnabled: false,
},
},
currentUserId: '123',
},
},
});
const renderComponent = createComponentRenderer(PersonalizationModal, {
props: {
teleported: false,
appendToBody: false,
},
pinia,
global: {
mocks: {
$route: {
name: VIEWS.HOMEPAGE,
},
},
},
});
describe('PersonalizationModal.vue', () => {
beforeEach(() => {
createAppModals();
});
afterEach(() => {
cleanupAppModals();
});
it('should render correctly', async () => {
const { getByTestId } = renderComponent();
await retry(() => expect(getByTestId('personalization-form')).toBeInTheDocument());
const modalContent = getByTestId('personalization-form');
expect(modalContent.querySelectorAll('.n8n-select').length).toEqual(5);
});
it('should display new option when role is "Devops", "Engineering", "IT", or "Sales and marketing"', async () => {
const { getByTestId } = renderComponent();
await retry(() => expect(getByTestId('personalization-form')).toBeInTheDocument());
for (const index of [3, 4, 5, 6]) {
const modalContent = getByTestId('personalization-form');
const expectFn = expect; // So we don't break @typescript-eslint/no-loop-func
const select = modalContent.querySelectorAll('.n8n-select')[1];
await fireEvent.click(select);
const item = select.querySelectorAll('.el-select-dropdown__item')[index];
await fireEvent.click(item);
await retry(() => {
expectFn(modalContent.querySelectorAll('.n8n-select').length).toEqual(6);
expectFn(modalContent.querySelector('[name^="automationGoal"]')).toBeInTheDocument();
});
}
});
it('should display self serve trial option when company size is larger than 500', async () => {
const { getByTestId } = renderComponent();
await retry(() => expect(getByTestId('personalization-form')).toBeInTheDocument());
const modalContent = getByTestId('personalization-form');
const select = modalContent.querySelectorAll('.n8n-select')[3];
await fireEvent.click(select);
const item = select.querySelectorAll('.el-select-dropdown__item')[3];
await fireEvent.click(item);
await retry(() => {
expect(modalContent.querySelector('.card')).not.toBeNull();
});
});
it('should display send telemetry when requesting enterprise trial', async () => {
const usersStore = useUsersStore(pinia);
vi.spyOn(usersStore, 'submitPersonalizationSurvey').mockResolvedValue();
const usageStore = useUsageStore(pinia);
const spyLicenseTrial = vi.spyOn(usageStore, 'requestEnterpriseLicenseTrial');
const { getByTestId, getByRole } = renderComponent();
await retry(() => expect(getByTestId('personalization-form')).toBeInTheDocument());
const modalContent = getByTestId('personalization-form');
const select = modalContent.querySelectorAll('.n8n-select')[3];
await fireEvent.click(select);
const item = select.querySelectorAll('.el-select-dropdown__item')[3];
await fireEvent.click(item);
const agreeCheckbox = modalContent.querySelector('.n8n-checkbox');
assert(agreeCheckbox);
await fireEvent.click(agreeCheckbox);
const submitButton = getByRole('button');
await userEvent.click(submitButton);
await retry(() => expect(spyLicenseTrial).toHaveBeenCalled());
});
});