refactor(editor): Move setup templates to templates feature (no-changelog) (#20362)

This commit is contained in:
Alex Grozav 2025-10-03 17:22:43 +03:00 committed by GitHub
parent 5a75f82659
commit 0602018d9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 123 additions and 118 deletions

View File

@ -77,7 +77,7 @@ import PersonalizationModal from '@/components/PersonalizationModal.vue';
import PreBuiltAgentsModal from '@/components/PreBuiltAgentsModal.vue';
import ProjectMoveResourceModal from '@/components/Projects/ProjectMoveResourceModal.vue';
import EventDestinationSettingsModal from '@/components/SettingsLogStreaming/EventDestinationSettingsModal.ee.vue';
import SetupWorkflowCredentialsModal from '@/components/SetupWorkflowCredentialsModal/SetupWorkflowCredentialsModal.vue';
import SetupWorkflowCredentialsModal from '@/features/templates/components/SetupWorkflowCredentialsModal.vue';
import SourceControlPullModal from '@/components/SourceControlPullModal.ee.vue';
import SourceControlPushModal from '@/components/SourceControlPushModal.ee.vue';
import AnnotationTagsManager from '@/components/TagsManager/AnnotationTagsManager.ee.vue';

View File

@ -40,7 +40,10 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
import type { NodeIconSource } from '../../../utils/nodeIcon';
import type { CommunityNodeDetails, ViewStack } from './composables/useViewStacks';
import { PrebuiltAgentTemplates, SampleTemplates } from '@/utils/templates/workflowSamples';
import {
PrebuiltAgentTemplates,
SampleTemplates,
} from '@/features/templates/utils/workflowSamples';
const COMMUNITY_NODE_TYPE_PREVIEW_TOKEN = '-preview';

View File

@ -1,7 +1,10 @@
import { useCalloutHelpers } from '@/composables/useCalloutHelpers';
import { updateCurrentUserSettings } from '@n8n/rest-api-client/api/users';
import { createTestingPinia } from '@pinia/testing';
import { PrebuiltAgentTemplates, SampleTemplates } from '@/utils/templates/workflowSamples';
import {
PrebuiltAgentTemplates,
SampleTemplates,
} from '@/features/templates/utils/workflowSamples';
import { useNDVStore } from '@/stores/ndv.store';
import { mockedStore } from '@/__tests__/utils';
import { NODE_CREATOR_OPEN_SOURCES, VIEWS } from '@/constants';

View File

@ -26,7 +26,7 @@ import {
isPrebuiltAgentTemplateId,
isTutorialTemplateId,
SampleTemplates,
} from '@/utils/templates/workflowSamples';
} from '@/features/templates/utils/workflowSamples';
import type { INodeCreateElement, OpenTemplateElement } from '@/Interface';
import { useUIStore } from '@/stores/ui.store';
import { useProjectsStore } from '@/stores/projects.store';

View File

@ -19,7 +19,7 @@ import {
SampleTemplates,
isPrebuiltAgentTemplateId,
isTutorialTemplateId,
} from '@/utils/templates/workflowSamples';
} from '@/features/templates/utils/workflowSamples';
import {
clearPopupWindowState,
getExecutionErrorMessage,

View File

@ -2,10 +2,7 @@
import { computed } from 'vue';
import { formatList } from '@/utils/formatters/listFormatter';
import { useI18n } from '@n8n/i18n';
import type {
AppCredentials,
BaseNode,
} from '@/views/SetupWorkflowFromTemplateView/useCredentialSetupState';
import type { AppCredentials, BaseNode } from '../templates.types';
import { I18nT } from 'vue-i18n';
import { N8nNotice } from '@n8n/design-system';

View File

@ -6,13 +6,10 @@ import IconSuccess from './IconSuccess.vue';
import { getAppNameFromNodeName } from '@/utils/nodeTypesUtils';
import { formatList } from '@/utils/formatters/listFormatter';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import type {
BaseNode,
CredentialUsages,
} from '@/views/SetupWorkflowFromTemplateView/useCredentialSetupState';
import type { BaseNode, CredentialUsages } from '../templates.types';
import { useI18n } from '@n8n/i18n';
import { useTelemetry } from '@/composables/useTelemetry';
import type { TemplateCredentialKey } from '@/utils/templates/templateTransforms';
import type { TemplateCredentialKey } from '../utils/templateTransforms';
import { I18nT } from 'vue-i18n';
import { N8nHeading } from '@n8n/design-system';

View File

@ -1,5 +1,5 @@
import { createComponentRenderer } from '@/__tests__/render';
import SetupWorkflowCredentialsButton from '@/components/SetupWorkflowCredentialsButton/SetupWorkflowCredentialsButton.vue';
import SetupWorkflowCredentialsButton from './SetupWorkflowCredentialsButton.vue';
import { createTestingPinia } from '@pinia/testing';
import { mockedStore } from '@/__tests__/utils';
import { useWorkflowsStore } from '@/stores/workflows.store';

View File

@ -1,9 +1,9 @@
<script lang="ts" setup>
import Modal from '@/components/Modal.vue';
import { useSetupWorkflowCredentialsModalState } from '@/components/SetupWorkflowCredentialsModal/useSetupWorkflowCredentialsModalState';
import { useSetupWorkflowCredentialsModalState } from '../composables/useSetupWorkflowCredentialsModalState';
import { useI18n } from '@n8n/i18n';
import AppsRequiringCredsNotice from '@/views/SetupWorkflowFromTemplateView/AppsRequiringCredsNotice.vue';
import SetupTemplateFormStep from '@/views/SetupWorkflowFromTemplateView/SetupTemplateFormStep.vue';
import AppsRequiringCredsNotice from './AppsRequiringCredsNotice.vue';
import SetupTemplateFormStep from './SetupTemplateFormStep.vue';
import { onMounted, onUnmounted } from 'vue';
import { useTelemetry } from '@/composables/useTelemetry';
import { useWorkflowsStore } from '@/stores/workflows.store';

View File

@ -10,9 +10,9 @@ import type {
ITemplatesWorkflow,
} from '@n8n/rest-api-client/api/templates';
import type { ITag } from '@n8n/rest-api-client/api/tags';
import { useTemplatesStore } from '@/features/templates/templates.store';
import { useTemplatesStore } from '../templates.store';
import TimeAgo from '@/components/TimeAgo.vue';
import { isFullTemplatesCollection, isTemplatesWorkflow } from '@/utils/templates/typeGuards';
import { isFullTemplatesCollection, isTemplatesWorkflow } from '../utils/typeGuards';
import { useRouter } from 'vue-router';
import { useI18n } from '@n8n/i18n';
import { computed } from 'vue';

View File

@ -1,12 +1,12 @@
import { mock } from 'vitest-mock-extended';
import type { IWorkflowTemplateNode } from '@n8n/rest-api-client/api/templates';
import { keyFromCredentialTypeAndName } from '@/utils/templates/templateTransforms';
import type { IWorkflowTemplateNodeWithCredentials } from '@/utils/templates/templateTransforms';
import type { CredentialUsages } from '@/views/SetupWorkflowFromTemplateView/useCredentialSetupState';
import { keyFromCredentialTypeAndName } from '../utils/templateTransforms';
import type { IWorkflowTemplateNodeWithCredentials } from '../utils/templateTransforms';
import type { CredentialUsages } from '../templates.types';
import {
getAppCredentials,
groupNodeCredentialsByKey,
} from '@/views/SetupWorkflowFromTemplateView/useCredentialSetupState';
} from '../composables/useCredentialSetupState';
import { nodeTypeTelegram, nodeTypeTwitter } from '@/utils/testData/nodeTypeTestData';
const objToMap = <TKey extends string, T>(obj: Record<TKey, T>) => {

View File

@ -1,63 +1,24 @@
import type { Ref } from 'vue';
import { computed, ref } from 'vue';
import type { ICredentialsResponse, INodeUi } from '@/Interface';
import type { ICredentialsResponse } from '@/Interface';
import { useCredentialsStore } from '@/stores/credentials.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { getAppNameFromNodeName } from '@/utils/nodeTypesUtils';
import type { TemplateCredentialKey } from '@/utils/templates/templateTransforms';
import type { TemplateCredentialKey } from '../utils/templateTransforms';
import {
keyFromCredentialTypeAndName,
normalizeTemplateNodeCredentials,
} from '@/utils/templates/templateTransforms';
import type { INodeCredentialDescription, INodeCredentialsDetails } from 'n8n-workflow';
} from '../utils/templateTransforms';
import type { INodeCredentialsDetails } from 'n8n-workflow';
import type { NodeTypeProvider } from '@/utils/nodeTypes/nodeTypeTransforms';
import { getNodeTypeDisplayableCredentials } from '@/utils/nodes/nodeTransforms';
import sortBy from 'lodash/sortBy';
//#region Types
export type NodeCredentials = {
[key: string]: string | INodeCredentialsDetails;
};
/**
* Node that can either be in a workflow or in a template workflow. These
* have a bit different shape and this type is used to represent both.
*/
export type BaseNode = Pick<
INodeUi,
'name' | 'parameters' | 'position' | 'type' | 'typeVersion'
> & {
credentials?: NodeCredentials;
};
export type NodeWithCredentials<TNode extends BaseNode> = TNode & {
credentials: NodeCredentials;
};
export type NodeWithRequiredCredential<TNode extends BaseNode> = {
node: TNode;
requiredCredentials: INodeCredentialDescription[];
};
export type CredentialUsages<TNode extends BaseNode = BaseNode> = {
/**
* Key is a combination of the credential name and the credential type name,
* e.g. "twitter-twitterOAuth1Api"
*/
key: TemplateCredentialKey;
credentialName: string;
credentialType: string;
nodeTypeName: string;
usedBy: TNode[];
};
export type AppCredentials<TNode extends BaseNode> = {
appName: string;
credentials: Array<CredentialUsages<TNode>>;
};
//#endregion Types
import type {
AppCredentials,
BaseNode,
CredentialUsages,
NodeWithRequiredCredential,
} from '../templates.types';
//#region Getters

View File

@ -3,8 +3,8 @@ import type { INodeCredentialsDetails } from 'n8n-workflow';
import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useCredentialsStore } from '@/stores/credentials.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import type { TemplateCredentialKey } from '@/utils/templates/templateTransforms';
import { useCredentialSetupState } from '@/views/SetupWorkflowFromTemplateView/useCredentialSetupState';
import type { TemplateCredentialKey } from '../utils/templateTransforms';
import { useCredentialSetupState } from './useCredentialSetupState';
export const useSetupWorkflowCredentialsModalState = () => {
const workflowsStore = useWorkflowsStore();

View File

@ -9,8 +9,8 @@ import type {
IWorkflowTemplateNode,
} from '@n8n/rest-api-client/api/templates';
import { useTemplatesStore } from '@/features/templates/templates.store';
import { keyFromCredentialTypeAndName } from '@/utils/templates/templateTransforms';
import { useSetupTemplateStore } from '@/views/SetupWorkflowFromTemplateView/setupTemplate.store';
import { keyFromCredentialTypeAndName } from './utils/templateTransforms';
import { useSetupTemplateStore } from './setupTemplate.store';
import { useCredentialsStore } from '@/stores/credentials.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';

View File

@ -9,10 +9,10 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import type { INodeTypeDescription } from 'n8n-workflow';
import type { INodeUi } from '@/Interface';
import { VIEWS } from '@/constants';
import { createWorkflowFromTemplate } from '@/utils/templates/templateActions';
import { createWorkflowFromTemplate } from './utils/templateActions';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useTelemetry } from '@/composables/useTelemetry';
import { useCredentialSetupState } from '@/views/SetupWorkflowFromTemplateView/useCredentialSetupState';
import { useCredentialSetupState } from './composables/useCredentialSetupState';
import { tryToParseNumber } from '@/utils/typesUtils';
export type NodeAndType = {

View File

@ -0,0 +1,54 @@
import type { INodeUi } from '@/Interface';
import type { INodeCredentialDescription, INodeCredentialsDetails } from 'n8n-workflow';
import type { TemplateCredentialKey } from './utils/templateTransforms';
export type NodeCredentials = {
[key: string]: string | INodeCredentialsDetails;
};
/**
* Node that can either be in a workflow or in a template workflow. These
* have a bit different shape and this type is used to represent both.
*/
export type BaseNode = Pick<
INodeUi,
'name' | 'parameters' | 'position' | 'type' | 'typeVersion'
> & {
credentials?: NodeCredentials;
};
export type NodeWithCredentials<TNode extends BaseNode> = TNode & {
credentials: NodeCredentials;
};
export type NodeWithRequiredCredential<TNode extends BaseNode> = {
node: TNode;
requiredCredentials: INodeCredentialDescription[];
};
export type CredentialUsages<TNode extends BaseNode = BaseNode> = {
/**
* Key is a combination of the credential name and the credential type name,
* e.g. "twitter-twitterOAuth1Api"
*/
key: TemplateCredentialKey;
credentialName: string;
credentialType: string;
nodeTypeName: string;
usedBy: TNode[];
};
export type AppCredentials<TNode extends BaseNode> = {
appName: string;
credentials: Array<CredentialUsages<TNode>>;
};
/**
* The credentials of a node in a template workflow. Map from credential
* type name to credential name.
* @example
* {
* twitterOAuth1Api: "Twitter credentials"
* }
*/
export type NormalizedTemplateNodeCredentials = Record<string, string>;

View File

@ -11,7 +11,7 @@ import type { NodeTypesStore } from '@/stores/nodeTypes.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import type { TemplatesStore } from '@/features/templates/templates.store';
import { useTemplatesStore } from '@/features/templates/templates.store';
import { useTemplateWorkflow } from '@/utils/templates/templateActions';
import { useTemplateWorkflow } from './templateActions';
import { nodeTypeTelegram } from '@/utils/testData/nodeTypeTestData';
const testTemplate1 = mock<ITemplatesWorkflowFull>({

View File

@ -7,8 +7,8 @@ import type { useRootStore } from '@n8n/stores/useRootStore';
import type { useWorkflowsStore } from '@/stores/workflows.store';
import { getNodesWithNormalizedPosition } from '@/utils/nodeViewUtils';
import type { NodeTypeProvider } from '@/utils/nodeTypes/nodeTypeTransforms';
import type { TemplateCredentialKey } from '@/utils/templates/templateTransforms';
import { replaceAllTemplateNodeCredentials } from '@/utils/templates/templateTransforms';
import type { TemplateCredentialKey } from './templateTransforms';
import { replaceAllTemplateNodeCredentials } from './templateTransforms';
import type { INodeCredentialsDetails } from 'n8n-workflow';
import type { RouteLocationRaw, Router } from 'vue-router';
import type { TemplatesStore } from '@/features/templates/templates.store';

View File

@ -3,7 +3,7 @@ import type { IWorkflowTemplateNode } from '@n8n/rest-api-client/api/templates';
import {
keyFromCredentialTypeAndName,
replaceAllTemplateNodeCredentials,
} from '@/utils/templates/templateTransforms';
} from './templateTransforms';
describe('templateTransforms', () => {
describe('replaceAllTemplateNodeCredentials', () => {

View File

@ -5,7 +5,7 @@ import type {
import type { NodeTypeProvider } from '@/utils/nodeTypes/nodeTypeTransforms';
import { getNodeTypeDisplayableCredentials } from '@/utils/nodes/nodeTransforms';
import type { NormalizedTemplateNodeCredentials } from '@/utils/templates/templateTypes';
import type { NormalizedTemplateNodeCredentials } from '../templates.types';
import type {
INodeCredentialDescription,
INodeCredentials,

View File

@ -1,20 +1,20 @@
import { ApplicationError, type INodeTypeNameVersion } from 'n8n-workflow';
import type { WorkflowDataWithTemplateId } from '@/Interface';
import { isWorkflowDataWithTemplateId } from '@/utils/templates/typeGuards';
import { isWorkflowDataWithTemplateId } from './typeGuards';
/* eslint-disable import-x/extensions */
import easyAiStarterJson from '@/utils/templates/samples/easy_ai_starter.json';
import ragStarterJson from '@/utils/templates/samples/rag_starter.json';
import emailTriageAgentWithGmailJson from '@/utils/templates/samples/agents/email_triage_agent_with_gmail.json';
import jokeAgentWithHttpToolJson from '@/utils/templates/samples/agents/joke_agent_with_http_tool.json';
import knowledgeStoreAgentWithGoogleDriveJson from '@/utils/templates/samples/agents/knowledge_store_agent_with_google_drive.json';
import taskManagementAgentWithGoogleSheetsJson from '@/utils/templates/samples/agents/task_management_agent_with_google_sheets.json';
import voiceAssistantAgentJson from '@/utils/templates/samples/agents/voice-agent.json';
import calendarAgentJson from '@/utils/templates/samples/agents/calendar-agent.json';
import buildYourFirstAiAgentJson from '@/utils/templates/samples/tutorial/build_your_first_ai_agent.json';
import jsonBasicsJson from '@/utils/templates/samples/tutorial/json_basics.json';
import expressionsTutorialJson from '@/utils/templates/samples/tutorial/expressions_tutorial.json';
import workflowLogicJson from '@/utils/templates/samples/tutorial/workflow_logic.json';
import apiFundamentalsJson from '@/utils/templates/samples/tutorial/api_fundamentals.json';
import easyAiStarterJson from './samples/easy_ai_starter.json';
import ragStarterJson from './samples/rag_starter.json';
import emailTriageAgentWithGmailJson from './samples/agents/email_triage_agent_with_gmail.json';
import jokeAgentWithHttpToolJson from './samples/agents/joke_agent_with_http_tool.json';
import knowledgeStoreAgentWithGoogleDriveJson from './samples/agents/knowledge_store_agent_with_google_drive.json';
import taskManagementAgentWithGoogleSheetsJson from './samples/agents/task_management_agent_with_google_sheets.json';
import voiceAssistantAgentJson from './samples/agents/voice-agent.json';
import calendarAgentJson from './samples/agents/calendar-agent.json';
import buildYourFirstAiAgentJson from './samples/tutorial/build_your_first_ai_agent.json';
import jsonBasicsJson from './samples/tutorial/json_basics.json';
import expressionsTutorialJson from './samples/tutorial/expressions_tutorial.json';
import workflowLogicJson from './samples/tutorial/workflow_logic.json';
import apiFundamentalsJson from './samples/tutorial/api_fundamentals.json';
/* eslint-enable import-x/extensions */
const getWorkflowJson = (json: unknown): WorkflowDataWithTemplateId => {

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
import { computed, onMounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useSetupTemplateStore } from './setupTemplate.store';
import AppsRequiringCredsNotice from './AppsRequiringCredsNotice.vue';
import SetupTemplateFormStep from './SetupTemplateFormStep.vue';
import { useSetupTemplateStore } from '../setupTemplate.store';
import AppsRequiringCredsNotice from '../components/AppsRequiringCredsNotice.vue';
import SetupTemplateFormStep from '../components/SetupTemplateFormStep.vue';
import TemplatesView from '@/features/templates/views/TemplatesView.vue';
import { VIEWS } from '@/constants';
import { useI18n } from '@n8n/i18n';

View File

@ -6,10 +6,10 @@ import TemplatesView from './TemplatesView.vue';
import type { ITemplatesWorkflow } from '@n8n/rest-api-client/api/templates';
import { VIEWS } from '@/constants';
import { useTemplatesStore } from '@/features/templates/templates.store';
import { useTemplateWorkflow } from '@/utils/templates/templateActions';
import { useTemplateWorkflow } from '@/features/templates/utils/templateActions';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { isFullTemplatesCollection } from '@/utils/templates/typeGuards';
import { isFullTemplatesCollection } from '@/features/templates/utils/typeGuards';
import { useRoute, useRouter } from 'vue-router';
import { useTelemetry } from '@/composables/useTelemetry';
import { useDocumentTitle } from '@/composables/useDocumentTitle';

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
import { useTemplatesStore } from '@/features/templates/templates.store';
import { useTemplateWorkflow } from '@/utils/templates/templateActions';
import { useTemplateWorkflow } from '@/features/templates/utils/templateActions';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useRoute, useRouter } from 'vue-router';

View File

@ -53,7 +53,7 @@ const TemplatesCollectionView = async () =>
const TemplatesWorkflowView = async () =>
await import('@/features/templates/views/TemplatesWorkflowView.vue');
const SetupWorkflowFromTemplateView = async () =>
await import('@/views/SetupWorkflowFromTemplateView/SetupWorkflowFromTemplateView.vue');
await import('@/features/templates/views/SetupWorkflowFromTemplateView.vue');
const TemplatesSearchView = async () =>
await import('@/features/templates/views/TemplatesSearchView.vue');
const VariablesView = async () => await import('@/views/VariablesView.vue');

View File

@ -1,9 +0,0 @@
/**
* The credentials of a node in a template workflow. Map from credential
* type name to credential name.
* @example
* {
* twitterOAuth1Api: "Twitter credentials"
* }
*/
export type NormalizedTemplateNodeCredentials = Record<string, string>;

View File

@ -124,7 +124,7 @@ import {
shouldIgnoreCanvasShortcut,
} from '@/utils/canvasUtils';
import { isValidNodeConnectionType } from '@/utils/typeGuards';
import { getSampleWorkflowByTemplateId } from '@/utils/templates/workflowSamples';
import { getSampleWorkflowByTemplateId } from '@/features/templates/utils/workflowSamples';
import type { CanvasLayoutEvent } from '@/composables/useCanvasLayout';
import { useWorkflowSaving } from '@/composables/useWorkflowSaving';
import { useBuilderStore } from '@/stores/builder.store';
@ -163,8 +163,7 @@ const LazyNodeDetailsViewV2 = defineAsyncComponent(
);
const LazySetupWorkflowCredentialsButton = defineAsyncComponent(
async () =>
await import('@/components/SetupWorkflowCredentialsButton/SetupWorkflowCredentialsButton.vue'),
async () => await import('@/features/templates/components/SetupWorkflowCredentialsButton.vue'),
);
const $style = useCssModule();

View File

@ -60,7 +60,7 @@ import { useUsageStore } from '@/stores/usage.store';
import { useUsersStore } from '@/stores/users.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { type Project, type ProjectSharingData, ProjectTypes } from '@/types/projects.types';
import { getEasyAiWorkflowJson } from '@/utils/templates/workflowSamples';
import { getEasyAiWorkflowJson } from '@/features/templates/utils/workflowSamples';
import {
isExtraTemplateLinksExperimentEnabled,
TemplateClickSource,