feat(editor): Add new ways to discover templates (#17183)

This commit is contained in:
Romeo Balta
2025-07-11 15:24:32 +01:00
committed by GitHub
parent 4eb18b7dc7
commit 0259c58cb8
11 changed files with 511 additions and 54 deletions

View File

@@ -1,17 +1,6 @@
<script lang="ts" setup>
import Draggable from '@/components/Draggable.vue';
import { FOLDER_LIST_ITEM_ACTIONS } from '@/components/Folders/constants';
import type {
BaseFilters,
FolderResource,
Resource,
SortingAndPaginationUpdates,
WorkflowResource,
FolderListItem,
UserAction,
WorkflowListItem,
WorkflowListResource,
} from '@/Interface';
import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue';
import ProjectHeader from '@/components/Projects/ProjectHeader.vue';
import WorkflowCard from '@/components/WorkflowCard.vue';
@@ -20,7 +9,6 @@ import { useDebounce } from '@/composables/useDebounce';
import { useDocumentTitle } from '@/composables/useDocumentTitle';
import type { DragTarget, DropTarget } from '@/composables/useFolders';
import { useFolders } from '@/composables/useFolders';
import { useI18n } from '@n8n/i18n';
import { useMessage } from '@/composables/useMessage';
import { useProjectPages } from '@/composables/useProjectPages';
import { useTelemetry } from '@/composables/useTelemetry';
@@ -32,14 +20,30 @@ import {
MODAL_CONFIRM,
VIEWS,
} from '@/constants';
import {
isExtraTemplateLinksExperimentEnabled,
TemplateClickSource,
trackTemplatesClick,
} from '@/utils/experiments';
import InsightsSummary from '@/features/insights/components/InsightsSummary.vue';
import { useInsightsStore } from '@/features/insights/insights.store';
import { getResourcePermissions } from '@n8n/permissions';
import type {
BaseFilters,
FolderListItem,
FolderResource,
Resource,
SortingAndPaginationUpdates,
UserAction,
WorkflowListItem,
WorkflowListResource,
WorkflowResource,
} from '@/Interface';
import { useFoldersStore } from '@/stores/folders.store';
import { useProjectsStore } from '@/stores/projects.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useTagsStore } from '@/stores/tags.store';
import { useTemplatesStore } from '@/stores/templates.store';
import { useUIStore } from '@/stores/ui.store';
import { useUsageStore } from '@/stores/usage.store';
import { useUsersStore } from '@/stores/users.store';
@@ -47,6 +51,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { type Project, type ProjectSharingData, ProjectTypes } from '@/types/projects.types';
import { getEasyAiWorkflowJson } from '@/utils/easyAiWorkflowUtils';
import {
N8nButton,
N8nCard,
N8nHeading,
N8nIcon,
@@ -55,13 +60,14 @@ import {
N8nOption,
N8nSelect,
N8nText,
N8nButton,
} from '@n8n/design-system';
import type { PathItem } from '@n8n/design-system/components/N8nBreadcrumbs/Breadcrumbs.vue';
import { useI18n } from '@n8n/i18n';
import { getResourcePermissions } from '@n8n/permissions';
import { createEventBus } from '@n8n/utils/event-bus';
import debounce from 'lodash/debounce';
import { type IUser, PROJECT_ROOT } from 'n8n-workflow';
import { useTemplateRef, computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';
import { type LocationQueryRaw, useRoute, useRouter } from 'vue-router';
const SEARCH_DEBOUNCE_TIME = 300;
@@ -105,6 +111,7 @@ const tagsStore = useTagsStore();
const foldersStore = useFoldersStore();
const usageStore = useUsageStore();
const insightsStore = useInsightsStore();
const templatesStore = useTemplatesStore();
const documentTitle = useDocumentTitle();
const { callDebounced } = useDebounce();
@@ -323,6 +330,10 @@ const showEasyAIWorkflowCallout = computed(() => {
return !easyAIWorkflowOnboardingDone;
});
const templatesCardEnabled = computed(() => {
return isExtraTemplateLinksExperimentEnabled() && settingsStore.isTemplatesEnabled;
});
const projectPermissions = computed(() => {
return getResourcePermissions(
projectsStore.currentProject?.scopes ?? personalProject.value?.scopes,
@@ -746,6 +757,17 @@ const addWorkflow = () => {
trackEmptyCardClick('blank');
};
const openTemplatesRepository = async () => {
trackTemplatesClick(TemplateClickSource.emptyInstanceCard);
if (templatesStore.hasCustomTemplatesHost) {
await router.push({ name: VIEWS.TEMPLATES });
return;
}
window.open(templatesStore.websiteTemplateRepositoryURL, '_blank');
};
const trackEmptyCardClick = (option: 'blank' | 'templates' | 'courses') => {
telemetry.track('User clicked empty page option', {
option,
@@ -1800,6 +1822,25 @@ 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>
</template>