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,297 +1,331 @@
import {
getNodeParameterOptions,
getNodesInformation,
getNodeTranslationHeaders,
getNodeTypes,
getResourceLocatorResults,
getResourceMapperFields,
} from '@/api/nodeTypes';
import * as nodeTypesApi from '@/api/nodeTypes';
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 { omit } from '@/utils/typesUtils';
import type {
ConnectionTypes,
INode,
INodeListSearchResult,
INodeOutputConfiguration,
INodePropertyOptions,
INodeTypeDescription,
INodeTypeNameVersion,
ResourceMapperFields,
Workflow,
} from 'n8n-workflow';
import { NodeConnectionType, NodeHelpers } from 'n8n-workflow';
import { defineStore } from 'pinia';
import { useCredentialsStore } from './credentials.store';
import { useRootStore } from './root.store';
import {
getCredentialOnlyNodeType,
getCredentialTypeName,
isCredentialOnlyNodeType,
} from '@/utils/credentialOnlyNodes';
import * as utils from '@/utils/credentialOnlyNodes';
import { groupNodeTypesByNameAndType } from '@/utils/nodeTypes/nodeTypeTransforms';
import { computed, ref } from 'vue';
export type NodeTypesStore = ReturnType<typeof useNodeTypesStore>;
export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
state: (): INodeTypesState => ({
nodeTypes: {},
}),
getters: {
allNodeTypes(): INodeTypeDescription[] {
return Object.values(this.nodeTypes).reduce<INodeTypeDescription[]>(
(allNodeTypes, nodeType) => {
const versionNumbers = Object.keys(nodeType).map(Number);
const allNodeVersions = versionNumbers.map((version) => nodeType[version]);
export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, () => {
const nodeTypes = ref<NodeTypesByTypeNameAndVersion>({});
return [...allNodeTypes, ...allNodeVersions];
},
[],
);
},
allLatestNodeTypes(): INodeTypeDescription[] {
return Object.values(this.nodeTypes).reduce<INodeTypeDescription[]>(
(allLatestNodeTypes, nodeVersions) => {
const versionNumbers = Object.keys(nodeVersions).map(Number);
const latestNodeVersion = nodeVersions[Math.max(...versionNumbers)];
const rootStore = useRootStore();
if (!latestNodeVersion) return allLatestNodeTypes;
// ---------------------------------------------------------------------------
// #region Computed
// ---------------------------------------------------------------------------
return [...allLatestNodeTypes, latestNodeVersion];
},
[],
);
},
getNodeType() {
return (nodeTypeName: string, version?: number): INodeTypeDescription | null => {
if (isCredentialOnlyNodeType(nodeTypeName)) {
return this.getCredentialOnlyNodeType(nodeTypeName, version);
}
const allNodeTypes = computed(() => {
return Object.values(nodeTypes.value).reduce<INodeTypeDescription[]>(
(allNodeTypes, nodeType) => {
const versionNumbers = Object.keys(nodeType).map(Number);
const allNodeVersions = versionNumbers.map((version) => nodeType[version]);
const nodeVersions = this.nodeTypes[nodeTypeName];
if (!nodeVersions) return null;
return [...allNodeTypes, ...allNodeVersions];
},
[],
);
});
const allLatestNodeTypes = computed(() => {
return Object.values(nodeTypes.value).reduce<INodeTypeDescription[]>(
(allLatestNodeTypes, nodeVersions) => {
const versionNumbers = Object.keys(nodeVersions).map(Number);
const nodeType = nodeVersions[version ?? Math.max(...versionNumbers)];
return nodeType ?? null;
};
},
getNodeVersions() {
return (nodeTypeName: string): number[] => {
return Object.keys(this.nodeTypes[nodeTypeName] ?? {}).map(Number);
};
},
getCredentialOnlyNodeType() {
return (nodeTypeName: string, version?: number): INodeTypeDescription | null => {
const credentialName = getCredentialTypeName(nodeTypeName);
const httpNode = this.getNodeType(
HTTP_REQUEST_NODE_TYPE,
version ?? CREDENTIAL_ONLY_HTTP_NODE_VERSION,
);
const credential = useCredentialsStore().getCredentialTypeByName(credentialName);
return getCredentialOnlyNodeType(httpNode, credential) ?? null;
};
},
isConfigNode() {
return (workflow: Workflow, node: INode, nodeTypeName: string): boolean => {
if (!workflow.nodes[node.name]) {
return false;
}
const nodeType = this.getNodeType(nodeTypeName);
if (!nodeType) {
return false;
}
const outputs = NodeHelpers.getNodeOutputs(workflow, node, nodeType);
const outputTypes = NodeHelpers.getConnectionTypes(outputs);
const latestNodeVersion = nodeVersions[Math.max(...versionNumbers)];
return outputTypes
? outputTypes.filter((output) => output !== NodeConnectionType.Main).length > 0
: 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);
if (!latestNodeVersion) return allLatestNodeTypes;
return [...allLatestNodeTypes, latestNodeVersion];
},
[],
);
});
const getNodeType = computed(() => {
return (nodeTypeName: string, version?: number): INodeTypeDescription | null => {
if (utils.isCredentialOnlyNodeType(nodeTypeName)) {
return getCredentialOnlyNodeType.value(nodeTypeName, version);
}
const nodeVersions = nodeTypes.value[nodeTypeName];
if (!nodeVersions) return null;
const versionNumbers = Object.keys(nodeVersions).map(Number);
const nodeType = nodeVersions[version ?? Math.max(...versionNumbers)];
return nodeType ?? null;
};
});
const getNodeVersions = computed(() => {
return (nodeTypeName: string): number[] => {
return Object.keys(nodeTypes.value[nodeTypeName] ?? {}).map(Number);
};
});
const getCredentialOnlyNodeType = computed(() => {
return (nodeTypeName: string, version?: number): INodeTypeDescription | null => {
const credentialName = utils.getCredentialTypeName(nodeTypeName);
const httpNode = getNodeType.value(
HTTP_REQUEST_NODE_TYPE,
version ?? CREDENTIAL_ONLY_HTTP_NODE_VERSION,
);
const credential = useCredentialsStore().getCredentialTypeByName(credentialName);
return utils.getCredentialOnlyNodeType(httpNode, credential) ?? null;
};
});
const isConfigNode = computed(() => {
return (workflow: Workflow, node: INode, nodeTypeName: string): boolean => {
if (!workflow.nodes[node.name]) {
return false;
}
const nodeType = getNodeType.value(nodeTypeName);
if (!nodeType) {
return false;
}
const outputs = NodeHelpers.getNodeOutputs(workflow, node, nodeType);
const outputTypes = NodeHelpers.getConnectionTypes(outputs);
return outputTypes
? outputTypes.filter((output) => output !== NodeConnectionType.Main).length > 0
: false;
};
});
const isTriggerNode = computed(() => {
return (nodeTypeName: string) => {
const nodeType = getNodeType.value(nodeTypeName);
return !!(nodeType && nodeType.group.includes('trigger'));
};
});
const isCoreNodeType = computed(() => {
return (nodeType: INodeTypeDescription) => {
return nodeType.codex?.categories?.includes('Core Nodes');
};
});
const visibleNodeTypes = computed(() => {
return allLatestNodeTypes.value.filter((nodeType: INodeTypeDescription) => !nodeType.hidden);
});
const nativelyNumberSuffixedDefaults = computed(() => {
return allNodeTypes.value.reduce<string[]>((acc, cur) => {
if (/\d$/.test(cur.defaults.name as string)) acc.push(cur.defaults.name as string);
return acc;
}, []);
});
const visibleNodeTypesByOutputConnectionTypeNames = computed(() => {
const nodesByOutputType = visibleNodeTypes.value.reduce(
(acc, node) => {
const outputTypes = node.outputs;
if (Array.isArray(outputTypes)) {
outputTypes.forEach((value: ConnectionTypes | INodeOutputConfiguration) => {
const outputType = typeof value === 'string' ? value : value.type;
if (!acc[outputType]) {
acc[outputType] = [];
}
acc[outputType].push(node.name);
});
} else {
// If outputs is not an array, it must be a string expression
// in which case we'll try to match all possible non-main output types that are supported
const connectorTypes: ConnectionTypes[] = [
NodeConnectionType.AiVectorStore,
NodeConnectionType.AiChain,
NodeConnectionType.AiDocument,
NodeConnectionType.AiEmbedding,
NodeConnectionType.AiLanguageModel,
NodeConnectionType.AiMemory,
NodeConnectionType.AiOutputParser,
NodeConnectionType.AiTextSplitter,
NodeConnectionType.AiTool,
];
connectorTypes.forEach((outputType: ConnectionTypes) => {
if (outputTypes.includes(outputType)) {
acc[outputType] = acc[outputType] || [];
acc[outputType].push(node.name);
}
});
}
return inputTypes
? inputTypes.filter((input) => input !== NodeConnectionType.Main).length > 0
: false;
};
},
isTriggerNode() {
return (nodeTypeName: string) => {
const nodeType = this.getNodeType(nodeTypeName);
return !!(nodeType && nodeType.group.includes('trigger'));
};
},
isCoreNodeType() {
return (nodeType: INodeTypeDescription) => {
return nodeType.codex?.categories?.includes('Core Nodes');
};
},
visibleNodeTypes(): INodeTypeDescription[] {
return this.allLatestNodeTypes.filter((nodeType: INodeTypeDescription) => !nodeType.hidden);
},
/**
* Getter for node default names ending with a number: `'S3'`, `'Magento 2'`, etc.
*/
nativelyNumberSuffixedDefaults(): string[] {
return this.allNodeTypes.reduce<string[]>((acc, cur) => {
if (/\d$/.test(cur.defaults.name as string)) acc.push(cur.defaults.name as string);
return acc;
}, []);
},
visibleNodeTypesByOutputConnectionTypeNames(): { [key: string]: string[] } {
const nodesByOutputType = this.visibleNodeTypes.reduce(
(acc, node) => {
const outputTypes = node.outputs;
if (Array.isArray(outputTypes)) {
outputTypes.forEach((value: ConnectionTypes | INodeOutputConfiguration) => {
const outputType = typeof value === 'string' ? value : value.type;
if (!acc[outputType]) {
acc[outputType] = [];
}
acc[outputType].push(node.name);
});
} else {
// If outputs is not an array, it must be a string expression
// in which case we'll try to match all possible non-main output types that are supported
const connectorTypes: ConnectionTypes[] = [
NodeConnectionType.AiVectorStore,
NodeConnectionType.AiChain,
NodeConnectionType.AiDocument,
NodeConnectionType.AiEmbedding,
NodeConnectionType.AiLanguageModel,
NodeConnectionType.AiMemory,
NodeConnectionType.AiOutputParser,
NodeConnectionType.AiTextSplitter,
NodeConnectionType.AiTool,
];
connectorTypes.forEach((outputType: ConnectionTypes) => {
if (outputTypes.includes(outputType)) {
acc[outputType] = acc[outputType] || [];
acc[outputType].push(node.name);
}
});
}
},
{} as { [key: string]: string[] },
);
return acc;
},
{} as { [key: string]: string[] },
);
return nodesByOutputType;
});
return nodesByOutputType;
},
visibleNodeTypesByInputConnectionTypeNames(): { [key: string]: string[] } {
const nodesByOutputType = this.visibleNodeTypes.reduce(
(acc, node) => {
const inputTypes = node.inputs;
if (Array.isArray(inputTypes)) {
inputTypes.forEach((value: ConnectionTypes | INodeOutputConfiguration) => {
const outputType = typeof value === 'string' ? value : value.type;
if (!acc[outputType]) {
acc[outputType] = [];
}
acc[outputType].push(node.name);
});
}
return acc;
},
{} as { [key: string]: string[] },
);
return nodesByOutputType;
},
},
actions: {
setNodeTypes(newNodeTypes: INodeTypeDescription[] = []): void {
const nodeTypes = groupNodeTypesByNameAndType(newNodeTypes);
this.nodeTypes = {
...this.nodeTypes,
...nodeTypes,
};
},
removeNodeTypes(nodeTypesToRemove: INodeTypeDescription[]): void {
this.nodeTypes = nodeTypesToRemove.reduce(
(oldNodes, newNodeType) => omit(newNodeType.name, oldNodes),
this.nodeTypes,
);
},
async getNodesInformation(
nodeInfos: INodeTypeNameVersion[],
replace = true,
): Promise<INodeTypeDescription[]> {
const rootStore = useRootStore();
const nodesInformation = await getNodesInformation(rootStore.restApiContext, nodeInfos);
nodesInformation.forEach((nodeInformation) => {
if (nodeInformation.translation) {
const nodeType = nodeInformation.name.replace('n8n-nodes-base.', '');
addNodeTranslation({ [nodeType]: nodeInformation.translation }, rootStore.defaultLocale);
const visibleNodeTypesByInputConnectionTypeNames = computed(() => {
const nodesByOutputType = visibleNodeTypes.value.reduce(
(acc, node) => {
const inputTypes = node.inputs;
if (Array.isArray(inputTypes)) {
inputTypes.forEach((value: ConnectionTypes | INodeOutputConfiguration) => {
const outputType = typeof value === 'string' ? value : value.type;
if (!acc[outputType]) {
acc[outputType] = [];
}
acc[outputType].push(node.name);
});
}
});
if (replace) this.setNodeTypes(nodesInformation);
return nodesInformation;
},
async getFullNodesProperties(nodesToBeFetched: INodeTypeNameVersion[]): Promise<void> {
const credentialsStore = useCredentialsStore();
await credentialsStore.fetchCredentialTypes(true);
await this.getNodesInformation(nodesToBeFetched);
},
async getNodeTypes(): Promise<void> {
const rootStore = useRootStore();
const nodeTypes = await getNodeTypes(rootStore.baseUrl);
if (nodeTypes.length) {
this.setNodeTypes(nodeTypes);
}
},
/**
* Loads node types if they haven't been loaded yet
*/
async loadNodeTypesIfNotLoaded(): Promise<void> {
if (Object.keys(this.nodeTypes).length === 0) {
await this.getNodeTypes();
}
},
async getNodeTranslationHeaders(): Promise<void> {
const rootStore = useRootStore();
const headers = await getNodeTranslationHeaders(rootStore.restApiContext);
return acc;
},
{} as { [key: string]: string[] },
);
if (headers) {
addHeaders(headers, rootStore.defaultLocale);
return nodesByOutputType;
});
const isConfigurableNode = computed(() => {
return (workflow: Workflow, node: INode, nodeTypeName: string): boolean => {
const nodeType = getNodeType.value(nodeTypeName);
if (nodeType === null) {
return false;
}
},
async getNodeParameterOptions(
sendData: DynamicNodeParameters.OptionsRequest,
): Promise<INodePropertyOptions[]> {
const rootStore = useRootStore();
return await getNodeParameterOptions(rootStore.restApiContext, sendData);
},
async getResourceLocatorResults(
sendData: DynamicNodeParameters.ResourceLocatorResultsRequest,
): Promise<INodeListSearchResult> {
const rootStore = useRootStore();
return await getResourceLocatorResults(rootStore.restApiContext, sendData);
},
async getResourceMapperFields(
sendData: DynamicNodeParameters.ResourceMapperFieldsRequest,
): Promise<ResourceMapperFields | null> {
const rootStore = useRootStore();
try {
return await getResourceMapperFields(rootStore.restApiContext, sendData);
} catch (error) {
return null;
const inputs = NodeHelpers.getNodeInputs(workflow, node, nodeType);
const inputTypes = NodeHelpers.getConnectionTypes(inputs);
return inputTypes
? inputTypes.filter((input) => input !== NodeConnectionType.Main).length > 0
: false;
};
});
// #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),
nodeTypes.value,
);
};
const getNodesInformation = async (
nodeInfos: INodeTypeNameVersion[],
replace = true,
): Promise<INodeTypeDescription[]> => {
const nodesInformation = await nodeTypesApi.getNodesInformation(
rootStore.restApiContext,
nodeInfos,
);
nodesInformation.forEach((nodeInformation) => {
if (nodeInformation.translation) {
const nodeType = nodeInformation.name.replace('n8n-nodes-base.', '');
addNodeTranslation({ [nodeType]: nodeInformation.translation }, rootStore.defaultLocale);
}
},
},
});
if (replace) setNodeTypes(nodesInformation);
return nodesInformation;
};
const getFullNodesProperties = async (nodesToBeFetched: INodeTypeNameVersion[]) => {
const credentialsStore = useCredentialsStore();
await credentialsStore.fetchCredentialTypes(true);
await getNodesInformation(nodesToBeFetched);
};
const getNodeTypes = async () => {
const nodeTypes = await nodeTypesApi.getNodeTypes(rootStore.baseUrl);
if (nodeTypes.length) {
setNodeTypes(nodeTypes);
}
};
const loadNodeTypesIfNotLoaded = async () => {
if (Object.keys(nodeTypes.value).length === 0) {
await getNodeTypes();
}
};
const getNodeTranslationHeaders = async () => {
const headers = await nodeTypesApi.getNodeTranslationHeaders(rootStore.restApiContext);
if (headers) {
addHeaders(headers, rootStore.defaultLocale);
}
};
const getNodeParameterOptions = async (sendData: DynamicNodeParameters.OptionsRequest) => {
return await nodeTypesApi.getNodeParameterOptions(rootStore.restApiContext, sendData);
};
const getResourceLocatorResults = async (
sendData: DynamicNodeParameters.ResourceLocatorResultsRequest,
) => {
return await nodeTypesApi.getResourceLocatorResults(rootStore.restApiContext, sendData);
};
const getResourceMapperFields = async (
sendData: DynamicNodeParameters.ResourceMapperFieldsRequest,
) => {
try {
return await nodeTypesApi.getResourceMapperFields(rootStore.restApiContext, sendData);
} catch (error) {
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,
};
});