mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
feat: Fetch user cloud role and pass it on in website links (#8942)
This commit is contained in:
parent
69363e461b
commit
666867a236
|
@ -28,9 +28,11 @@ describe('Workflow templates', () => {
|
||||||
// Link should contain instance address and n8n version
|
// Link should contain instance address and n8n version
|
||||||
mainSidebar.getters.templates().parent('a').then(($a) => {
|
mainSidebar.getters.templates().parent('a').then(($a) => {
|
||||||
const href = $a.attr('href');
|
const href = $a.attr('href');
|
||||||
// Link should have current instance address and n8n version
|
const params = new URLSearchParams(href);
|
||||||
expect(href).to.include(`utm_instance=${window.location.origin}`);
|
// Link should have all mandatory parameters expected on the website
|
||||||
expect(href).to.match(/utm_n8n_version=[0-9]+\.[0-9]+\.[0-9]+/);
|
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');
|
mainSidebar.getters.templates().parent('a').should('have.attr', 'target', '_blank');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1758,6 +1758,7 @@ export declare namespace Cloud {
|
||||||
username: string;
|
username: string;
|
||||||
email: string;
|
email: string;
|
||||||
hasEarlyAccess?: boolean;
|
hasEarlyAccess?: boolean;
|
||||||
|
role?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -387,7 +387,10 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
trackTemplatesClick() {
|
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) {
|
async onUserActionToggle(action: string) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
|
|
@ -752,7 +752,10 @@ export const MOUSE_EVENT_BUTTONS = {
|
||||||
export const TEMPLATES_URLS = {
|
export const TEMPLATES_URLS = {
|
||||||
DEFAULT_API_HOST: 'https://api.n8n.io/api/',
|
DEFAULT_API_HOST: 'https://api.n8n.io/api/',
|
||||||
BASE_WEBSITE_URL: 'https://n8n.io/workflows',
|
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 = {
|
export const ROLE = {
|
||||||
|
|
|
@ -2102,6 +2102,7 @@
|
||||||
"workflows.empty.description": "Create your first workflow",
|
"workflows.empty.description": "Create your first workflow",
|
||||||
"workflows.empty.description.readOnlyEnv": "No workflows here yet",
|
"workflows.empty.description.readOnlyEnv": "No workflows here yet",
|
||||||
"workflows.empty.startFromScratch": "Start from scratch",
|
"workflows.empty.startFromScratch": "Start from scratch",
|
||||||
|
"workflows.empty.browseTemplates": "Browse {category} templates",
|
||||||
"workflows.shareModal.title": "Share '{name}'",
|
"workflows.shareModal.title": "Share '{name}'",
|
||||||
"workflows.shareModal.select.placeholder": "Add users...",
|
"workflows.shareModal.select.placeholder": "Add users...",
|
||||||
"workflows.shareModal.list.delete": "Remove access",
|
"workflows.shareModal.list.delete": "Remove access",
|
||||||
|
|
|
@ -74,6 +74,7 @@ import {
|
||||||
faGraduationCap,
|
faGraduationCap,
|
||||||
faGripLinesVertical,
|
faGripLinesVertical,
|
||||||
faGripVertical,
|
faGripVertical,
|
||||||
|
faHandHoldingUsd,
|
||||||
faHandScissors,
|
faHandScissors,
|
||||||
faHandPointLeft,
|
faHandPointLeft,
|
||||||
faHashtag,
|
faHashtag,
|
||||||
|
@ -237,6 +238,7 @@ export const FontAwesomePlugin: Plugin<{}> = {
|
||||||
addIcon(faGlobe);
|
addIcon(faGlobe);
|
||||||
addIcon(faGlobeAmericas);
|
addIcon(faGlobeAmericas);
|
||||||
addIcon(faGraduationCap);
|
addIcon(faGraduationCap);
|
||||||
|
addIcon(faHandHoldingUsd);
|
||||||
addIcon(faHandScissors);
|
addIcon(faHandScissors);
|
||||||
addIcon(faHandPointLeft);
|
addIcon(faHandPointLeft);
|
||||||
addIcon(faHashtag);
|
addIcon(faHashtag);
|
||||||
|
|
|
@ -22,6 +22,8 @@ import {
|
||||||
} from '@/api/templates';
|
} from '@/api/templates';
|
||||||
import { getFixedNodesList } from '@/utils/nodeViewUtils';
|
import { getFixedNodesList } from '@/utils/nodeViewUtils';
|
||||||
import { useRootStore } from '@/stores/n8nRoot.store';
|
import { useRootStore } from '@/stores/n8nRoot.store';
|
||||||
|
import { useUsersStore } from './users.store';
|
||||||
|
import { useWorkflowsStore } from './workflows.store';
|
||||||
|
|
||||||
const TEMPLATES_PAGE_SIZE = 20;
|
const TEMPLATES_PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
@ -115,14 +117,38 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
return settingsStore.templatesHost !== TEMPLATES_URLS.DEFAULT_API_HOST;
|
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
|
* Construct the URL for the template repository on the website
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
getWebsiteTemplateRepositoryURL(): string {
|
getWebsiteTemplateRepositoryURL(): string {
|
||||||
return `${TEMPLATES_URLS.BASE_WEBSITE_URL}?${TEMPLATES_URLS.UTM_QUERY}&utm_instance=${
|
return `${
|
||||||
this.currentN8nPath
|
TEMPLATES_URLS.BASE_WEBSITE_URL
|
||||||
}&utm_n8n_version=${useRootStore().versionCli}`;
|
}?${this.getWebsiteTemplateRepositoryParameters().toString()}`;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Construct the URL for the template page on the website for a given template id
|
* 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() {
|
getWebsiteTemplatePageURL() {
|
||||||
return (id: string) => {
|
return (id: string) => {
|
||||||
return `${TEMPLATES_URLS.BASE_WEBSITE_URL}/${id}?${TEMPLATES_URLS.UTM_QUERY}&utm_instance=${
|
return `${
|
||||||
this.currentN8nPath
|
TEMPLATES_URLS.BASE_WEBSITE_URL
|
||||||
}&utm_n8n_version=${useRootStore().versionCli}`;
|
}/${id}?${this.getWebsiteTemplateRepositoryParameters().toString()}`;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Construct the URL for the template category page on the website for a given category id
|
* Construct the URL for the template category page on the website for a given category id
|
||||||
* @returns {function(string): string}
|
|
||||||
*/
|
*/
|
||||||
getWebsiteCategoryURL() {
|
getWebsiteCategoryURL() {
|
||||||
return (id: string) => {
|
return (id: string) => {
|
||||||
return `${TEMPLATES_URLS.BASE_WEBSITE_URL}/?categories=${id}&${
|
return `${TEMPLATES_URLS.BASE_WEBSITE_URL}/?${this.getWebsiteTemplateRepositoryParameters({
|
||||||
TEMPLATES_URLS.UTM_QUERY
|
categories: id,
|
||||||
}&utm_instance=${this.currentN8nPath}&utm_n8n_version=${useRootStore().versionCli}`;
|
}).toString()}`;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -84,6 +84,27 @@
|
||||||
</n8n-text>
|
</n8n-text>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!readOnlyEnv" :class="['text-center', 'mt-2xl', $style.actionsContainer]">
|
<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
|
<n8n-card
|
||||||
:class="$style.emptyStateCard"
|
:class="$style.emptyStateCard"
|
||||||
hoverable
|
hoverable
|
||||||
|
@ -154,6 +175,7 @@ import { mapStores } from 'pinia';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
|
import { useTemplatesStore } from '@/stores/templates.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||||
|
@ -205,6 +227,8 @@ const WorkflowsView = defineComponent({
|
||||||
useCredentialsStore,
|
useCredentialsStore,
|
||||||
useSourceControlStore,
|
useSourceControlStore,
|
||||||
useTagsStore,
|
useTagsStore,
|
||||||
|
useTemplatesStore,
|
||||||
|
useUsersStore,
|
||||||
),
|
),
|
||||||
readOnlyEnv(): boolean {
|
readOnlyEnv(): boolean {
|
||||||
return this.sourceControlStore.preferences.branchReadOnly;
|
return this.sourceControlStore.preferences.branchReadOnly;
|
||||||
|
@ -237,6 +261,9 @@ const WorkflowsView = defineComponent({
|
||||||
suggestedTemplates() {
|
suggestedTemplates() {
|
||||||
return this.uiStore.suggestedTemplates;
|
return this.uiStore.suggestedTemplates;
|
||||||
},
|
},
|
||||||
|
userCloudAccount() {
|
||||||
|
return this.usersStore.currentUserCloudInfo;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'filters.tags'() {
|
'filters.tags'() {
|
||||||
|
@ -272,6 +299,15 @@ const WorkflowsView = defineComponent({
|
||||||
source: 'Workflows list',
|
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() {
|
async initialize() {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.usersStore.fetchUsers(),
|
this.usersStore.fetchUsers(),
|
||||||
|
|
Loading…
Reference in a new issue