From 666867a236bce519dbd1a8f9162d4ced1b80d567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milorad=20FIlipovi=C4=87?= Date: Fri, 22 Mar 2024 11:33:39 +0100 Subject: [PATCH] feat: Fetch user cloud role and pass it on in website links (#8942) --- cypress/e2e/29-templates.cy.ts | 8 ++-- packages/editor-ui/src/Interface.ts | 1 + .../editor-ui/src/components/MainSidebar.vue | 5 ++- packages/editor-ui/src/constants.ts | 5 ++- .../src/plugins/i18n/locales/en.json | 1 + packages/editor-ui/src/plugins/icons/index.ts | 2 + .../editor-ui/src/stores/templates.store.ts | 45 ++++++++++++++----- .../editor-ui/src/views/WorkflowsView.vue | 36 +++++++++++++++ 8 files changed, 88 insertions(+), 15 deletions(-) diff --git a/cypress/e2e/29-templates.cy.ts b/cypress/e2e/29-templates.cy.ts index 2f3c144c84..34762b12fc 100644 --- a/cypress/e2e/29-templates.cy.ts +++ b/cypress/e2e/29-templates.cy.ts @@ -28,9 +28,11 @@ describe('Workflow templates', () => { // Link should contain instance address and n8n version mainSidebar.getters.templates().parent('a').then(($a) => { const href = $a.attr('href'); - // Link should have current instance address and n8n version - expect(href).to.include(`utm_instance=${window.location.origin}`); - expect(href).to.match(/utm_n8n_version=[0-9]+\.[0-9]+\.[0-9]+/); + const params = new URLSearchParams(href); + // Link should have all mandatory parameters expected on the website + expect(decodeURIComponent(`${params.get('utm_instance')}`)).to.include(window.location.origin); + expect(params.get('utm_n8n_version')).to.match(/[0-9]+\.[0-9]+\.[0-9]+/); + expect(params.get('utm_awc')).to.match(/[0-9]+/); }); mainSidebar.getters.templates().parent('a').should('have.attr', 'target', '_blank'); }); diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index ee0f46af58..2ed67fbaf4 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -1758,6 +1758,7 @@ export declare namespace Cloud { username: string; email: string; hasEarlyAccess?: boolean; + role?: string; }; } diff --git a/packages/editor-ui/src/components/MainSidebar.vue b/packages/editor-ui/src/components/MainSidebar.vue index 3d146d0f57..2605976062 100644 --- a/packages/editor-ui/src/components/MainSidebar.vue +++ b/packages/editor-ui/src/components/MainSidebar.vue @@ -387,7 +387,10 @@ export default defineComponent({ }); }, trackTemplatesClick() { - this.$telemetry.track('User clicked on templates', {}); + this.$telemetry.track('User clicked on templates', { + role: this.usersStore.currentUserCloudInfo?.role, + active_workflow_count: this.workflowsStore.activeWorkflows.length, + }); }, async onUserActionToggle(action: string) { switch (action) { diff --git a/packages/editor-ui/src/constants.ts b/packages/editor-ui/src/constants.ts index 86f4c4360f..c8d119f14f 100644 --- a/packages/editor-ui/src/constants.ts +++ b/packages/editor-ui/src/constants.ts @@ -752,7 +752,10 @@ export const MOUSE_EVENT_BUTTONS = { export const TEMPLATES_URLS = { DEFAULT_API_HOST: 'https://api.n8n.io/api/', BASE_WEBSITE_URL: 'https://n8n.io/workflows', - UTM_QUERY: 'utm_source=n8n_app&utm_medium=template_library', + UTM_QUERY: { + utm_source: 'n8n_app', + utm_medium: 'template_library', + }, }; export const ROLE = { diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index aac976ad5e..8f663343c4 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -2102,6 +2102,7 @@ "workflows.empty.description": "Create your first workflow", "workflows.empty.description.readOnlyEnv": "No workflows here yet", "workflows.empty.startFromScratch": "Start from scratch", + "workflows.empty.browseTemplates": "Browse {category} templates", "workflows.shareModal.title": "Share '{name}'", "workflows.shareModal.select.placeholder": "Add users...", "workflows.shareModal.list.delete": "Remove access", diff --git a/packages/editor-ui/src/plugins/icons/index.ts b/packages/editor-ui/src/plugins/icons/index.ts index 231c3e2d5e..d3fcf5bb6a 100644 --- a/packages/editor-ui/src/plugins/icons/index.ts +++ b/packages/editor-ui/src/plugins/icons/index.ts @@ -74,6 +74,7 @@ import { faGraduationCap, faGripLinesVertical, faGripVertical, + faHandHoldingUsd, faHandScissors, faHandPointLeft, faHashtag, @@ -237,6 +238,7 @@ export const FontAwesomePlugin: Plugin<{}> = { addIcon(faGlobe); addIcon(faGlobeAmericas); addIcon(faGraduationCap); + addIcon(faHandHoldingUsd); addIcon(faHandScissors); addIcon(faHandPointLeft); addIcon(faHashtag); diff --git a/packages/editor-ui/src/stores/templates.store.ts b/packages/editor-ui/src/stores/templates.store.ts index f695a5db25..d18f48c9ed 100644 --- a/packages/editor-ui/src/stores/templates.store.ts +++ b/packages/editor-ui/src/stores/templates.store.ts @@ -22,6 +22,8 @@ import { } from '@/api/templates'; import { getFixedNodesList } from '@/utils/nodeViewUtils'; import { useRootStore } from '@/stores/n8nRoot.store'; +import { useUsersStore } from './users.store'; +import { useWorkflowsStore } from './workflows.store'; const TEMPLATES_PAGE_SIZE = 20; @@ -115,14 +117,38 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, { const settingsStore = useSettingsStore(); return settingsStore.templatesHost !== TEMPLATES_URLS.DEFAULT_API_HOST; }, + /** + * Constructs URLSearchParams object based on the default parameters for the template repository + * and provided additional parameters + */ + getWebsiteTemplateRepositoryParameters() { + const rootStore = useRootStore(); + const userStore = useUsersStore(); + const workflowsStore = useWorkflowsStore(); + const defaultParameters: Record = { + ...TEMPLATES_URLS.UTM_QUERY, + utm_instance: this.currentN8nPath, + utm_n8n_version: rootStore.versionCli, + utm_awc: String(workflowsStore.activeWorkflows.length), + }; + if (userStore.currentUserCloudInfo?.role) { + defaultParameters.utm_user_role = userStore.currentUserCloudInfo.role; + } + return (additionalParameters: Record = {}) => { + return new URLSearchParams({ + ...defaultParameters, + ...additionalParameters, + }); + }; + }, /** * Construct the URL for the template repository on the website * @returns {string} */ getWebsiteTemplateRepositoryURL(): string { - return `${TEMPLATES_URLS.BASE_WEBSITE_URL}?${TEMPLATES_URLS.UTM_QUERY}&utm_instance=${ - this.currentN8nPath - }&utm_n8n_version=${useRootStore().versionCli}`; + return `${ + TEMPLATES_URLS.BASE_WEBSITE_URL + }?${this.getWebsiteTemplateRepositoryParameters().toString()}`; }, /** * Construct the URL for the template page on the website for a given template id @@ -130,20 +156,19 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, { */ getWebsiteTemplatePageURL() { return (id: string) => { - return `${TEMPLATES_URLS.BASE_WEBSITE_URL}/${id}?${TEMPLATES_URLS.UTM_QUERY}&utm_instance=${ - this.currentN8nPath - }&utm_n8n_version=${useRootStore().versionCli}`; + return `${ + TEMPLATES_URLS.BASE_WEBSITE_URL + }/${id}?${this.getWebsiteTemplateRepositoryParameters().toString()}`; }; }, /** * Construct the URL for the template category page on the website for a given category id - * @returns {function(string): string} */ getWebsiteCategoryURL() { return (id: string) => { - return `${TEMPLATES_URLS.BASE_WEBSITE_URL}/?categories=${id}&${ - TEMPLATES_URLS.UTM_QUERY - }&utm_instance=${this.currentN8nPath}&utm_n8n_version=${useRootStore().versionCli}`; + return `${TEMPLATES_URLS.BASE_WEBSITE_URL}/?${this.getWebsiteTemplateRepositoryParameters({ + categories: id, + }).toString()}`; }; }, }, diff --git a/packages/editor-ui/src/views/WorkflowsView.vue b/packages/editor-ui/src/views/WorkflowsView.vue index b6874e6eca..55e65da43e 100644 --- a/packages/editor-ui/src/views/WorkflowsView.vue +++ b/packages/editor-ui/src/views/WorkflowsView.vue @@ -84,6 +84,27 @@
+ + + + + {{ + $locale.baseText('workflows.empty.browseTemplates', { + interpolate: { category: 'Sales' }, + }) + }} + + +