mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat(editor): Update suggested workflows experiment (no-changelog) (#17701)
This commit is contained in:
@@ -31,7 +31,7 @@ export declare namespace Cloud {
|
||||
email: string;
|
||||
hasEarlyAccess?: boolean;
|
||||
role?: string;
|
||||
selectedApps?: string;
|
||||
selectedApps?: string[];
|
||||
information?: {
|
||||
[key: string]: string | string[];
|
||||
};
|
||||
|
||||
@@ -32,4 +32,5 @@ export const STORES = {
|
||||
MODULES: 'modules',
|
||||
FOCUS_PANEL: 'focusPanel',
|
||||
AI_TEMPLATES_STARTER_COLLECTION: 'aiTemplatesStarterCollection',
|
||||
PERSONALIZED_TEMPLATES: 'personalizedTemplates',
|
||||
} as const;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
import { VIEWS } from '@/constants';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
|
||||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
|
||||
const navigateTo = () => {
|
||||
void router.push({ name: VIEWS.TEMPLATES });
|
||||
void router.back();
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useTemplatesStore } from '@/stores/templates.store';
|
||||
import { usePersonalizedTemplatesStore } from '@/experiments/personalizedTemplates/stores/personalizedTemplates.store';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { ref } from 'vue';
|
||||
|
||||
type SuggestedWorkflow = {
|
||||
id: number;
|
||||
@@ -13,19 +12,27 @@ const props = defineProps<{
|
||||
}>();
|
||||
const { data } = props;
|
||||
|
||||
const templatesStore = useTemplatesStore();
|
||||
const {
|
||||
dismissSuggestedWorkflow,
|
||||
getTemplateRoute,
|
||||
trackUserClickedOnPersonalizedTemplate,
|
||||
trackUserDismissedCallout,
|
||||
} = usePersonalizedTemplatesStore();
|
||||
const locale = useI18n();
|
||||
|
||||
const isVisible = ref(true);
|
||||
const onDismissCallout = () => {
|
||||
trackUserDismissedCallout(data.id);
|
||||
dismissSuggestedWorkflow(data.id);
|
||||
};
|
||||
|
||||
const dismissCallout = () => {
|
||||
templatesStore.experimentalDismissSuggestedWorkflow(data.id);
|
||||
const onTryTemplate = () => {
|
||||
trackUserClickedOnPersonalizedTemplate(data.id);
|
||||
dismissSuggestedWorkflow(data.id);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<N8nCallout
|
||||
v-if="isVisible"
|
||||
theme="secondary"
|
||||
:iconless="true"
|
||||
:class="$style['suggested-workflow-callout']"
|
||||
@@ -39,7 +46,8 @@ const dismissCallout = () => {
|
||||
<N8nLink
|
||||
data-test-id="suggested-workflow-button"
|
||||
size="small"
|
||||
:href="templatesStore.websiteTemplateURLById(data.id.toString())"
|
||||
:to="getTemplateRoute(data.id)"
|
||||
@click="onTryTemplate"
|
||||
>
|
||||
{{ locale.baseText('workflows.itemSuggestion.try') }}
|
||||
</N8nLink>
|
||||
@@ -48,7 +56,7 @@ const dismissCallout = () => {
|
||||
icon="x"
|
||||
:title="locale.baseText('generic.dismiss')"
|
||||
class="clickable"
|
||||
@click="dismissCallout"
|
||||
@click="onDismissCallout"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,184 @@
|
||||
import { useStorage } from '@/composables/useStorage';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import {
|
||||
LOCAL_STORAGE_EXPERIMENTAL_DISMISSED_SUGGESTED_WORKFLOWS,
|
||||
TEMPLATE_ONBOARDING_EXPERIMENT,
|
||||
VIEWS,
|
||||
} from '@/constants';
|
||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
import { useTemplatesStore } from '@/stores/templates.store';
|
||||
import type { ITemplatesWorkflowFull } from '@n8n/rest-api-client';
|
||||
import { STORES } from '@n8n/stores';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
const SIMPLE_TEMPLATES = [6270, 5271, 2178];
|
||||
|
||||
const PREDEFINED_TEMPLATES_BY_NODE = {
|
||||
gmail: [5678, 4722, 5694],
|
||||
googleSheets: [5694, 5690, 5906],
|
||||
telegram: [5626, 2114, 4875],
|
||||
openAi: [2462, 2722, 2178],
|
||||
googleGemini: [5993, 6270, 5677],
|
||||
googleCalendar: [2328, 3393, 2110],
|
||||
youTube: [3188, 4846, 4506],
|
||||
airtable: [3053, 2700, 2579],
|
||||
};
|
||||
|
||||
function getPredefinedFromSelected(selectedApps: string[]) {
|
||||
const predefinedNodes = Object.keys(PREDEFINED_TEMPLATES_BY_NODE);
|
||||
const predefinedSelected = predefinedNodes.filter((node) => selectedApps.includes(node));
|
||||
|
||||
return predefinedSelected.reduce<number[]>(
|
||||
(acc, app) => [
|
||||
...acc,
|
||||
...PREDEFINED_TEMPLATES_BY_NODE[app as keyof typeof PREDEFINED_TEMPLATES_BY_NODE],
|
||||
],
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
function getSuggestedTemplatesForLowCodingSkill(selectedApps: string[]) {
|
||||
if (selectedApps.length === 0) {
|
||||
return SIMPLE_TEMPLATES;
|
||||
}
|
||||
|
||||
const predefinedSelected = getPredefinedFromSelected(selectedApps);
|
||||
if (predefinedSelected.length > 0) {
|
||||
return predefinedSelected;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function keepTop3Templates(templates: ITemplatesWorkflowFull[]) {
|
||||
if (templates.length <= 3) {
|
||||
return templates;
|
||||
}
|
||||
|
||||
return Array.from(new Map(templates.map((t) => [t.id, t])).values())
|
||||
.sort((a, b) => b.totalViews - a.totalViews)
|
||||
.slice(0, 3);
|
||||
}
|
||||
|
||||
export const usePersonalizedTemplatesStore = defineStore(STORES.PERSONALIZED_TEMPLATES, () => {
|
||||
const telemetry = useTelemetry();
|
||||
const posthogStore = usePostHog();
|
||||
const cloudPlanStore = useCloudPlanStore();
|
||||
const templatesStore = useTemplatesStore();
|
||||
|
||||
const allSuggestedWorkflows = ref<ITemplatesWorkflowFull[]>([]);
|
||||
const dismissedSuggestedWorkflowsStorage = useStorage(
|
||||
LOCAL_STORAGE_EXPERIMENTAL_DISMISSED_SUGGESTED_WORKFLOWS,
|
||||
);
|
||||
const dismissedSuggestedWorkflows = computed((): number[] => {
|
||||
return dismissedSuggestedWorkflowsStorage.value
|
||||
? jsonParse(dismissedSuggestedWorkflowsStorage.value, { fallbackValue: [] })
|
||||
: [];
|
||||
});
|
||||
const suggestedWorkflows = computed(() =>
|
||||
allSuggestedWorkflows.value.filter(({ id }) => !dismissedSuggestedWorkflows.value.includes(id)),
|
||||
);
|
||||
const dismissSuggestedWorkflow = (id: number) => {
|
||||
dismissedSuggestedWorkflowsStorage.value = JSON.stringify([
|
||||
...(dismissedSuggestedWorkflows.value ?? []),
|
||||
id,
|
||||
]);
|
||||
};
|
||||
|
||||
const isFeatureEnabled = () => {
|
||||
return (
|
||||
posthogStore.getVariant(TEMPLATE_ONBOARDING_EXPERIMENT.name) ===
|
||||
TEMPLATE_ONBOARDING_EXPERIMENT.variantSuggestedTemplates && cloudPlanStore.userIsTrialing
|
||||
);
|
||||
};
|
||||
|
||||
const trackUserWasRecommendedTemplates = (templateIds: number[]) => {
|
||||
telemetry.track('User was recommended personalized templates', {
|
||||
templateIds,
|
||||
});
|
||||
};
|
||||
|
||||
const trackUserClickedOnPersonalizedTemplate = (templateId: number) => {
|
||||
telemetry.track('User clicked on personalized template callout', {
|
||||
templateId,
|
||||
});
|
||||
};
|
||||
|
||||
const trackUserDismissedCallout = (templateId: number) => {
|
||||
telemetry.track('User dismissed personalized template callout', {
|
||||
templateId,
|
||||
});
|
||||
};
|
||||
|
||||
const fetchSuggestedWorkflows = async (codingSkill: number, selectedApps: string[]) => {
|
||||
if (!isFeatureEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (codingSkill === 1) {
|
||||
const predefinedSelected = getSuggestedTemplatesForLowCodingSkill(selectedApps);
|
||||
|
||||
if (predefinedSelected.length > 0) {
|
||||
const suggestedWorkflowsPromises = predefinedSelected.map(
|
||||
async (id) => await templatesStore.fetchTemplateById(id.toString()),
|
||||
);
|
||||
|
||||
const allWorkflows = await Promise.all(suggestedWorkflowsPromises);
|
||||
const top3Templates = keepTop3Templates(allWorkflows);
|
||||
allSuggestedWorkflows.value = top3Templates;
|
||||
trackUserWasRecommendedTemplates(top3Templates.map((t) => t.id));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const topWorkflowsByApp = await templatesStore.getWorkflows({
|
||||
categories: [],
|
||||
search: '',
|
||||
sort: 'rank:desc',
|
||||
apps: selectedApps.length > 0 ? selectedApps : undefined,
|
||||
combineWith: 'or',
|
||||
});
|
||||
|
||||
const topWorkflowsIds = topWorkflowsByApp.slice(0, 3).map((workflow) => workflow.id);
|
||||
const suggestedWorkflowsPromises = topWorkflowsIds.map(
|
||||
async (id) => await templatesStore.fetchTemplateById(id.toString()),
|
||||
);
|
||||
|
||||
const allWorkflows = await Promise.all(suggestedWorkflowsPromises);
|
||||
const top3Templates = keepTop3Templates(allWorkflows);
|
||||
allSuggestedWorkflows.value = top3Templates;
|
||||
trackUserWasRecommendedTemplates(top3Templates.map((t) => t.id));
|
||||
} catch (error) {
|
||||
// Let it fail silently
|
||||
}
|
||||
};
|
||||
|
||||
const getTemplateRoute = (id: number) => {
|
||||
return { name: VIEWS.TEMPLATE, params: { id } };
|
||||
};
|
||||
|
||||
watch(
|
||||
() => cloudPlanStore.currentUserCloudInfo,
|
||||
async (userInfo) => {
|
||||
if (!userInfo) return;
|
||||
|
||||
const codingSkill = cloudPlanStore.codingSkill;
|
||||
const selectedApps = cloudPlanStore.selectedApps ?? [];
|
||||
|
||||
await fetchSuggestedWorkflows(codingSkill, selectedApps);
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
isFeatureEnabled,
|
||||
suggestedWorkflows,
|
||||
dismissSuggestedWorkflow,
|
||||
trackUserClickedOnPersonalizedTemplate,
|
||||
trackUserDismissedCallout,
|
||||
getTemplateRoute,
|
||||
};
|
||||
});
|
||||
@@ -41,9 +41,7 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
|
||||
|
||||
const currentUsageData = computed(() => state.usage);
|
||||
|
||||
const selectedApps = computed(
|
||||
() => currentUserCloudInfo.value?.selectedApps?.split(',').filter(Boolean) ?? [],
|
||||
);
|
||||
const selectedApps = computed(() => currentUserCloudInfo.value?.selectedApps);
|
||||
const codingSkill = computed(() => {
|
||||
const information = currentUserCloudInfo.value?.information;
|
||||
if (!information) {
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
import { useStorage } from '@/composables/useStorage';
|
||||
import {
|
||||
LOCAL_STORAGE_EXPERIMENTAL_DISMISSED_SUGGESTED_WORKFLOWS,
|
||||
TEMPLATES_URLS,
|
||||
} from '@/constants';
|
||||
import { TEMPLATES_URLS } from '@/constants';
|
||||
import type { INodeUi } from '@/Interface';
|
||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||
import {
|
||||
getSuggestedTemplatesForLowCodingSkill,
|
||||
getTemplatePathByRole,
|
||||
getTop3Templates,
|
||||
isPersonalizedTemplatesExperimentEnabled,
|
||||
} from '@/utils/experiments';
|
||||
import { getTemplatePathByRole } from '@/utils/experiments';
|
||||
import { getNodesWithNormalizedPosition } from '@/utils/nodeViewUtils';
|
||||
import type {
|
||||
ITemplatesCategory,
|
||||
@@ -24,7 +15,6 @@ import type {
|
||||
import * as templatesApi from '@n8n/rest-api-client/api/templates';
|
||||
import { STORES } from '@n8n/stores';
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useSettingsStore } from './settings.store';
|
||||
@@ -90,27 +80,6 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, () => {
|
||||
`${window.location.protocol}//${window.location.host}${window.BASE_PATH}`,
|
||||
);
|
||||
|
||||
const experimentalAllSuggestedWorkflows = ref<ITemplatesWorkflowFull[]>([]);
|
||||
const experimentalDismissedSuggestedWorkflowsStorage = useStorage(
|
||||
LOCAL_STORAGE_EXPERIMENTAL_DISMISSED_SUGGESTED_WORKFLOWS,
|
||||
);
|
||||
const experimentalDismissedSuggestedWorkflows = computed((): number[] => {
|
||||
return experimentalDismissedSuggestedWorkflowsStorage.value
|
||||
? jsonParse(experimentalDismissedSuggestedWorkflowsStorage.value, { fallbackValue: [] })
|
||||
: [];
|
||||
});
|
||||
const experimentalSuggestedWorkflows = computed(() =>
|
||||
experimentalAllSuggestedWorkflows.value.filter(
|
||||
({ id }) => !experimentalDismissedSuggestedWorkflows.value.includes(id),
|
||||
),
|
||||
);
|
||||
const experimentalDismissSuggestedWorkflow = (id: number) => {
|
||||
experimentalDismissedSuggestedWorkflowsStorage.value = JSON.stringify([
|
||||
...(experimentalDismissedSuggestedWorkflows.value ?? []),
|
||||
id,
|
||||
]);
|
||||
};
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
const rootStore = useRootStore();
|
||||
const userStore = useUsersStore();
|
||||
@@ -226,9 +195,6 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, () => {
|
||||
`${TEMPLATES_URLS.BASE_WEBSITE_URL}${getTemplatePathByRole(userRole.value)}?${websiteTemplateRepositoryParameters.value.toString()}`,
|
||||
);
|
||||
|
||||
const websiteTemplateURLById = (path: string) =>
|
||||
`${TEMPLATES_URLS.BASE_WEBSITE_URL}${path}${getTemplatePathByRole(userRole.value)}?${websiteTemplateRepositoryParameters.value.toString()}`;
|
||||
|
||||
const constructTemplateRepositoryURL = (params: URLSearchParams): string => {
|
||||
return `${TEMPLATES_URLS.BASE_WEBSITE_URL}?${params.toString()}`;
|
||||
};
|
||||
@@ -474,49 +440,6 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, () => {
|
||||
return template;
|
||||
};
|
||||
|
||||
const experimentalFetchSuggestedWorkflows = async () => {
|
||||
if (!isPersonalizedTemplatesExperimentEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const codingSkill = cloudPlanStore.codingSkill;
|
||||
const selectedApps = cloudPlanStore.selectedApps;
|
||||
|
||||
if (codingSkill === 1) {
|
||||
const predefinedSelected = getSuggestedTemplatesForLowCodingSkill(selectedApps);
|
||||
|
||||
if (predefinedSelected.length > 0) {
|
||||
const suggestedWorkflowsPromises = predefinedSelected.map(
|
||||
async (id) => await fetchTemplateById(id.toString()),
|
||||
);
|
||||
|
||||
const suggestedWorkflows = await Promise.all(suggestedWorkflowsPromises);
|
||||
experimentalAllSuggestedWorkflows.value = getTop3Templates(suggestedWorkflows);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const topWorkflowsByApp = await getWorkflows({
|
||||
categories: [],
|
||||
search: '',
|
||||
sort: 'rank:desc',
|
||||
apps: selectedApps.length > 0 ? selectedApps : undefined,
|
||||
combineWith: 'or',
|
||||
});
|
||||
|
||||
const topWorkflowsIds = topWorkflowsByApp.slice(0, 3).map((workflow) => workflow.id);
|
||||
const suggestedWorkflowsPromises = topWorkflowsIds.map(
|
||||
async (id) => await fetchTemplateById(id.toString()),
|
||||
);
|
||||
|
||||
const suggestedWorkflows = await Promise.all(suggestedWorkflowsPromises);
|
||||
experimentalAllSuggestedWorkflows.value = getTop3Templates(suggestedWorkflows);
|
||||
} catch (error) {
|
||||
// Let it fail silently
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
categories,
|
||||
collections,
|
||||
@@ -557,9 +480,5 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, () => {
|
||||
getMoreWorkflows,
|
||||
getWorkflowTemplate,
|
||||
getFixedWorkflowTemplate,
|
||||
websiteTemplateURLById,
|
||||
experimentalFetchSuggestedWorkflows,
|
||||
experimentalSuggestedWorkflows,
|
||||
experimentalDismissSuggestedWorkflow,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { EXTRA_TEMPLATE_LINKS_EXPERIMENT, TEMPLATE_ONBOARDING_EXPERIMENT } from '@/constants';
|
||||
import { EXTRA_TEMPLATE_LINKS_EXPERIMENT } from '@/constants';
|
||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import type { ITemplatesWorkflowFull } from '@n8n/rest-api-client';
|
||||
|
||||
/*
|
||||
* Extra template links
|
||||
@@ -60,67 +59,3 @@ export const trackTemplatesClick = (source: TemplateClickSource) => {
|
||||
source,
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Personalized templates
|
||||
*/
|
||||
|
||||
export const isPersonalizedTemplatesExperimentEnabled = () => {
|
||||
return (
|
||||
usePostHog().getVariant(TEMPLATE_ONBOARDING_EXPERIMENT.name) ===
|
||||
TEMPLATE_ONBOARDING_EXPERIMENT.variantSuggestedTemplates && useCloudPlanStore().userIsTrialing
|
||||
);
|
||||
};
|
||||
|
||||
export const getUserSkillCount = () => {
|
||||
return 1;
|
||||
};
|
||||
|
||||
const SIMPLE_TEMPLATES = [6035, 1960, 2178];
|
||||
|
||||
const PREDEFINED_TEMPLATES_BY_NODE = {
|
||||
googleSheets: [5694, 5690, 5906],
|
||||
gmail: [5678, 4722, 5694],
|
||||
telegram: [5626, 5784, 4875],
|
||||
openAi: [2462, 2722, 2178],
|
||||
googleGemini: [5993, 6035, 5677],
|
||||
googleCalendar: [2328, 3393, 3657],
|
||||
youTube: [3188, 4846, 4506],
|
||||
airtable: [3053, 2700, 2579],
|
||||
};
|
||||
|
||||
export function getPredefinedFromSelected(selectedApps: string[]) {
|
||||
const predefinedNodes = Object.keys(PREDEFINED_TEMPLATES_BY_NODE);
|
||||
const predefinedSelected = predefinedNodes.filter((node) => selectedApps.includes(node));
|
||||
|
||||
return predefinedSelected.reduce<number[]>(
|
||||
(acc, app) => [
|
||||
...acc,
|
||||
...PREDEFINED_TEMPLATES_BY_NODE[app as keyof typeof PREDEFINED_TEMPLATES_BY_NODE],
|
||||
],
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
export function getSuggestedTemplatesForLowCodingSkill(selectedApps: string[]) {
|
||||
if (selectedApps.length === 0) {
|
||||
return SIMPLE_TEMPLATES;
|
||||
}
|
||||
|
||||
const predefinedSelected = getPredefinedFromSelected(selectedApps);
|
||||
if (predefinedSelected.length > 0) {
|
||||
return predefinedSelected;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export function getTop3Templates(templates: ITemplatesWorkflowFull[]) {
|
||||
if (templates.length <= 3) {
|
||||
return templates;
|
||||
}
|
||||
|
||||
return Array.from(new Map(templates.map((t) => [t.id, t])).values())
|
||||
.sort((a, b) => a.totalViews - b.totalViews)
|
||||
.slice(0, 3);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ import {
|
||||
MODAL_CONFIRM,
|
||||
VIEWS,
|
||||
} from '@/constants';
|
||||
import { useAITemplatesStarterCollectionStore } from '@/experiments/aiTemplatesStarterCollection/stores/aiTemplatesStarterCollection.store';
|
||||
import SuggestedWorkflowCard from '@/experiments/personalizedTemplates/components/SuggestedWorkflowCard.vue';
|
||||
import SuggestedWorkflows from '@/experiments/personalizedTemplates/components/SuggestedWorkflows.vue';
|
||||
import { usePersonalizedTemplatesStore } from '@/experiments/personalizedTemplates/stores/personalizedTemplates.store';
|
||||
import InsightsSummary from '@/features/insights/components/InsightsSummary.vue';
|
||||
import { useInsightsStore } from '@/features/insights/insights.store';
|
||||
import type {
|
||||
@@ -47,7 +51,6 @@ import { type Project, type ProjectSharingData, ProjectTypes } from '@/types/pro
|
||||
import { getEasyAiWorkflowJson } from '@/utils/easyAiWorkflowUtils';
|
||||
import {
|
||||
isExtraTemplateLinksExperimentEnabled,
|
||||
isPersonalizedTemplatesExperimentEnabled,
|
||||
TemplateClickSource,
|
||||
trackTemplatesClick,
|
||||
} from '@/utils/experiments';
|
||||
@@ -70,7 +73,6 @@ import debounce from 'lodash/debounce';
|
||||
import { type IUser, PROJECT_ROOT } from 'n8n-workflow';
|
||||
import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';
|
||||
import { type LocationQueryRaw, useRoute, useRouter } from 'vue-router';
|
||||
import { useAITemplatesStarterCollectionStore } from '@/experiments/aiTemplatesStarterCollection/stores/aiTemplatesStarterCollection.store';
|
||||
|
||||
const SEARCH_DEBOUNCE_TIME = 300;
|
||||
const FILTERS_DEBOUNCE_TIME = 100;
|
||||
@@ -115,6 +117,7 @@ const usageStore = useUsageStore();
|
||||
const insightsStore = useInsightsStore();
|
||||
const templatesStore = useTemplatesStore();
|
||||
const aiStarterTemplatesStore = useAITemplatesStarterCollectionStore();
|
||||
const personalizedTemplatesStore = usePersonalizedTemplatesStore();
|
||||
|
||||
const documentTitle = useDocumentTitle();
|
||||
const { callDebounced } = useDebounce();
|
||||
@@ -372,10 +375,6 @@ const showRegisteredCommunityCTA = computed(
|
||||
() => isSelfHostedDeployment.value && !foldersEnabled.value && canUserRegisterCommunityPlus.value,
|
||||
);
|
||||
|
||||
const experimentalShowSuggestedWorkflows = computed(() =>
|
||||
isPersonalizedTemplatesExperimentEnabled(),
|
||||
);
|
||||
|
||||
const showAIStarterCollectionCallout = computed(() => {
|
||||
return (
|
||||
!loading.value &&
|
||||
@@ -389,6 +388,10 @@ const showAIStarterCollectionCallout = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
const showPersonalizedTemplates = computed(
|
||||
() => !loading.value && personalizedTemplatesStore.isFeatureEnabled(),
|
||||
);
|
||||
|
||||
/**
|
||||
* WATCHERS, STORE SUBSCRIPTIONS AND EVENT BUS HANDLERS
|
||||
*/
|
||||
@@ -489,7 +492,6 @@ const initialize = async () => {
|
||||
workflowsStore.fetchActiveWorkflows(),
|
||||
usageStore.getLicenseInfo(),
|
||||
foldersStore.fetchTotalWorkflowsAndFoldersCount(route.params.projectId as string | undefined),
|
||||
templatesStore.experimentalFetchSuggestedWorkflows(),
|
||||
]);
|
||||
breadcrumbsLoading.value = false;
|
||||
workflowsAndFolders.value = resourcesPage;
|
||||
@@ -1701,6 +1703,17 @@ const onNameSubmit = async (name: string) => {
|
||||
</div>
|
||||
</template>
|
||||
</N8nCallout>
|
||||
<SuggestedWorkflows v-else-if="showPersonalizedTemplates">
|
||||
<SuggestedWorkflowCard
|
||||
v-for="workflow in personalizedTemplatesStore.suggestedWorkflows"
|
||||
:key="workflow.id"
|
||||
data-test-id="resource-list-item-suggested-workflow"
|
||||
:data="{
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
}"
|
||||
/>
|
||||
</SuggestedWorkflows>
|
||||
<N8nCallout
|
||||
v-else-if="!loading && showEasyAIWorkflowCallout && easyAICalloutVisible"
|
||||
theme="secondary"
|
||||
@@ -1728,17 +1741,6 @@ const onNameSubmit = async (name: string) => {
|
||||
</div>
|
||||
</template>
|
||||
</N8nCallout>
|
||||
<SuggestedWorkflows v-if="experimentalShowSuggestedWorkflows">
|
||||
<SuggestedWorkflowCard
|
||||
v-for="workflow in templatesStore.experimentalSuggestedWorkflows"
|
||||
:key="workflow.id"
|
||||
data-test-id="resource-list-item-suggested-workflow"
|
||||
:data="{
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
}"
|
||||
/>
|
||||
</SuggestedWorkflows>
|
||||
</template>
|
||||
<template #breadcrumbs>
|
||||
<div v-if="breadcrumbsLoading" :class="$style['breadcrumbs-loading']">
|
||||
|
||||
Reference in New Issue
Block a user