diff --git a/packages/editor-ui/src/App.vue b/packages/editor-ui/src/App.vue index d4a8a2d2b2..7faa2132d7 100644 --- a/packages/editor-ui/src/App.vue +++ b/packages/editor-ui/src/App.vue @@ -160,8 +160,7 @@ export default mixins( this.$externalHooks().run('app.mount'); if (this.defaultLocale !== 'en') { - const headers = await this.restApi().getNodeTranslationHeaders(); - if (headers) addHeaders(headers, this.defaultLocale); + void this.$store.dispatch('nodeTypes/getNodeTranslationHeaders'); } }, watch: { diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index a2f089681d..2f929f81de 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -173,10 +173,6 @@ export interface IRestApi { stopCurrentExecution(executionId: string): Promise; makeRestApiRequest(method: string, endpoint: string, data?: any): Promise; // tslint:disable-line:no-any getCredentialTranslation(credentialType: string): Promise; - getNodeTranslationHeaders(): Promise; - getNodeTypes(onlyLatest?: boolean): Promise; - getNodesInformation(nodeInfos: INodeTypeNameVersion[]): Promise; - getNodeParameterOptions(sendData: { nodeTypeAndVersion: INodeTypeNameVersion, path: string, methodName?: string, loadOptions?: ILoadOptions, currentNodeParameters: INodeParameters, credentials?: INodeCredentials }): Promise ; removeTestWebhook(workflowId: string): Promise; runWorkflow(runData: IStartRunData): Promise; createNewWorkflow(sendData: IWorkflowDataUpdate): Promise; @@ -853,7 +849,6 @@ export interface IRootState { lastSelectedNode: string | null; lastSelectedNodeOutputIndex: number | null; nodeIndex: Array; - nodeTypes: INodeTypeDescription[]; nodeViewOffsetPosition: XYPosition; nodeViewMoveInProgress: boolean; selectedNodes: INodeUi[]; @@ -959,6 +954,10 @@ export interface ISettingsState { onboardingCallPromptEnabled: boolean; } +export interface INodeTypesState { + nodeTypes: { [nodeType: string]: INodeTypeDescription }; +} + export interface ITemplateState { categories: {[id: string]: ITemplatesCategory}; collections: {[id: string]: ITemplatesCollection}; diff --git a/packages/editor-ui/src/api/nodeTypes.ts b/packages/editor-ui/src/api/nodeTypes.ts new file mode 100644 index 0000000000..2328db1a70 --- /dev/null +++ b/packages/editor-ui/src/api/nodeTypes.ts @@ -0,0 +1,47 @@ +import { makeRestApiRequest } from './helpers'; +import type { + INodeTranslationHeaders, + IRestApiContext, +} from '@/Interface'; +import type { + ILoadOptions, + INodeCredentials, + INodeParameters, + INodePropertyOptions, + INodeTypeDescription, + INodeTypeNameVersion, +} from 'n8n-workflow'; + +export async function getNodeTypes( + context: IRestApiContext, + { onlyLatest } = { onlyLatest: false }, +) { + return makeRestApiRequest(context, 'GET', '/node-types', { onlyLatest }); +} + +export async function getNodeTranslationHeaders( + context: IRestApiContext, +): Promise { + return makeRestApiRequest(context, 'GET', '/node-translation-headers'); +} + +export async function getNodesInformation( + context: IRestApiContext, + nodeInfos: INodeTypeNameVersion[], +): Promise { + return makeRestApiRequest(context, 'POST', '/node-types', { nodeInfos }); +} + +export async function getNodeParameterOptions( + context: IRestApiContext, + sendData: { + nodeTypeAndVersion: INodeTypeNameVersion, + path: string, + methodName?: string, + loadOptions?: ILoadOptions, + currentNodeParameters: INodeParameters, + credentials?: INodeCredentials, + }, +): Promise { + return makeRestApiRequest(context, 'GET', '/node-parameter-options', sendData); +} diff --git a/packages/editor-ui/src/components/ActivationModal.vue b/packages/editor-ui/src/components/ActivationModal.vue index e9d2c5f6a4..6690b4b2ef 100644 --- a/packages/editor-ui/src/components/ActivationModal.vue +++ b/packages/editor-ui/src/components/ActivationModal.vue @@ -80,7 +80,7 @@ export default Vue.extend({ const trigger = foundTriggers[0]; - const triggerNodeType = this.$store.getters.nodeType(trigger.type, trigger.typeVersion) as INodeTypeDescription; + const triggerNodeType = this.$store.getters['nodeTypes/getNodeType'](trigger.type, trigger.typeVersion) as INodeTypeDescription; if (triggerNodeType.activationMessage) { return triggerNodeType.activationMessage; } diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue index 5fd82ae5c8..c770346222 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue @@ -193,16 +193,6 @@ export default mixins(restApi).extend({ }, }, methods: { - /** - * Get the current version for a node type. - */ - async getCurrentNodeVersion(targetNodeType: string) { - const { allNodeTypes }: { allNodeTypes: INodeTypeDescription[] } = this.$store.getters; - const found = allNodeTypes.find(nodeType => nodeType.name === targetNodeType); - - return found ? found.version : 1; - }, - onDataChange (event: { name: string; value: string | number | boolean | Date | null }): void { this.$emit('change', event); }, diff --git a/packages/editor-ui/src/components/CredentialIcon.vue b/packages/editor-ui/src/components/CredentialIcon.vue index f4d5e302d8..995b927c8a 100644 --- a/packages/editor-ui/src/components/CredentialIcon.vue +++ b/packages/editor-ui/src/components/CredentialIcon.vue @@ -34,7 +34,7 @@ export default Vue.extend({ if (this.credentialWithIcon && this.credentialWithIcon.icon && this.credentialWithIcon.icon.startsWith('node:')) { const nodeType = this.credentialWithIcon.icon.replace('node:', ''); - return this.$store.getters.nodeType(nodeType); + return this.$store.getters['nodeTypes/getNodeType'](nodeType); } const nodesWithAccess = this.$store.getters['credentials/getNodesWithAccess'](this.credentialTypeName); diff --git a/packages/editor-ui/src/components/Error/NodeErrorView.vue b/packages/editor-ui/src/components/Error/NodeErrorView.vue index af4cc6fcc1..01a695a92f 100644 --- a/packages/editor-ui/src/components/Error/NodeErrorView.vue +++ b/packages/editor-ui/src/components/Error/NodeErrorView.vue @@ -129,7 +129,7 @@ export default mixins( if (!node) { return []; } - const nodeType = this.$store.getters.nodeType(node.type, node.typeVersion); + const nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion); if (nodeType === null) { return []; diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue index 9f5a20e44b..6626662d33 100644 --- a/packages/editor-ui/src/components/Node.vue +++ b/packages/editor-ui/src/components/Node.vue @@ -172,7 +172,7 @@ export default mixins( }, isSingleActiveTriggerNode (): boolean { const nodes = this.$store.getters.workflowTriggerNodes.filter((node: INodeUi) => { - const nodeType = this.$store.getters.nodeType(node.type, node.typeVersion) as INodeTypeDescription | null; + const nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null; return nodeType && nodeType.eventTriggerDescription !== '' && !node.disabled; }); @@ -188,7 +188,7 @@ export default mixins( return this.node && this.node.disabled; }, nodeType (): INodeTypeDescription | null { - return this.data && this.$store.getters.nodeType(this.data.type, this.data.typeVersion); + return this.data && this.$store.getters['nodeTypes/getNodeType'](this.data.type, this.data.typeVersion); }, node (): INodeUi | undefined { // same as this.data but reactive.. return this.$store.getters.nodesByName[this.name] as INodeUi | undefined; diff --git a/packages/editor-ui/src/components/NodeCreator/NodeCreator.vue b/packages/editor-ui/src/components/NodeCreator/NodeCreator.vue index b9bb417637..edc8b643d3 100644 --- a/packages/editor-ui/src/components/NodeCreator/NodeCreator.vue +++ b/packages/editor-ui/src/components/NodeCreator/NodeCreator.vue @@ -50,7 +50,7 @@ export default Vue.extend({ computed: { ...mapGetters('users', ['personalizedNodeTypes']), nodeTypes(): INodeTypeDescription[] { - return this.$store.getters.allNodeTypes; + return this.$store.getters['nodeTypes/allNodeTypes']; }, visibleNodeTypes(): INodeTypeDescription[] { return this.allNodeTypes diff --git a/packages/editor-ui/src/components/NodeCredentials.vue b/packages/editor-ui/src/components/NodeCredentials.vue index 65a892263f..e1db8b4852 100644 --- a/packages/editor-ui/src/components/NodeCredentials.vue +++ b/packages/editor-ui/src/components/NodeCredentials.vue @@ -117,7 +117,7 @@ export default mixins( if (credType) return [credType]; - const activeNodeType = this.$store.getters.nodeType(node.type, node.typeVersion) as INodeTypeDescription | null; + const activeNodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null; if (activeNodeType && activeNodeType.credentials) { return activeNodeType.credentials; } diff --git a/packages/editor-ui/src/components/NodeDetailsView.vue b/packages/editor-ui/src/components/NodeDetailsView.vue index b453b0d2f3..fb32a3c5d3 100644 --- a/packages/editor-ui/src/components/NodeDetailsView.vue +++ b/packages/editor-ui/src/components/NodeDetailsView.vue @@ -209,7 +209,7 @@ export default mixins( }, activeNodeType(): INodeTypeDescription | null { if (this.activeNode) { - return this.$store.getters.nodeType(this.activeNode.type, this.activeNode.typeVersion); + return this.$store.getters['nodeTypes/getNodeType'](this.activeNode.type, this.activeNode.typeVersion); } return null; }, diff --git a/packages/editor-ui/src/components/NodeExecuteButton.vue b/packages/editor-ui/src/components/NodeExecuteButton.vue index 0c084b23bf..60666cbd69 100644 --- a/packages/editor-ui/src/components/NodeExecuteButton.vue +++ b/packages/editor-ui/src/components/NodeExecuteButton.vue @@ -59,7 +59,7 @@ export default mixins( }, nodeType (): INodeTypeDescription | null { if (this.node) { - return this.$store.getters.nodeType(this.node.type, this.node.typeVersion); + return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion); } return null; }, diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue index baec494c20..208e5f989d 100644 --- a/packages/editor-ui/src/components/NodeSettings.vue +++ b/packages/editor-ui/src/components/NodeSettings.vue @@ -146,7 +146,7 @@ export default mixins( computed: { nodeType (): INodeTypeDescription | null { if (this.node) { - return this.$store.getters.nodeType(this.node.type, this.node.typeVersion); + return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion); } return null; @@ -446,7 +446,7 @@ export default mixins( } else if (parameterData.name.startsWith('parameters.')) { // A node parameter changed - const nodeType = this.$store.getters.nodeType(node.type, node.typeVersion) as INodeTypeDescription | null; + const nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null; if (!nodeType) { return; } diff --git a/packages/editor-ui/src/components/OutputPanel.vue b/packages/editor-ui/src/components/OutputPanel.vue index 255aad2c7c..cbb80cfa8b 100644 --- a/packages/editor-ui/src/components/OutputPanel.vue +++ b/packages/editor-ui/src/components/OutputPanel.vue @@ -114,7 +114,7 @@ export default mixins( }, nodeType (): INodeTypeDescription | null { if (this.node) { - return this.$store.getters.nodeType(this.node.type, this.node.typeVersion); + return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion); } return null; }, diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue index 9e65a9e5de..10caa68ccd 100644 --- a/packages/editor-ui/src/components/ParameterInput.vue +++ b/packages/editor-ui/src/components/ParameterInput.vue @@ -738,7 +738,20 @@ export default mixins( const loadOptionsMethod = this.getArgument('loadOptionsMethod') as string | undefined; const loadOptions = this.getArgument('loadOptions') as ILoadOptions | undefined; - const options = await this.restApi().getNodeParameterOptions({ nodeTypeAndVersion: { name: this.node.type, version: this.node.typeVersion}, path: this.path, methodName: loadOptionsMethod, loadOptions, currentNodeParameters: resolvedNodeParameters, credentials: this.node.credentials }); + const options = await this.$store.dispatch('nodeTypes/getNodeParameterOptions', + { + nodeTypeAndVersion: { + name: this.node.type, + version: this.node.typeVersion, + }, + path: this.path, + methodName: loadOptionsMethod, + loadOptions, + currentNodeParameters: resolvedNodeParameters, + credentials: this.node.credentials, + }, + ); + this.remoteParameterOptions.push.apply(this.remoteParameterOptions, options); } catch (error) { this.remoteParameterOptionsLoadingIssues = error.message; diff --git a/packages/editor-ui/src/components/ParameterInputList.vue b/packages/editor-ui/src/components/ParameterInputList.vue index bc89d73dab..0daae3e8ec 100644 --- a/packages/editor-ui/src/components/ParameterInputList.vue +++ b/packages/editor-ui/src/components/ParameterInputList.vue @@ -153,7 +153,7 @@ export default mixins( methods: { getCredentialsDependencies() { const dependencies = new Set(); - const nodeType = this.$store.getters.nodeType(this.node.type, this.node.typeVersion) as INodeTypeDescription | undefined; + const nodeType = this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion) as INodeTypeDescription | undefined; // Get names of all fields that credentials rendering depends on (using displayOptions > show) if(nodeType && nodeType.credentials) { diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 3f4cf326dc..99d57758fe 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -507,7 +507,7 @@ export default mixins( }, nodeType (): INodeTypeDescription | null { if (this.node) { - return this.$store.getters.nodeType(this.node.type, this.node.typeVersion); + return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion); } return null; }, diff --git a/packages/editor-ui/src/components/Sticky.vue b/packages/editor-ui/src/components/Sticky.vue index 12d7ef7c29..e57d31eb36 100644 --- a/packages/editor-ui/src/components/Sticky.vue +++ b/packages/editor-ui/src/components/Sticky.vue @@ -81,7 +81,7 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext return this.$store.getters.getSelectedNodes.find((node: INodeUi) => node.name === this.data.name); }, nodeType (): INodeTypeDescription | null { - return this.data && this.$store.getters.nodeType(this.data.type, this.data.typeVersion); + return this.data && this.$store.getters['nodeTypes/getNodeType'](this.data.type, this.data.typeVersion); }, node (): INodeUi | undefined { // same as this.data but reactive.. return this.$store.getters.nodesByName[this.name] as INodeUi | undefined; diff --git a/packages/editor-ui/src/components/TriggerPanel.vue b/packages/editor-ui/src/components/TriggerPanel.vue index ca403b87fe..6b5ddb9c74 100644 --- a/packages/editor-ui/src/components/TriggerPanel.vue +++ b/packages/editor-ui/src/components/TriggerPanel.vue @@ -125,7 +125,7 @@ export default mixins(workflowHelpers, copyPaste, showMessage).extend({ }, nodeType(): INodeTypeDescription | null { if (this.node) { - return this.$store.getters.nodeType(this.node.type, this.node.typeVersion); + return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion); } return null; diff --git a/packages/editor-ui/src/components/mixins/nodeBase.ts b/packages/editor-ui/src/components/mixins/nodeBase.ts index c712a9e800..06f574178c 100644 --- a/packages/editor-ui/src/components/mixins/nodeBase.ts +++ b/packages/editor-ui/src/components/mixins/nodeBase.ts @@ -287,10 +287,10 @@ export const nodeBase = mixins( }); }, __addNode (node: INodeUi) { - let nodeTypeData = this.$store.getters.nodeType(node.type, node.typeVersion) as INodeTypeDescription | null; + let nodeTypeData = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null; if (!nodeTypeData) { // If node type is not know use by default the base.noOp data to display it - nodeTypeData = this.$store.getters.nodeType(NO_OP_NODE_TYPE) as INodeTypeDescription; + nodeTypeData = this.$store.getters['nodeTypes/getNodeType'](NO_OP_NODE_TYPE) as INodeTypeDescription; } this.__addInputEndpoints(node, nodeTypeData); diff --git a/packages/editor-ui/src/components/mixins/nodeHelpers.ts b/packages/editor-ui/src/components/mixins/nodeHelpers.ts index 6fae0e4a9b..4309b7582a 100644 --- a/packages/editor-ui/src/components/mixins/nodeHelpers.ts +++ b/packages/editor-ui/src/components/mixins/nodeHelpers.ts @@ -190,7 +190,7 @@ export const nodeHelpers = mixins( // Updates the parameter-issues of the node updateNodeParameterIssues(node: INodeUi, nodeType?: INodeTypeDescription): void { if (nodeType === undefined) { - nodeType = this.$store.getters.nodeType(node.type, node.typeVersion); + nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion); } if (nodeType === null) { @@ -221,7 +221,7 @@ export const nodeHelpers = mixins( } if (nodeType === undefined) { - nodeType = this.$store.getters.nodeType(node.type, node.typeVersion); + nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion); } if (nodeType === null || nodeType!.credentials === undefined) { diff --git a/packages/editor-ui/src/components/mixins/pushConnection.ts b/packages/editor-ui/src/components/mixins/pushConnection.ts index 83d6b2ebaa..3750e5ab4a 100644 --- a/packages/editor-ui/src/components/mixins/pushConnection.ts +++ b/packages/editor-ui/src/components/mixins/pushConnection.ts @@ -272,7 +272,7 @@ export const pushConnection = mixins( const execution = this.$store.getters.getWorkflowExecution; if (execution && execution.executedNode) { const node = this.$store.getters.getNodeByName(execution.executedNode); - const nodeType = node && this.$store.getters.nodeType(node.type, node.typeVersion); + const nodeType = node && this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion); const nodeOutput = execution && execution.executedNode && execution.data && execution.data.resultData && execution.data.resultData.runData && execution.data.resultData.runData[execution.executedNode]; if (node && nodeType && !nodeOutput) { this.$showMessage({ @@ -370,19 +370,7 @@ export const pushConnection = mixins( this.processWaitingPushMessages(); } else if (receivedData.type === 'reloadNodeType') { - const pushData = receivedData.data; - - const nodesToBeFetched: INodeTypeNameVersion[] = [pushData]; - - // Force reload of all credential types - this.$store.dispatch('credentials/fetchCredentialTypes', true) - .then(() => { - // Get the data of the node and update in internal storage - return this.restApi().getNodesInformation(nodesToBeFetched); - }) - .then((nodesInfo) => { - this.$store.commit('updateNodeTypes', nodesInfo); - }); + this.$store.dispatch('nodeTypes/getFullNodesProperties', [receivedData.data]); } else if (receivedData.type === 'removeNodeType') { const pushData = receivedData.data; @@ -391,7 +379,7 @@ export const pushConnection = mixins( // Force reload of all credential types this.$store.dispatch('credentials/fetchCredentialTypes') .then(() => { - this.$store.commit('removeNodeTypes', nodesToBeRemoved); + this.$store.commit('nodeTypes/removeNodeTypes', nodesToBeRemoved); }); } return true; diff --git a/packages/editor-ui/src/components/mixins/restApi.ts b/packages/editor-ui/src/components/mixins/restApi.ts index 7dad40044f..3109399001 100644 --- a/packages/editor-ui/src/components/mixins/restApi.ts +++ b/packages/editor-ui/src/components/mixins/restApi.ts @@ -84,24 +84,6 @@ export const restApi = Vue.extend({ return self.restApi().makeRestApiRequest('GET', '/credential-translation', { credentialType }); }, - getNodeTranslationHeaders: (): Promise => { - return self.restApi().makeRestApiRequest('GET', '/node-translation-headers'); - }, - - // Returns all node-types - getNodeTypes: (onlyLatest = false): Promise => { - return self.restApi().makeRestApiRequest('GET', `/node-types`, {onlyLatest}); - }, - - getNodesInformation: (nodeInfos: INodeTypeNameVersion[]): Promise => { - return self.restApi().makeRestApiRequest('POST', `/node-types`, {nodeInfos}); - }, - - // Returns all the parameter options from the server - getNodeParameterOptions: (sendData: { nodeTypeAndVersion: INodeTypeNameVersion, path: string, methodName?: string, loadOptions?: ILoadOptions, currentNodeParameters: INodeParameters, credentials?: INodeCredentials }): Promise => { - return self.restApi().makeRestApiRequest('GET', '/node-parameter-options', sendData); - }, - // Removes a test webhook removeTestWebhook: (workflowId: string): Promise => { return self.restApi().makeRestApiRequest('DELETE', `/test-webhook/${workflowId}`); diff --git a/packages/editor-ui/src/components/mixins/workflowHelpers.ts b/packages/editor-ui/src/components/mixins/workflowHelpers.ts index 86f6503c72..29af5bb8fd 100644 --- a/packages/editor-ui/src/components/mixins/workflowHelpers.ts +++ b/packages/editor-ui/src/components/mixins/workflowHelpers.ts @@ -188,7 +188,7 @@ export const workflowHelpers = mixins( const returnData: INodeTypesMaxCount = {}; - const nodeTypes = this.$store.getters.allNodeTypes; + const nodeTypes = this.$store.getters['nodeTypes/allNodeTypes']; for (const nodeType of nodeTypes) { if (nodeType.maxNodes !== undefined) { returnData[nodeType.name] = { @@ -298,7 +298,7 @@ export const workflowHelpers = mixins( return []; }, getByNameAndVersion: (nodeType: string, version?: number): INodeType | undefined => { - const nodeTypeDescription = this.$store.getters.nodeType(nodeType, version) as INodeTypeDescription | null; + const nodeTypeDescription = this.$store.getters['nodeTypes/getNodeType'](nodeType, version) as INodeTypeDescription | null; if (nodeTypeDescription === null) { return undefined; @@ -406,7 +406,7 @@ export const workflowHelpers = mixins( // Get the data of the node type that we can get the default values // TODO: Later also has to care about the node-type-version as defaults could be different - const nodeType = this.$store.getters.nodeType(node.type, node.typeVersion) as INodeTypeDescription | null; + const nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null; if (nodeType !== null) { // Node-Type is known so we can save the parameters correctly diff --git a/packages/editor-ui/src/modules/credentials.ts b/packages/editor-ui/src/modules/credentials.ts index 05f624acd5..64720ae3c5 100644 --- a/packages/editor-ui/src/modules/credentials.ts +++ b/packages/editor-ui/src/modules/credentials.ts @@ -104,7 +104,7 @@ const module: Module = { }, getNodesWithAccess (state: ICredentialsState, getters: any, rootState: IRootState, rootGetters: any) { // tslint:disable-line:no-any return (credentialTypeName: string) => { - const nodeTypes: INodeTypeDescription[] = rootGetters.allNodeTypes; + const nodeTypes: INodeTypeDescription[] = rootGetters['nodeTypes/allNodeTypes']; return nodeTypes.filter((nodeType: INodeTypeDescription) => { if (!nodeType.credentials) { diff --git a/packages/editor-ui/src/modules/nodeTypes.ts b/packages/editor-ui/src/modules/nodeTypes.ts new file mode 100644 index 0000000000..e84820b0ec --- /dev/null +++ b/packages/editor-ui/src/modules/nodeTypes.ts @@ -0,0 +1,136 @@ +import Vue from 'vue'; +import { ActionContext, Module } from 'vuex'; +import type { + ILoadOptions, + INodeCredentials, + INodeParameters, + INodeTypeDescription, + INodeTypeNameVersion, +} from 'n8n-workflow'; + +import { DEFAULT_NODETYPE_VERSION } from '@/constants'; +import { addHeaders, addNodeTranslation } from '@/plugins/i18n'; +import { + getNodeParameterOptions, + getNodesInformation, + getNodeTranslationHeaders, + getNodeTypes, +} from '@/api/nodeTypes'; +import { omit } from '@/utils'; +import type { IRootState, INodeTypesState } from '../Interface'; + +const module: Module = { + namespaced: true, + state: { + nodeTypes: {}, + }, + getters: { + allNodeTypes: (state): INodeTypeDescription[] => { + return Object.values(state.nodeTypes); + }, + getNodeType: (state) => (nodeTypeName: string, version?: number): INodeTypeDescription | null => { + const nodeType = state.nodeTypes[nodeTypeName]; + + if (!nodeType || !hasValidVersion(nodeType, version)) return null; + + return nodeType; + }, + }, + mutations: { + setNodeTypes(state, nodeTypesArray: INodeTypeDescription[]) { + state.nodeTypes = toNodeTypesState(nodeTypesArray); + }, + + updateNodeTypes(state, newNodeTypes: INodeTypeDescription[]) { + newNodeTypes.forEach((node) => Vue.set(state.nodeTypes, node.name, node)); + }, + + removeNodeTypes(state, nodeTypesToRemove: INodeTypeDescription[]) { + state.nodeTypes = nodeTypesToRemove.reduce( + (oldNodes, newNodeType) => omit(newNodeType.name, oldNodes), + state.nodeTypes, + ); + }, + }, + actions: { + async getFullNodesProperties( + context: ActionContext, + nodesToBeFetched: INodeTypeNameVersion[], + ) { + await context.dispatch('credentials/fetchCredentialTypes', true); + + const nodesInformation = await context.dispatch( + 'getNodesInformation', + nodesToBeFetched, + ); + + context.commit('updateNodeTypes', nodesInformation); + }, + async getNodeTypes(context: ActionContext) { + const nodeTypes = await getNodeTypes(context.rootGetters.getRestApiContext); + if (nodeTypes.length) { + context.commit('setNodeTypes', nodeTypes); + } + }, + async getNodeTranslationHeaders(context: ActionContext) { + const headers = await getNodeTranslationHeaders(context.rootGetters.getRestApiContext); + + if (headers) { + addHeaders(headers, context.getters.defaultLocale); + } + }, + async getNodesInformation( + context: ActionContext, + nodeInfos: INodeTypeNameVersion[], + ) { + const nodesInformation = await getNodesInformation( + context.rootGetters.getRestApiContext, + nodeInfos, + ); + + nodesInformation.forEach(nodeInformation => { + if (nodeInformation.translation) { + const nodeType = nodeInformation.name.replace('n8n-nodes-base.', ''); + + addNodeTranslation( + { [nodeType]: nodeInformation.translation }, + context.getters.defaultLocale, + ); + } + }); + + context.commit('updateNodeTypes', nodesInformation); + }, + async getNodeParameterOptions( + context: ActionContext, + sendData: { + nodeTypeAndVersion: INodeTypeNameVersion, + path: string, + methodName?: string, + loadOptions?: ILoadOptions, + currentNodeParameters: INodeParameters, + credentials?: INodeCredentials, + }, + ) { + return getNodeParameterOptions(context.rootGetters.getRestApiContext, sendData); + }, + }, +}; + +function toNodeTypesState(nodeTypes: INodeTypeDescription[]) { + return nodeTypes.reduce((acc, cur) => { + acc[cur.name] = cur; + + return acc; + }, {}); +} + +function hasValidVersion(nodeType: INodeTypeDescription, version?: number) { + const nodeTypeVersion = Array.isArray(nodeType.version) + ? nodeType.version + : [nodeType.version]; + + return nodeTypeVersion.includes(version || nodeType.defaultVersion || DEFAULT_NODETYPE_VERSION); +} + +export default module; diff --git a/packages/editor-ui/src/store.ts b/packages/editor-ui/src/store.ts index eea0f5f192..ff2f9f30ce 100644 --- a/packages/editor-ui/src/store.ts +++ b/packages/editor-ui/src/store.ts @@ -35,6 +35,7 @@ import { ICommunityNodesState, } from './Interface'; +import nodeTypes from './modules/nodeTypes'; import credentials from './modules/credentials'; import settings from './modules/settings'; import tags from './modules/tags'; @@ -79,7 +80,6 @@ const state: IRootState = { lastSelectedNode: null, lastSelectedNodeOutputIndex: null, nodeIndex: [], - nodeTypes: [], nodeViewOffsetPosition: [0, 0], nodeViewMoveInProgress: false, selectedNodes: [], @@ -103,6 +103,7 @@ const state: IRootState = { }; const modules = { + nodeTypes, credentials, tags, settings, @@ -551,10 +552,6 @@ export const store = new Vuex.Store({ state.nodeViewOffsetPosition = data.newOffset; }, - // Node-Types - setNodeTypes(state, nodeTypes: INodeTypeDescription[]) { - Vue.set(state, 'nodeTypes', nodeTypes); - }, // Active Execution setExecutingNode(state, executingNode: string) { state.executingNode = executingNode; @@ -702,21 +699,6 @@ export const store = new Vuex.Store({ } }, - updateNodeTypes(state, nodeTypes: INodeTypeDescription[]) { - const oldNodesToKeep = state.nodeTypes.filter(node => !nodeTypes.find(n => n.name === node.name && n.version.toString() === node.version.toString())); - const newNodesState = [...oldNodesToKeep, ...nodeTypes]; - - Vue.set(state, 'nodeTypes', newNodesState); - state.nodeTypes = newNodesState; - }, - - removeNodeTypes (state, nodeTypes: INodeTypeDescription[]) { - console.log('Store will remove nodes: ', nodeTypes); // eslint-disable-line no-console - const oldNodesToKeep = state.nodeTypes.filter(node => !nodeTypes.find(n => n.name === node.name && n.version === node.version)); - Vue.set(state, 'nodeTypes', oldNodesToKeep); - state.nodeTypes = oldNodesToKeep; - }, - addSidebarMenuItems (state, menuItems: IMenuItem[]) { const updated = state.sidebarMenuItems.concat(menuItems); Vue.set(state, 'sidebarMenuItems', updated); @@ -834,7 +816,7 @@ export const store = new Vuex.Store({ workflowTriggerNodes: (state, getters) => { return state.workflow.nodes.filter(node => { - const nodeType = getters.nodeType(node.type, node.typeVersion); + const nodeType = getters['nodeTypes/getNodeType'](node.type, node.typeVersion); return nodeType && nodeType.group.includes('trigger'); }); }, @@ -901,9 +883,6 @@ export const store = new Vuex.Store({ } return false; }, - allNodeTypes: (state): INodeTypeDescription[] => { - return state.nodeTypes; - }, /** * Pin data */ @@ -929,8 +908,8 @@ export const store = new Vuex.Store({ * Getter for node default names ending with a number: `'S3'`, `'Magento 2'`, etc. */ nativelyNumberSuffixedDefaults: (_, getters): string[] => { - const {allNodeTypes} = getters as { - allNodeTypes: Array; + const { 'nodeTypes/allNodeTypes': allNodeTypes } = getters as { + ['nodeTypes/allNodeTypes']: Array; }; return allNodeTypes.reduce((acc, cur) => { @@ -938,21 +917,6 @@ export const store = new Vuex.Store({ return acc; }, []); }, - - nodeType: (state, getters) => (nodeType: string, version?: number): INodeTypeDescription | null => { - const foundType = state.nodeTypes.find(typeData => { - const typeVersion = Array.isArray(typeData.version) - ? typeData.version - : [typeData.version]; - - return typeData.name === nodeType && typeVersion.includes(version || typeData.defaultVersion || DEFAULT_NODETYPE_VERSION); - }); - - if (foundType === undefined) { - return null; - } - return foundType; - }, activeNode: (state, getters): INodeUi | null => { return getters.getNodeByName(state.activeNode); }, diff --git a/packages/editor-ui/src/utils.ts b/packages/editor-ui/src/utils.ts new file mode 100644 index 0000000000..a3c7487352 --- /dev/null +++ b/packages/editor-ui/src/utils.ts @@ -0,0 +1 @@ +export const omit = (keyToOmit: string, { [keyToOmit]: _, ...remainder }) => remainder; diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 6374ab0abb..0ed00bdb20 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -1480,7 +1480,7 @@ export default mixins( }); }, async injectNode (nodeTypeName: string, options: AddNodeOptions = {}) { - const nodeTypeData: INodeTypeDescription | null = this.$store.getters.nodeType(nodeTypeName); + const nodeTypeData: INodeTypeDescription | null = this.$store.getters['nodeTypes/getNodeType'](nodeTypeName); if (nodeTypeData === null) { this.$showMessage({ @@ -1534,7 +1534,7 @@ export default mixins( let yOffset = 0; if (lastSelectedConnection) { - const sourceNodeType = this.$store.getters.nodeType(lastSelectedNode.type, lastSelectedNode.typeVersion) as INodeTypeDescription | null; + const sourceNodeType = this.$store.getters['nodeTypes/getNodeType'](lastSelectedNode.type, lastSelectedNode.typeVersion) as INodeTypeDescription | null; const offsets = [[-100, 100], [-140, 0, 140], [-240, -100, 100, 240]]; if (sourceNodeType && sourceNodeType.outputs.length > 1) { const offset = offsets[sourceNodeType.outputs.length - 2]; @@ -1937,7 +1937,7 @@ export default mixins( const nodeName = (element as HTMLElement).dataset['name'] as string; const node = this.$store.getters.getNodeByName(nodeName) as INodeUi | null; if (node) { - const nodeType = this.$store.getters.nodeType(node.type, node.typeVersion) as INodeTypeDescription | null; + const nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null; if (nodeType && nodeType.inputs && nodeType.inputs.length === 1) { this.pullConnActiveNodeName = node.name; const endpoint = this.instance.getEndpoint(this.getInputEndpointUUID(nodeName, 0)); @@ -2199,7 +2199,7 @@ export default mixins( const node = this.$store.getters.getNodeByName(nodeName); - const nodeTypeData: INodeTypeDescription | null= this.$store.getters.nodeType(node.type, node.typeVersion); + const nodeTypeData: INodeTypeDescription | null= this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion); if (nodeTypeData && nodeTypeData.maxNodes !== undefined && this.getNodeTypeCount(node.type) >= nodeTypeData.maxNodes) { this.showMaxNodeTypeError(nodeTypeData); return; @@ -2411,7 +2411,7 @@ export default mixins( let waitForNewConnection = false; // connect nodes before/after deleted node - const nodeType: INodeTypeDescription | null = this.$store.getters.nodeType(node.type, node.typeVersion); + const nodeType: INodeTypeDescription | null = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion); if (nodeType && nodeType.outputs.length === 1 && nodeType.inputs.length === 1) { const {incoming, outgoing} = this.getIncomingOutgoingConnections(node.name); @@ -2621,7 +2621,7 @@ export default mixins( let nodeType: INodeTypeDescription | null; let foundNodeIssues: INodeIssues | null; nodes.forEach((node) => { - nodeType = this.$store.getters.nodeType(node.type, node.typeVersion) as INodeTypeDescription | null; + nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null; // Make sure that some properties always exist if (!node.hasOwnProperty('disabled')) { @@ -2931,8 +2931,7 @@ export default mixins( this.$store.commit('setActiveWorkflows', activeWorkflows); }, async loadNodeTypes (): Promise { - const nodeTypes = await this.restApi().getNodeTypes(); - this.$store.commit('setNodeTypes', nodeTypes); + this.$store.dispatch('nodeTypes/getNodeTypes'); }, async loadCredentialTypes (): Promise { await this.$store.dispatch('credentials/fetchCredentialTypes', true); @@ -2941,7 +2940,7 @@ export default mixins( await this.$store.dispatch('credentials/fetchAllCredentials'); }, async loadNodesProperties(nodeInfos: INodeTypeNameVersion[]): Promise { - const allNodes:INodeTypeDescription[] = this.$store.getters.allNodeTypes; + const allNodes:INodeTypeDescription[] = this.$store.getters['nodeTypes/allNodeTypes']; const nodesToBeFetched:INodeTypeNameVersion[] = []; allNodes.forEach(node => { @@ -2959,21 +2958,7 @@ export default mixins( if (nodesToBeFetched.length > 0) { // Only call API if node information is actually missing this.startLoading(); - - const nodesInfo = await this.restApi().getNodesInformation(nodesToBeFetched); - - nodesInfo.forEach(nodeInfo => { - if (nodeInfo.translation) { - const nodeType = this.$locale.shortNodeType(nodeInfo.name); - - addNodeTranslation( - { [nodeType]: nodeInfo.translation }, - this.$store.getters.defaultLocale, - ); - } - }); - - this.$store.commit('updateNodeTypes', nodesInfo); + await this.$store.dispatch('nodeTypes/getNodesInformation', nodesToBeFetched); this.stopLoading(); } },