refactor(editor): Move nodeTypes into store module (#3799)

*  Refactor `nodeTypes` into store module

*  Fix condition

* 🔥 Remove leftovers

*  Move `getNodeTranslationHeaders`, `getNodesInformation`, `getNodeParameterOptions`

*  Move leftover call

*  Correct excess prefix

* 🚚 Rename `nodeType` to `getNodeType`

* 🚚 Move logic to `getFullNodesProperties`

*  Simplify `getNodeType`

*  Refactor `nodeTypes` mutations

*  Refactor `Vue.set` call

*  Simplify check

* 🚚 Move export to bottom

* 📘 Simplify typing

* 🔥 Remove unused interface

* 👕 Add `void`

* 🚚 Fix naming

* 🔥 Remove logging

*  Simplify `updateNodeTypes`

* 🚚 Move `omit` to utils

* 🐛 Update `rootGetters` call

* 🐛 Fix `allNodeTypes` call in `nativelyNumberSuffixedDefaults`

* 🔥 Remove unused method

* 🔥 Remove excess namespace

Co-authored-by: Mutasem <mutdmour@gmail.com>
This commit is contained in:
Iván Ovejero 2022-08-01 22:43:50 +02:00 committed by GitHub
parent 231cfaa24d
commit 2c17e6f3ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 244 additions and 140 deletions

View file

@ -160,8 +160,7 @@ export default mixins(
this.$externalHooks().run('app.mount'); this.$externalHooks().run('app.mount');
if (this.defaultLocale !== 'en') { if (this.defaultLocale !== 'en') {
const headers = await this.restApi().getNodeTranslationHeaders(); void this.$store.dispatch('nodeTypes/getNodeTranslationHeaders');
if (headers) addHeaders(headers, this.defaultLocale);
} }
}, },
watch: { watch: {

View file

@ -173,10 +173,6 @@ export interface IRestApi {
stopCurrentExecution(executionId: string): Promise<IExecutionsStopData>; stopCurrentExecution(executionId: string): Promise<IExecutionsStopData>;
makeRestApiRequest(method: string, endpoint: string, data?: any): Promise<any>; // tslint:disable-line:no-any makeRestApiRequest(method: string, endpoint: string, data?: any): Promise<any>; // tslint:disable-line:no-any
getCredentialTranslation(credentialType: string): Promise<object>; getCredentialTranslation(credentialType: string): Promise<object>;
getNodeTranslationHeaders(): Promise<INodeTranslationHeaders>;
getNodeTypes(onlyLatest?: boolean): Promise<INodeTypeDescription[]>;
getNodesInformation(nodeInfos: INodeTypeNameVersion[]): Promise<INodeTypeDescription[]>;
getNodeParameterOptions(sendData: { nodeTypeAndVersion: INodeTypeNameVersion, path: string, methodName?: string, loadOptions?: ILoadOptions, currentNodeParameters: INodeParameters, credentials?: INodeCredentials }): Promise<INodePropertyOptions[]> ;
removeTestWebhook(workflowId: string): Promise<boolean>; removeTestWebhook(workflowId: string): Promise<boolean>;
runWorkflow(runData: IStartRunData): Promise<IExecutionPushResponse>; runWorkflow(runData: IStartRunData): Promise<IExecutionPushResponse>;
createNewWorkflow(sendData: IWorkflowDataUpdate): Promise<IWorkflowDb>; createNewWorkflow(sendData: IWorkflowDataUpdate): Promise<IWorkflowDb>;
@ -853,7 +849,6 @@ export interface IRootState {
lastSelectedNode: string | null; lastSelectedNode: string | null;
lastSelectedNodeOutputIndex: number | null; lastSelectedNodeOutputIndex: number | null;
nodeIndex: Array<string | null>; nodeIndex: Array<string | null>;
nodeTypes: INodeTypeDescription[];
nodeViewOffsetPosition: XYPosition; nodeViewOffsetPosition: XYPosition;
nodeViewMoveInProgress: boolean; nodeViewMoveInProgress: boolean;
selectedNodes: INodeUi[]; selectedNodes: INodeUi[];
@ -959,6 +954,10 @@ export interface ISettingsState {
onboardingCallPromptEnabled: boolean; onboardingCallPromptEnabled: boolean;
} }
export interface INodeTypesState {
nodeTypes: { [nodeType: string]: INodeTypeDescription };
}
export interface ITemplateState { export interface ITemplateState {
categories: {[id: string]: ITemplatesCategory}; categories: {[id: string]: ITemplatesCategory};
collections: {[id: string]: ITemplatesCollection}; collections: {[id: string]: ITemplatesCollection};

View file

@ -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<INodeTranslationHeaders | undefined> {
return makeRestApiRequest(context, 'GET', '/node-translation-headers');
}
export async function getNodesInformation(
context: IRestApiContext,
nodeInfos: INodeTypeNameVersion[],
): Promise<INodeTypeDescription[]> {
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<INodePropertyOptions[]> {
return makeRestApiRequest(context, 'GET', '/node-parameter-options', sendData);
}

View file

@ -80,7 +80,7 @@ export default Vue.extend({
const trigger = foundTriggers[0]; 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) { if (triggerNodeType.activationMessage) {
return triggerNodeType.activationMessage; return triggerNodeType.activationMessage;
} }

View file

@ -193,16 +193,6 @@ export default mixins(restApi).extend({
}, },
}, },
methods: { 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 { onDataChange (event: { name: string; value: string | number | boolean | Date | null }): void {
this.$emit('change', event); this.$emit('change', event);
}, },

View file

@ -34,7 +34,7 @@ export default Vue.extend({
if (this.credentialWithIcon && this.credentialWithIcon.icon && this.credentialWithIcon.icon.startsWith('node:')) { if (this.credentialWithIcon && this.credentialWithIcon.icon && this.credentialWithIcon.icon.startsWith('node:')) {
const nodeType = this.credentialWithIcon.icon.replace('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); const nodesWithAccess = this.$store.getters['credentials/getNodesWithAccess'](this.credentialTypeName);

View file

@ -129,7 +129,7 @@ export default mixins(
if (!node) { if (!node) {
return []; 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) { if (nodeType === null) {
return []; return [];

View file

@ -172,7 +172,7 @@ export default mixins(
}, },
isSingleActiveTriggerNode (): boolean { isSingleActiveTriggerNode (): boolean {
const nodes = this.$store.getters.workflowTriggerNodes.filter((node: INodeUi) => { 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; return nodeType && nodeType.eventTriggerDescription !== '' && !node.disabled;
}); });
@ -188,7 +188,7 @@ export default mixins(
return this.node && this.node.disabled; return this.node && this.node.disabled;
}, },
nodeType (): INodeTypeDescription | null { 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.. node (): INodeUi | undefined { // same as this.data but reactive..
return this.$store.getters.nodesByName[this.name] as INodeUi | undefined; return this.$store.getters.nodesByName[this.name] as INodeUi | undefined;

View file

@ -50,7 +50,7 @@ export default Vue.extend({
computed: { computed: {
...mapGetters('users', ['personalizedNodeTypes']), ...mapGetters('users', ['personalizedNodeTypes']),
nodeTypes(): INodeTypeDescription[] { nodeTypes(): INodeTypeDescription[] {
return this.$store.getters.allNodeTypes; return this.$store.getters['nodeTypes/allNodeTypes'];
}, },
visibleNodeTypes(): INodeTypeDescription[] { visibleNodeTypes(): INodeTypeDescription[] {
return this.allNodeTypes return this.allNodeTypes

View file

@ -117,7 +117,7 @@ export default mixins(
if (credType) return [credType]; 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) { if (activeNodeType && activeNodeType.credentials) {
return activeNodeType.credentials; return activeNodeType.credentials;
} }

View file

@ -209,7 +209,7 @@ export default mixins(
}, },
activeNodeType(): INodeTypeDescription | null { activeNodeType(): INodeTypeDescription | null {
if (this.activeNode) { 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; return null;
}, },

View file

@ -59,7 +59,7 @@ export default mixins(
}, },
nodeType (): INodeTypeDescription | null { nodeType (): INodeTypeDescription | null {
if (this.node) { 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; return null;
}, },

View file

@ -146,7 +146,7 @@ export default mixins(
computed: { computed: {
nodeType (): INodeTypeDescription | null { nodeType (): INodeTypeDescription | null {
if (this.node) { 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; return null;
@ -446,7 +446,7 @@ export default mixins(
} else if (parameterData.name.startsWith('parameters.')) { } else if (parameterData.name.startsWith('parameters.')) {
// A node parameter changed // 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) { if (!nodeType) {
return; return;
} }

View file

@ -114,7 +114,7 @@ export default mixins(
}, },
nodeType (): INodeTypeDescription | null { nodeType (): INodeTypeDescription | null {
if (this.node) { 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; return null;
}, },

View file

@ -738,7 +738,20 @@ export default mixins(
const loadOptionsMethod = this.getArgument('loadOptionsMethod') as string | undefined; const loadOptionsMethod = this.getArgument('loadOptionsMethod') as string | undefined;
const loadOptions = this.getArgument('loadOptions') as ILoadOptions | 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); this.remoteParameterOptions.push.apply(this.remoteParameterOptions, options);
} catch (error) { } catch (error) {
this.remoteParameterOptionsLoadingIssues = error.message; this.remoteParameterOptionsLoadingIssues = error.message;

View file

@ -153,7 +153,7 @@ export default mixins(
methods: { methods: {
getCredentialsDependencies() { getCredentialsDependencies() {
const dependencies = new Set(); 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) // Get names of all fields that credentials rendering depends on (using displayOptions > show)
if(nodeType && nodeType.credentials) { if(nodeType && nodeType.credentials) {

View file

@ -507,7 +507,7 @@ export default mixins(
}, },
nodeType (): INodeTypeDescription | null { nodeType (): INodeTypeDescription | null {
if (this.node) { 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; return null;
}, },

View file

@ -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); return this.$store.getters.getSelectedNodes.find((node: INodeUi) => node.name === this.data.name);
}, },
nodeType (): INodeTypeDescription | null { 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.. node (): INodeUi | undefined { // same as this.data but reactive..
return this.$store.getters.nodesByName[this.name] as INodeUi | undefined; return this.$store.getters.nodesByName[this.name] as INodeUi | undefined;

View file

@ -125,7 +125,7 @@ export default mixins(workflowHelpers, copyPaste, showMessage).extend({
}, },
nodeType(): INodeTypeDescription | null { nodeType(): INodeTypeDescription | null {
if (this.node) { 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; return null;

View file

@ -287,10 +287,10 @@ export const nodeBase = mixins(
}); });
}, },
__addNode (node: INodeUi) { __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 (!nodeTypeData) {
// If node type is not know use by default the base.noOp data to display it // 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); this.__addInputEndpoints(node, nodeTypeData);

View file

@ -190,7 +190,7 @@ export const nodeHelpers = mixins(
// Updates the parameter-issues of the node // Updates the parameter-issues of the node
updateNodeParameterIssues(node: INodeUi, nodeType?: INodeTypeDescription): void { updateNodeParameterIssues(node: INodeUi, nodeType?: INodeTypeDescription): void {
if (nodeType === undefined) { 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) { if (nodeType === null) {
@ -221,7 +221,7 @@ export const nodeHelpers = mixins(
} }
if (nodeType === undefined) { 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) { if (nodeType === null || nodeType!.credentials === undefined) {

View file

@ -272,7 +272,7 @@ export const pushConnection = mixins(
const execution = this.$store.getters.getWorkflowExecution; const execution = this.$store.getters.getWorkflowExecution;
if (execution && execution.executedNode) { if (execution && execution.executedNode) {
const node = this.$store.getters.getNodeByName(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]; const nodeOutput = execution && execution.executedNode && execution.data && execution.data.resultData && execution.data.resultData.runData && execution.data.resultData.runData[execution.executedNode];
if (node && nodeType && !nodeOutput) { if (node && nodeType && !nodeOutput) {
this.$showMessage({ this.$showMessage({
@ -370,19 +370,7 @@ export const pushConnection = mixins(
this.processWaitingPushMessages(); this.processWaitingPushMessages();
} else if (receivedData.type === 'reloadNodeType') { } else if (receivedData.type === 'reloadNodeType') {
const pushData = receivedData.data; this.$store.dispatch('nodeTypes/getFullNodesProperties', [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);
});
} else if (receivedData.type === 'removeNodeType') { } else if (receivedData.type === 'removeNodeType') {
const pushData = receivedData.data; const pushData = receivedData.data;
@ -391,7 +379,7 @@ export const pushConnection = mixins(
// Force reload of all credential types // Force reload of all credential types
this.$store.dispatch('credentials/fetchCredentialTypes') this.$store.dispatch('credentials/fetchCredentialTypes')
.then(() => { .then(() => {
this.$store.commit('removeNodeTypes', nodesToBeRemoved); this.$store.commit('nodeTypes/removeNodeTypes', nodesToBeRemoved);
}); });
} }
return true; return true;

View file

@ -84,24 +84,6 @@ export const restApi = Vue.extend({
return self.restApi().makeRestApiRequest('GET', '/credential-translation', { credentialType }); return self.restApi().makeRestApiRequest('GET', '/credential-translation', { credentialType });
}, },
getNodeTranslationHeaders: (): Promise<INodeTranslationHeaders> => {
return self.restApi().makeRestApiRequest('GET', '/node-translation-headers');
},
// Returns all node-types
getNodeTypes: (onlyLatest = false): Promise<INodeTypeDescription[]> => {
return self.restApi().makeRestApiRequest('GET', `/node-types`, {onlyLatest});
},
getNodesInformation: (nodeInfos: INodeTypeNameVersion[]): Promise<INodeTypeDescription[]> => {
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<INodePropertyOptions[]> => {
return self.restApi().makeRestApiRequest('GET', '/node-parameter-options', sendData);
},
// Removes a test webhook // Removes a test webhook
removeTestWebhook: (workflowId: string): Promise<boolean> => { removeTestWebhook: (workflowId: string): Promise<boolean> => {
return self.restApi().makeRestApiRequest('DELETE', `/test-webhook/${workflowId}`); return self.restApi().makeRestApiRequest('DELETE', `/test-webhook/${workflowId}`);

View file

@ -188,7 +188,7 @@ export const workflowHelpers = mixins(
const returnData: INodeTypesMaxCount = {}; const returnData: INodeTypesMaxCount = {};
const nodeTypes = this.$store.getters.allNodeTypes; const nodeTypes = this.$store.getters['nodeTypes/allNodeTypes'];
for (const nodeType of nodeTypes) { for (const nodeType of nodeTypes) {
if (nodeType.maxNodes !== undefined) { if (nodeType.maxNodes !== undefined) {
returnData[nodeType.name] = { returnData[nodeType.name] = {
@ -298,7 +298,7 @@ export const workflowHelpers = mixins(
return []; return [];
}, },
getByNameAndVersion: (nodeType: string, version?: number): INodeType | undefined => { 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) { if (nodeTypeDescription === null) {
return undefined; return undefined;
@ -406,7 +406,7 @@ export const workflowHelpers = mixins(
// Get the data of the node type that we can get the default values // 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 // 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) { if (nodeType !== null) {
// Node-Type is known so we can save the parameters correctly // Node-Type is known so we can save the parameters correctly

View file

@ -104,7 +104,7 @@ const module: Module<ICredentialsState, IRootState> = {
}, },
getNodesWithAccess (state: ICredentialsState, getters: any, rootState: IRootState, rootGetters: any) { // tslint:disable-line:no-any getNodesWithAccess (state: ICredentialsState, getters: any, rootState: IRootState, rootGetters: any) { // tslint:disable-line:no-any
return (credentialTypeName: string) => { return (credentialTypeName: string) => {
const nodeTypes: INodeTypeDescription[] = rootGetters.allNodeTypes; const nodeTypes: INodeTypeDescription[] = rootGetters['nodeTypes/allNodeTypes'];
return nodeTypes.filter((nodeType: INodeTypeDescription) => { return nodeTypes.filter((nodeType: INodeTypeDescription) => {
if (!nodeType.credentials) { if (!nodeType.credentials) {

View file

@ -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<INodeTypesState, IRootState> = {
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<INodeTypesState, IRootState>,
nodesToBeFetched: INodeTypeNameVersion[],
) {
await context.dispatch('credentials/fetchCredentialTypes', true);
const nodesInformation = await context.dispatch(
'getNodesInformation',
nodesToBeFetched,
);
context.commit('updateNodeTypes', nodesInformation);
},
async getNodeTypes(context: ActionContext<INodeTypesState, IRootState>) {
const nodeTypes = await getNodeTypes(context.rootGetters.getRestApiContext);
if (nodeTypes.length) {
context.commit('setNodeTypes', nodeTypes);
}
},
async getNodeTranslationHeaders(context: ActionContext<INodeTypesState, IRootState>) {
const headers = await getNodeTranslationHeaders(context.rootGetters.getRestApiContext);
if (headers) {
addHeaders(headers, context.getters.defaultLocale);
}
},
async getNodesInformation(
context: ActionContext<INodeTypesState, IRootState>,
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<INodeTypesState, IRootState>,
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<INodeTypesState['nodeTypes']>((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;

View file

@ -35,6 +35,7 @@ import {
ICommunityNodesState, ICommunityNodesState,
} from './Interface'; } from './Interface';
import nodeTypes from './modules/nodeTypes';
import credentials from './modules/credentials'; import credentials from './modules/credentials';
import settings from './modules/settings'; import settings from './modules/settings';
import tags from './modules/tags'; import tags from './modules/tags';
@ -79,7 +80,6 @@ const state: IRootState = {
lastSelectedNode: null, lastSelectedNode: null,
lastSelectedNodeOutputIndex: null, lastSelectedNodeOutputIndex: null,
nodeIndex: [], nodeIndex: [],
nodeTypes: [],
nodeViewOffsetPosition: [0, 0], nodeViewOffsetPosition: [0, 0],
nodeViewMoveInProgress: false, nodeViewMoveInProgress: false,
selectedNodes: [], selectedNodes: [],
@ -103,6 +103,7 @@ const state: IRootState = {
}; };
const modules = { const modules = {
nodeTypes,
credentials, credentials,
tags, tags,
settings, settings,
@ -551,10 +552,6 @@ export const store = new Vuex.Store({
state.nodeViewOffsetPosition = data.newOffset; state.nodeViewOffsetPosition = data.newOffset;
}, },
// Node-Types
setNodeTypes(state, nodeTypes: INodeTypeDescription[]) {
Vue.set(state, 'nodeTypes', nodeTypes);
},
// Active Execution // Active Execution
setExecutingNode(state, executingNode: string) { setExecutingNode(state, executingNode: string) {
state.executingNode = executingNode; 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[]) { addSidebarMenuItems (state, menuItems: IMenuItem[]) {
const updated = state.sidebarMenuItems.concat(menuItems); const updated = state.sidebarMenuItems.concat(menuItems);
Vue.set(state, 'sidebarMenuItems', updated); Vue.set(state, 'sidebarMenuItems', updated);
@ -834,7 +816,7 @@ export const store = new Vuex.Store({
workflowTriggerNodes: (state, getters) => { workflowTriggerNodes: (state, getters) => {
return state.workflow.nodes.filter(node => { 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'); return nodeType && nodeType.group.includes('trigger');
}); });
}, },
@ -901,9 +883,6 @@ export const store = new Vuex.Store({
} }
return false; return false;
}, },
allNodeTypes: (state): INodeTypeDescription[] => {
return state.nodeTypes;
},
/** /**
* Pin data * 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. * Getter for node default names ending with a number: `'S3'`, `'Magento 2'`, etc.
*/ */
nativelyNumberSuffixedDefaults: (_, getters): string[] => { nativelyNumberSuffixedDefaults: (_, getters): string[] => {
const {allNodeTypes} = getters as { const { 'nodeTypes/allNodeTypes': allNodeTypes } = getters as {
allNodeTypes: Array<INodeTypeDescription & { defaults: { name: string } }>; ['nodeTypes/allNodeTypes']: Array<INodeTypeDescription & { defaults: { name: string } }>;
}; };
return allNodeTypes.reduce<string[]>((acc, cur) => { return allNodeTypes.reduce<string[]>((acc, cur) => {
@ -938,21 +917,6 @@ export const store = new Vuex.Store({
return acc; 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 => { activeNode: (state, getters): INodeUi | null => {
return getters.getNodeByName(state.activeNode); return getters.getNodeByName(state.activeNode);
}, },

View file

@ -0,0 +1 @@
export const omit = (keyToOmit: string, { [keyToOmit]: _, ...remainder }) => remainder;

View file

@ -1480,7 +1480,7 @@ export default mixins(
}); });
}, },
async injectNode (nodeTypeName: string, options: AddNodeOptions = {}) { 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) { if (nodeTypeData === null) {
this.$showMessage({ this.$showMessage({
@ -1534,7 +1534,7 @@ export default mixins(
let yOffset = 0; let yOffset = 0;
if (lastSelectedConnection) { 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]]; const offsets = [[-100, 100], [-140, 0, 140], [-240, -100, 100, 240]];
if (sourceNodeType && sourceNodeType.outputs.length > 1) { if (sourceNodeType && sourceNodeType.outputs.length > 1) {
const offset = offsets[sourceNodeType.outputs.length - 2]; const offset = offsets[sourceNodeType.outputs.length - 2];
@ -1937,7 +1937,7 @@ export default mixins(
const nodeName = (element as HTMLElement).dataset['name'] as string; const nodeName = (element as HTMLElement).dataset['name'] as string;
const node = this.$store.getters.getNodeByName(nodeName) as INodeUi | null; const node = this.$store.getters.getNodeByName(nodeName) as INodeUi | null;
if (node) { 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) { if (nodeType && nodeType.inputs && nodeType.inputs.length === 1) {
this.pullConnActiveNodeName = node.name; this.pullConnActiveNodeName = node.name;
const endpoint = this.instance.getEndpoint(this.getInputEndpointUUID(nodeName, 0)); const endpoint = this.instance.getEndpoint(this.getInputEndpointUUID(nodeName, 0));
@ -2199,7 +2199,7 @@ export default mixins(
const node = this.$store.getters.getNodeByName(nodeName); 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) { if (nodeTypeData && nodeTypeData.maxNodes !== undefined && this.getNodeTypeCount(node.type) >= nodeTypeData.maxNodes) {
this.showMaxNodeTypeError(nodeTypeData); this.showMaxNodeTypeError(nodeTypeData);
return; return;
@ -2411,7 +2411,7 @@ export default mixins(
let waitForNewConnection = false; let waitForNewConnection = false;
// connect nodes before/after deleted node // 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 if (nodeType && nodeType.outputs.length === 1
&& nodeType.inputs.length === 1) { && nodeType.inputs.length === 1) {
const {incoming, outgoing} = this.getIncomingOutgoingConnections(node.name); const {incoming, outgoing} = this.getIncomingOutgoingConnections(node.name);
@ -2621,7 +2621,7 @@ export default mixins(
let nodeType: INodeTypeDescription | null; let nodeType: INodeTypeDescription | null;
let foundNodeIssues: INodeIssues | null; let foundNodeIssues: INodeIssues | null;
nodes.forEach((node) => { 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 // Make sure that some properties always exist
if (!node.hasOwnProperty('disabled')) { if (!node.hasOwnProperty('disabled')) {
@ -2931,8 +2931,7 @@ export default mixins(
this.$store.commit('setActiveWorkflows', activeWorkflows); this.$store.commit('setActiveWorkflows', activeWorkflows);
}, },
async loadNodeTypes (): Promise<void> { async loadNodeTypes (): Promise<void> {
const nodeTypes = await this.restApi().getNodeTypes(); this.$store.dispatch('nodeTypes/getNodeTypes');
this.$store.commit('setNodeTypes', nodeTypes);
}, },
async loadCredentialTypes (): Promise<void> { async loadCredentialTypes (): Promise<void> {
await this.$store.dispatch('credentials/fetchCredentialTypes', true); await this.$store.dispatch('credentials/fetchCredentialTypes', true);
@ -2941,7 +2940,7 @@ export default mixins(
await this.$store.dispatch('credentials/fetchAllCredentials'); await this.$store.dispatch('credentials/fetchAllCredentials');
}, },
async loadNodesProperties(nodeInfos: INodeTypeNameVersion[]): Promise<void> { async loadNodesProperties(nodeInfos: INodeTypeNameVersion[]): Promise<void> {
const allNodes:INodeTypeDescription[] = this.$store.getters.allNodeTypes; const allNodes:INodeTypeDescription[] = this.$store.getters['nodeTypes/allNodeTypes'];
const nodesToBeFetched:INodeTypeNameVersion[] = []; const nodesToBeFetched:INodeTypeNameVersion[] = [];
allNodes.forEach(node => { allNodes.forEach(node => {
@ -2959,21 +2958,7 @@ export default mixins(
if (nodesToBeFetched.length > 0) { if (nodesToBeFetched.length > 0) {
// Only call API if node information is actually missing // Only call API if node information is actually missing
this.startLoading(); this.startLoading();
await this.$store.dispatch('nodeTypes/getNodesInformation', nodesToBeFetched);
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);
this.stopLoading(); this.stopLoading();
} }
}, },