Skip to content

Commit

Permalink
feat: Fetch user cloud role and pass it on in website links (#8942)
Browse files Browse the repository at this point in the history
  • Loading branch information
MiloradFilipovic committed Mar 22, 2024
1 parent 69363e4 commit 666867a
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 15 deletions.
8 changes: 5 additions & 3 deletions cypress/e2e/29-templates.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
Expand Down
1 change: 1 addition & 0 deletions packages/editor-ui/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1758,6 +1758,7 @@ export declare namespace Cloud {
username: string;
email: string;
hasEarlyAccess?: boolean;
role?: string;
};
}

Expand Down
5 changes: 4 additions & 1 deletion packages/editor-ui/src/components/MainSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
5 changes: 4 additions & 1 deletion packages/editor-ui/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
1 change: 1 addition & 0 deletions packages/editor-ui/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions packages/editor-ui/src/plugins/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import {
faGraduationCap,
faGripLinesVertical,
faGripVertical,
faHandHoldingUsd,
faHandScissors,
faHandPointLeft,
faHashtag,
Expand Down Expand Up @@ -237,6 +238,7 @@ export const FontAwesomePlugin: Plugin<{}> = {
addIcon(faGlobe);
addIcon(faGlobeAmericas);
addIcon(faGraduationCap);
addIcon(faHandHoldingUsd);
addIcon(faHandScissors);
addIcon(faHandPointLeft);
addIcon(faHashtag);
Expand Down
45 changes: 35 additions & 10 deletions packages/editor-ui/src/stores/templates.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -115,35 +117,58 @@ 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<string, string> = {
...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<string, string> = {}) => {
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
* @returns {function(string): string}
*/
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()}`;
};
},
},
Expand Down
36 changes: 36 additions & 0 deletions packages/editor-ui/src/views/WorkflowsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,27 @@
</n8n-text>
</div>
<div v-if="!readOnlyEnv" :class="['text-center', 'mt-2xl', $style.actionsContainer]">
<a
v-if="userCloudAccount?.role === 'Sales'"
:href="getTemplateRepositoryURL('Sales')"
:class="$style.emptyStateCard"
target="_blank"
>
<n8n-card
hoverable
data-test-id="browse-sales-templates-card"
@click="trackCategoryLinkClick('Sales')"
>
<n8n-icon :class="$style.emptyStateCardIcon" icon="hand-holding-usd" />
<n8n-text size="large" class="mt-xs" color="text-base">
{{
$locale.baseText('workflows.empty.browseTemplates', {
interpolate: { category: 'Sales' },
})
}}
</n8n-text>
</n8n-card>
</a>
<n8n-card
:class="$style.emptyStateCard"
hoverable
Expand Down Expand Up @@ -154,6 +175,7 @@ import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useUsersStore } from '@/stores/users.store';
import { useTemplatesStore } from '@/stores/templates.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useCredentialsStore } from '@/stores/credentials.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
Expand Down Expand Up @@ -205,6 +227,8 @@ const WorkflowsView = defineComponent({
useCredentialsStore,
useSourceControlStore,
useTagsStore,
useTemplatesStore,
useUsersStore,
),
readOnlyEnv(): boolean {
return this.sourceControlStore.preferences.branchReadOnly;
Expand Down Expand Up @@ -237,6 +261,9 @@ const WorkflowsView = defineComponent({
suggestedTemplates() {
return this.uiStore.suggestedTemplates;
},
userCloudAccount() {
return this.usersStore.currentUserCloudInfo;
},
},
watch: {
'filters.tags'() {
Expand Down Expand Up @@ -272,6 +299,15 @@ const WorkflowsView = defineComponent({
source: 'Workflows list',
});
},
getTemplateRepositoryURL(category: string) {
return this.templatesStore.getWebsiteCategoryURL(category);
},
trackCategoryLinkClick(category: string) {
this.$telemetry.track(`User clicked Browse ${category} Templates`, {
role: this.usersStore.currentUserCloudInfo?.role,
active_workflow_count: this.workflowsStore.activeWorkflows.length,
});
},
async initialize() {
await Promise.all([
this.usersStore.fetchUsers(),
Expand Down

0 comments on commit 666867a

Please sign in to comment.