feat(editor): Add templates quality experiment inline section on empty project layout (no-changelog) (#22078)

This commit is contained in:
Svetoslav Dekov 2025-11-20 17:33:01 +02:00 committed by GitHub
parent 77b7c2dd1a
commit 2ba34dbb74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 22 additions and 78 deletions

View File

@ -13,7 +13,6 @@ import { useTagsStore } from '@/features/shared/tags/tags.store';
import { useUsersStore } from '@/features/settings/users/users.store';
import { useWorkflowsStore } from '@/app/stores/workflows.store';
import type { Project } from '@/features/collaboration/projects/projects.types';
import { TemplateClickSource } from '@/experiments/utils';
import WorkflowsView from '@/app/views/WorkflowsView.vue';
import { STORES } from '@n8n/stores';
import { createTestingPinia } from '@pinia/testing';
@ -172,36 +171,6 @@ describe('WorkflowsView', () => {
expect(router.currentRoute.value.name).toBe(VIEWS.NEW_WORKFLOW);
});
it('should show template card', async () => {
const projectsStore = mockedStore(useProjectsStore);
projectsStore.currentProject = { scopes: ['workflow:create'] } as Project;
settingsStore.settings.templates = { enabled: true, host: 'http://example.com' };
const { getByTestId } = renderComponent({ pinia });
await waitAllPromises();
expect(getByTestId('new-workflow-from-template-card')).toBeInTheDocument();
});
it('should track template card click', async () => {
const projectsStore = mockedStore(useProjectsStore);
projectsStore.currentProject = { scopes: ['workflow:create'] } as Project;
settingsStore.settings.templates = { enabled: true, host: 'http://example.com' };
const { getByTestId } = renderComponent({ pinia });
await waitAllPromises();
const card = getByTestId('new-workflow-from-template-card');
await userEvent.click(card);
expect(mockTrack).toHaveBeenCalledWith(
'User clicked on templates',
expect.objectContaining({
source: TemplateClickSource.emptyInstanceCard,
}),
);
});
});
describe('fetch workflow options', () => {

View File

@ -21,7 +21,6 @@ import { useCalloutHelpers } from '@/app/composables/useCalloutHelpers';
import {
DEFAULT_WORKFLOW_PAGE_SIZE,
EnterpriseEditionFeature,
EXPERIMENT_TEMPLATES_DATA_QUALITY_KEY,
MODAL_CONFIRM,
VIEWS,
} from '@/app/constants';
@ -33,6 +32,7 @@ import { usePersonalizedTemplatesStore } from '@/experiments/personalizedTemplat
import { useReadyToRunWorkflowsStore } from '@/experiments/readyToRunWorkflows/stores/readyToRunWorkflows.store';
import TemplateRecommendationV2 from '@/experiments/templateRecoV2/components/TemplateRecommendationV2.vue';
import TemplateRecommendationV3 from '@/experiments/personalizedTemplatesV3/components/TemplateRecommendationV3.vue';
import TemplatesDataQualityInlineSection from '@/experiments/templatesDataQuality/components/TemplatesDataQualityInlineSection.vue';
import { usePersonalizedTemplatesV2Store } from '@/experiments/templateRecoV2/stores/templateRecoV2.store';
import { usePersonalizedTemplatesV3Store } from '@/experiments/personalizedTemplatesV3/stores/personalizedTemplatesV3.store';
import EmptyStateLayout from '@/app/components/layouts/EmptyStateLayout.vue';
@ -55,7 +55,6 @@ import { useProjectsStore } from '@/features/collaboration/projects/projects.sto
import { useSettingsStore } from '@/app/stores/settings.store';
import { useSourceControlStore } from '@/features/integrations/sourceControl.ee/sourceControl.store';
import { useTagsStore } from '@/features/shared/tags/tags.store';
import { useTemplatesStore } from '@/features/workflows/templates/templates.store';
import { useUIStore } from '@/app/stores/ui.store';
import { useUsageStore } from '@/features/settings/usage/usage.store';
import { useUsersStore } from '@/features/settings/users/users.store';
@ -66,11 +65,6 @@ import {
ProjectTypes,
} from '@/features/collaboration/projects/projects.types';
import { getEasyAiWorkflowJson } from '@/features/workflows/templates/utils/workflowSamples';
import {
isExtraTemplateLinksExperimentEnabled,
TemplateClickSource,
trackTemplatesClick,
} from '@/experiments/utils';
import type { PathItem } from '@n8n/design-system/components/N8nBreadcrumbs/Breadcrumbs.vue';
import { useI18n } from '@n8n/i18n';
import { getResourcePermissions } from '@n8n/permissions';
@ -140,7 +134,6 @@ const tagsStore = useTagsStore();
const foldersStore = useFoldersStore();
const usageStore = useUsageStore();
const insightsStore = useInsightsStore();
const templatesStore = useTemplatesStore();
const aiStarterTemplatesStore = useAITemplatesStarterCollectionStore();
const personalizedTemplatesStore = usePersonalizedTemplatesStore();
const readyToRunWorkflowsStore = useReadyToRunWorkflowsStore();
@ -390,16 +383,20 @@ const showEasyAIWorkflowCallout = computed(() => {
return !easyAIWorkflowOnboardingDone;
});
const templatesCardEnabled = computed(() => {
return isExtraTemplateLinksExperimentEnabled() && settingsStore.isTemplatesEnabled;
});
const projectPermissions = computed(() => {
return getResourcePermissions(
projectsStore.currentProject?.scopes ?? personalProject.value?.scopes,
);
});
const showTemplatesDataQualityInline = computed(() => {
return (
templatesDataQualityStore.isFeatureEnabled() &&
!readOnlyEnv.value &&
projectPermissions.value.workflow.create
);
});
const showReadyToRunWorkflowsCallout = computed(() => {
const isEnabled = readyToRunWorkflowsStore.isFeatureEnabled;
const isDismissed = readyToRunWorkflowsStore.isCalloutDismissed;
@ -921,22 +918,6 @@ const addWorkflow = () => {
trackEmptyCardClick('blank');
};
const openTemplatesRepository = async () => {
trackTemplatesClick(TemplateClickSource.emptyInstanceCard);
if (templatesStore.hasCustomTemplatesHost) {
await router.push({ name: VIEWS.TEMPLATES });
return;
}
if (templatesDataQualityStore.isFeatureEnabled()) {
uiStore.openModal(EXPERIMENT_TEMPLATES_DATA_QUALITY_KEY);
return;
}
window.open(templatesStore.websiteTemplateRepositoryURL, '_blank');
};
const trackEmptyCardClick = (option: 'blank' | 'templates' | 'courses') => {
telemetry.track('User clicked empty page option', {
option,
@ -2247,25 +2228,9 @@ const onNameSubmit = async (name: string) => {
</N8nText>
</div>
</N8nCard>
<N8nCard
v-if="templatesCardEnabled"
:class="$style.emptyStateCard"
hoverable
data-test-id="new-workflow-from-template-card"
@click="openTemplatesRepository"
>
<div :class="$style.emptyStateCardContent">
<N8nIcon
:class="$style.emptyStateCardIcon"
:stroke-width="1.5"
icon="package-open"
color="foreground-dark"
/>
<N8nText size="large" class="mt-xs pl-2xs pr-2xs">
{{ i18n.baseText('workflows.empty.startWithTemplate') }}
</N8nText>
</div>
</N8nCard>
</div>
<div v-if="showTemplatesDataQualityInline" :class="$style.templatesContainer">
<TemplatesDataQualityInlineSection />
</div>
<TemplateRecommendationV3 v-if="showTemplateRecommendationV3" />
<TemplateRecommendationV2 v-else-if="showTemplateRecommendationV2" />
@ -2369,6 +2334,16 @@ const onNameSubmit = async (name: string) => {
justify-content: center;
}
.templatesContainer {
display: flex;
justify-content: center;
width: 100%;
> section {
margin-top: var(--spacing--3xl);
}
}
.easy-ai-workflow-callout {
// Make the callout padding in line with workflow cards
margin-top: var(--spacing--xs);