refactor(editor): Migrate nodeTypes.store to use composition API (no-changelog) (#9795)

This commit is contained in:
Ricardo Espinoza 2024-06-19 12:34:38 -07:00 committed by GitHub
parent 15d631c412
commit 89b8d04fcd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,46 +1,37 @@
import { import * as nodeTypesApi from '@/api/nodeTypes';
getNodeParameterOptions,
getNodesInformation,
getNodeTranslationHeaders,
getNodeTypes,
getResourceLocatorResults,
getResourceMapperFields,
} from '@/api/nodeTypes';
import { HTTP_REQUEST_NODE_TYPE, STORES, CREDENTIAL_ONLY_HTTP_NODE_VERSION } from '@/constants'; import { HTTP_REQUEST_NODE_TYPE, STORES, CREDENTIAL_ONLY_HTTP_NODE_VERSION } from '@/constants';
import type { INodeTypesState, DynamicNodeParameters } from '@/Interface'; import type { DynamicNodeParameters, NodeTypesByTypeNameAndVersion } from '@/Interface';
import { addHeaders, addNodeTranslation } from '@/plugins/i18n'; import { addHeaders, addNodeTranslation } from '@/plugins/i18n';
import { omit } from '@/utils/typesUtils'; import { omit } from '@/utils/typesUtils';
import type { import type {
ConnectionTypes, ConnectionTypes,
INode, INode,
INodeListSearchResult,
INodeOutputConfiguration, INodeOutputConfiguration,
INodePropertyOptions,
INodeTypeDescription, INodeTypeDescription,
INodeTypeNameVersion, INodeTypeNameVersion,
ResourceMapperFields,
Workflow, Workflow,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeConnectionType, NodeHelpers } from 'n8n-workflow'; import { NodeConnectionType, NodeHelpers } from 'n8n-workflow';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useCredentialsStore } from './credentials.store'; import { useCredentialsStore } from './credentials.store';
import { useRootStore } from './root.store'; import { useRootStore } from './root.store';
import { import * as utils from '@/utils/credentialOnlyNodes';
getCredentialOnlyNodeType,
getCredentialTypeName,
isCredentialOnlyNodeType,
} from '@/utils/credentialOnlyNodes';
import { groupNodeTypesByNameAndType } from '@/utils/nodeTypes/nodeTypeTransforms'; import { groupNodeTypesByNameAndType } from '@/utils/nodeTypes/nodeTypeTransforms';
import { computed, ref } from 'vue';
export type NodeTypesStore = ReturnType<typeof useNodeTypesStore>; export type NodeTypesStore = ReturnType<typeof useNodeTypesStore>;
export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, { export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, () => {
state: (): INodeTypesState => ({ const nodeTypes = ref<NodeTypesByTypeNameAndVersion>({});
nodeTypes: {},
}), const rootStore = useRootStore();
getters: {
allNodeTypes(): INodeTypeDescription[] { // ---------------------------------------------------------------------------
return Object.values(this.nodeTypes).reduce<INodeTypeDescription[]>( // #region Computed
// ---------------------------------------------------------------------------
const allNodeTypes = computed(() => {
return Object.values(nodeTypes.value).reduce<INodeTypeDescription[]>(
(allNodeTypes, nodeType) => { (allNodeTypes, nodeType) => {
const versionNumbers = Object.keys(nodeType).map(Number); const versionNumbers = Object.keys(nodeType).map(Number);
const allNodeVersions = versionNumbers.map((version) => nodeType[version]); const allNodeVersions = versionNumbers.map((version) => nodeType[version]);
@ -49,9 +40,10 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
}, },
[], [],
); );
}, });
allLatestNodeTypes(): INodeTypeDescription[] {
return Object.values(this.nodeTypes).reduce<INodeTypeDescription[]>( const allLatestNodeTypes = computed(() => {
return Object.values(nodeTypes.value).reduce<INodeTypeDescription[]>(
(allLatestNodeTypes, nodeVersions) => { (allLatestNodeTypes, nodeVersions) => {
const versionNumbers = Object.keys(nodeVersions).map(Number); const versionNumbers = Object.keys(nodeVersions).map(Number);
const latestNodeVersion = nodeVersions[Math.max(...versionNumbers)]; const latestNodeVersion = nodeVersions[Math.max(...versionNumbers)];
@ -62,14 +54,15 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
}, },
[], [],
); );
}, });
getNodeType() {
const getNodeType = computed(() => {
return (nodeTypeName: string, version?: number): INodeTypeDescription | null => { return (nodeTypeName: string, version?: number): INodeTypeDescription | null => {
if (isCredentialOnlyNodeType(nodeTypeName)) { if (utils.isCredentialOnlyNodeType(nodeTypeName)) {
return this.getCredentialOnlyNodeType(nodeTypeName, version); return getCredentialOnlyNodeType.value(nodeTypeName, version);
} }
const nodeVersions = this.nodeTypes[nodeTypeName]; const nodeVersions = nodeTypes.value[nodeTypeName];
if (!nodeVersions) return null; if (!nodeVersions) return null;
@ -77,29 +70,32 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
const nodeType = nodeVersions[version ?? Math.max(...versionNumbers)]; const nodeType = nodeVersions[version ?? Math.max(...versionNumbers)];
return nodeType ?? null; return nodeType ?? null;
}; };
}, });
getNodeVersions() {
const getNodeVersions = computed(() => {
return (nodeTypeName: string): number[] => { return (nodeTypeName: string): number[] => {
return Object.keys(this.nodeTypes[nodeTypeName] ?? {}).map(Number); return Object.keys(nodeTypes.value[nodeTypeName] ?? {}).map(Number);
}; };
}, });
getCredentialOnlyNodeType() {
const getCredentialOnlyNodeType = computed(() => {
return (nodeTypeName: string, version?: number): INodeTypeDescription | null => { return (nodeTypeName: string, version?: number): INodeTypeDescription | null => {
const credentialName = getCredentialTypeName(nodeTypeName); const credentialName = utils.getCredentialTypeName(nodeTypeName);
const httpNode = this.getNodeType( const httpNode = getNodeType.value(
HTTP_REQUEST_NODE_TYPE, HTTP_REQUEST_NODE_TYPE,
version ?? CREDENTIAL_ONLY_HTTP_NODE_VERSION, version ?? CREDENTIAL_ONLY_HTTP_NODE_VERSION,
); );
const credential = useCredentialsStore().getCredentialTypeByName(credentialName); const credential = useCredentialsStore().getCredentialTypeByName(credentialName);
return getCredentialOnlyNodeType(httpNode, credential) ?? null; return utils.getCredentialOnlyNodeType(httpNode, credential) ?? null;
}; };
}, });
isConfigNode() {
const isConfigNode = computed(() => {
return (workflow: Workflow, node: INode, nodeTypeName: string): boolean => { return (workflow: Workflow, node: INode, nodeTypeName: string): boolean => {
if (!workflow.nodes[node.name]) { if (!workflow.nodes[node.name]) {
return false; return false;
} }
const nodeType = this.getNodeType(nodeTypeName); const nodeType = getNodeType.value(nodeTypeName);
if (!nodeType) { if (!nodeType) {
return false; return false;
} }
@ -110,46 +106,34 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
? outputTypes.filter((output) => output !== NodeConnectionType.Main).length > 0 ? outputTypes.filter((output) => output !== NodeConnectionType.Main).length > 0
: false; : false;
}; };
}, });
isConfigurableNode() {
return (workflow: Workflow, node: INode, nodeTypeName: string): boolean => {
const nodeType = this.getNodeType(nodeTypeName);
if (nodeType === null) {
return false;
}
const inputs = NodeHelpers.getNodeInputs(workflow, node, nodeType);
const inputTypes = NodeHelpers.getConnectionTypes(inputs);
return inputTypes const isTriggerNode = computed(() => {
? inputTypes.filter((input) => input !== NodeConnectionType.Main).length > 0
: false;
};
},
isTriggerNode() {
return (nodeTypeName: string) => { return (nodeTypeName: string) => {
const nodeType = this.getNodeType(nodeTypeName); const nodeType = getNodeType.value(nodeTypeName);
return !!(nodeType && nodeType.group.includes('trigger')); return !!(nodeType && nodeType.group.includes('trigger'));
}; };
}, });
isCoreNodeType() {
const isCoreNodeType = computed(() => {
return (nodeType: INodeTypeDescription) => { return (nodeType: INodeTypeDescription) => {
return nodeType.codex?.categories?.includes('Core Nodes'); return nodeType.codex?.categories?.includes('Core Nodes');
}; };
}, });
visibleNodeTypes(): INodeTypeDescription[] {
return this.allLatestNodeTypes.filter((nodeType: INodeTypeDescription) => !nodeType.hidden); const visibleNodeTypes = computed(() => {
}, return allLatestNodeTypes.value.filter((nodeType: INodeTypeDescription) => !nodeType.hidden);
/** });
* Getter for node default names ending with a number: `'S3'`, `'Magento 2'`, etc.
*/ const nativelyNumberSuffixedDefaults = computed(() => {
nativelyNumberSuffixedDefaults(): string[] { return allNodeTypes.value.reduce<string[]>((acc, cur) => {
return this.allNodeTypes.reduce<string[]>((acc, cur) => {
if (/\d$/.test(cur.defaults.name as string)) acc.push(cur.defaults.name as string); if (/\d$/.test(cur.defaults.name as string)) acc.push(cur.defaults.name as string);
return acc; return acc;
}, []); }, []);
}, });
visibleNodeTypesByOutputConnectionTypeNames(): { [key: string]: string[] } {
const nodesByOutputType = this.visibleNodeTypes.reduce( const visibleNodeTypesByOutputConnectionTypeNames = computed(() => {
const nodesByOutputType = visibleNodeTypes.value.reduce(
(acc, node) => { (acc, node) => {
const outputTypes = node.outputs; const outputTypes = node.outputs;
if (Array.isArray(outputTypes)) { if (Array.isArray(outputTypes)) {
@ -188,9 +172,10 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
); );
return nodesByOutputType; return nodesByOutputType;
}, });
visibleNodeTypesByInputConnectionTypeNames(): { [key: string]: string[] } {
const nodesByOutputType = this.visibleNodeTypes.reduce( const visibleNodeTypesByInputConnectionTypeNames = computed(() => {
const nodesByOutputType = visibleNodeTypes.value.reduce(
(acc, node) => { (acc, node) => {
const inputTypes = node.inputs; const inputTypes = node.inputs;
if (Array.isArray(inputTypes)) { if (Array.isArray(inputTypes)) {
@ -209,28 +194,52 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
); );
return nodesByOutputType; return nodesByOutputType;
}, });
},
actions: { const isConfigurableNode = computed(() => {
setNodeTypes(newNodeTypes: INodeTypeDescription[] = []): void { return (workflow: Workflow, node: INode, nodeTypeName: string): boolean => {
const nodeTypes = groupNodeTypesByNameAndType(newNodeTypes); const nodeType = getNodeType.value(nodeTypeName);
this.nodeTypes = { if (nodeType === null) {
...this.nodeTypes, return false;
...nodeTypes, }
const inputs = NodeHelpers.getNodeInputs(workflow, node, nodeType);
const inputTypes = NodeHelpers.getConnectionTypes(inputs);
return inputTypes
? inputTypes.filter((input) => input !== NodeConnectionType.Main).length > 0
: false;
}; };
}, });
removeNodeTypes(nodeTypesToRemove: INodeTypeDescription[]): void {
this.nodeTypes = nodeTypesToRemove.reduce( // #endregion
// ---------------------------------------------------------------------------
// #region Methods
// ---------------------------------------------------------------------------
const setNodeTypes = (newNodeTypes: INodeTypeDescription[] = []) => {
const groupedNodeTypes = groupNodeTypesByNameAndType(newNodeTypes);
nodeTypes.value = {
...nodeTypes.value,
...groupedNodeTypes,
};
};
const removeNodeTypes = (nodeTypesToRemove: INodeTypeDescription[]) => {
nodeTypes.value = nodeTypesToRemove.reduce(
(oldNodes, newNodeType) => omit(newNodeType.name, oldNodes), (oldNodes, newNodeType) => omit(newNodeType.name, oldNodes),
this.nodeTypes, nodeTypes.value,
); );
}, };
async getNodesInformation(
const getNodesInformation = async (
nodeInfos: INodeTypeNameVersion[], nodeInfos: INodeTypeNameVersion[],
replace = true, replace = true,
): Promise<INodeTypeDescription[]> { ): Promise<INodeTypeDescription[]> => {
const rootStore = useRootStore(); const nodesInformation = await nodeTypesApi.getNodesInformation(
const nodesInformation = await getNodesInformation(rootStore.restApiContext, nodeInfos); rootStore.restApiContext,
nodeInfos,
);
nodesInformation.forEach((nodeInformation) => { nodesInformation.forEach((nodeInformation) => {
if (nodeInformation.translation) { if (nodeInformation.translation) {
@ -239,59 +248,84 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
addNodeTranslation({ [nodeType]: nodeInformation.translation }, rootStore.defaultLocale); addNodeTranslation({ [nodeType]: nodeInformation.translation }, rootStore.defaultLocale);
} }
}); });
if (replace) this.setNodeTypes(nodesInformation); if (replace) setNodeTypes(nodesInformation);
return nodesInformation; return nodesInformation;
}, };
async getFullNodesProperties(nodesToBeFetched: INodeTypeNameVersion[]): Promise<void> {
const getFullNodesProperties = async (nodesToBeFetched: INodeTypeNameVersion[]) => {
const credentialsStore = useCredentialsStore(); const credentialsStore = useCredentialsStore();
await credentialsStore.fetchCredentialTypes(true); await credentialsStore.fetchCredentialTypes(true);
await this.getNodesInformation(nodesToBeFetched); await getNodesInformation(nodesToBeFetched);
}, };
async getNodeTypes(): Promise<void> {
const rootStore = useRootStore(); const getNodeTypes = async () => {
const nodeTypes = await getNodeTypes(rootStore.baseUrl); const nodeTypes = await nodeTypesApi.getNodeTypes(rootStore.baseUrl);
if (nodeTypes.length) { if (nodeTypes.length) {
this.setNodeTypes(nodeTypes); setNodeTypes(nodeTypes);
} }
}, };
/**
* Loads node types if they haven't been loaded yet const loadNodeTypesIfNotLoaded = async () => {
*/ if (Object.keys(nodeTypes.value).length === 0) {
async loadNodeTypesIfNotLoaded(): Promise<void> { await getNodeTypes();
if (Object.keys(this.nodeTypes).length === 0) {
await this.getNodeTypes();
} }
}, };
async getNodeTranslationHeaders(): Promise<void> {
const rootStore = useRootStore(); const getNodeTranslationHeaders = async () => {
const headers = await getNodeTranslationHeaders(rootStore.restApiContext); const headers = await nodeTypesApi.getNodeTranslationHeaders(rootStore.restApiContext);
if (headers) { if (headers) {
addHeaders(headers, rootStore.defaultLocale); addHeaders(headers, rootStore.defaultLocale);
} }
}, };
async getNodeParameterOptions(
sendData: DynamicNodeParameters.OptionsRequest, const getNodeParameterOptions = async (sendData: DynamicNodeParameters.OptionsRequest) => {
): Promise<INodePropertyOptions[]> { return await nodeTypesApi.getNodeParameterOptions(rootStore.restApiContext, sendData);
const rootStore = useRootStore(); };
return await getNodeParameterOptions(rootStore.restApiContext, sendData);
}, const getResourceLocatorResults = async (
async getResourceLocatorResults(
sendData: DynamicNodeParameters.ResourceLocatorResultsRequest, sendData: DynamicNodeParameters.ResourceLocatorResultsRequest,
): Promise<INodeListSearchResult> { ) => {
const rootStore = useRootStore(); return await nodeTypesApi.getResourceLocatorResults(rootStore.restApiContext, sendData);
return await getResourceLocatorResults(rootStore.restApiContext, sendData); };
},
async getResourceMapperFields( const getResourceMapperFields = async (
sendData: DynamicNodeParameters.ResourceMapperFieldsRequest, sendData: DynamicNodeParameters.ResourceMapperFieldsRequest,
): Promise<ResourceMapperFields | null> { ) => {
const rootStore = useRootStore();
try { try {
return await getResourceMapperFields(rootStore.restApiContext, sendData); return await nodeTypesApi.getResourceMapperFields(rootStore.restApiContext, sendData);
} catch (error) { } catch (error) {
return null; return null;
} }
}, };
},
// #endregion
return {
nodeTypes,
allNodeTypes,
allLatestNodeTypes,
getNodeType,
getNodeVersions,
getCredentialOnlyNodeType,
isConfigNode,
isTriggerNode,
isCoreNodeType,
visibleNodeTypes,
nativelyNumberSuffixedDefaults,
visibleNodeTypesByOutputConnectionTypeNames,
visibleNodeTypesByInputConnectionTypeNames,
isConfigurableNode,
getResourceMapperFields,
getResourceLocatorResults,
getNodeParameterOptions,
getNodesInformation,
getFullNodesProperties,
getNodeTypes,
loadNodeTypesIfNotLoaded,
getNodeTranslationHeaders,
setNodeTypes,
removeNodeTypes,
};
}); });