mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
✨ Updated node design and node versioning (#1961)
* ⚡ introduce versioned nodes * Export versioned nodes for separate process run * Add bse node for versioned nodes * fix node name for versioned nodes * extend node from nodeVersionedType * improve nodes base and flow to FE * revert lib es2019 to es2017 * include version in key to prevent duplicate key * handle type versions on FE * clean up * cleanup nodes base * add type versions in getNodeParameterOptions * cleanup * code review * code review + add default version to node type description * remove node default types from store * 💄 cleanups * Draft for migrated Mattermost node * First version of Mattermost node versioned according to node standards * Correcting deactivate operations name to match currently used one * ✨ Create utility types * ⚡ Simplify Mattermost types * ⚡ Rename exports for consistency * ⚡ Type channel properties * ⚡ Type message properties * ⚡ Type reaction properties * ⚡ Type user properties * ⚡ Add type import to router * 🐛 Add missing key * 🔨 Adjust typo in operation name * 🔨 Inline exports for channel properties * 🔨 Inline exports for message properties * 🔨 Inline exports for reaction properties * 🔨 Inline exports for user properties * 🔨 Inline exports for load options * 👕 Fix lint issue * 🔨 Inline export for description * 🔨 Rename descriptions for clarity * 🔨 Refactor imports/exports for methods * 🔨 Refactor latest version retrieval * 🔥 Remove unneeded else clause When the string literal union is exhausted, the resource key becomes never, so TS disallows wrong key usage. * ✨ Add overloads to getNodeParameter * ⚡ Improve overload * 🔥 Remove superfluous INodeVersions type * 🔨 Relocate pre-existing interface * 🔥 Remove JSDoc arg descriptions * ⚡ Minor reformatting in transport file * ⚡ Fix API call function type * Created first draft for Axios requests * Working version of mattermost node with Axios * Work in progress for replacing request library * Improvements to request translations * Fixed sending files via multipart / form-data * Fixing translation from request to axios and loading node parameter options * Improved typing for new http helper * Added ignore any for specific lines for linting * Fixed follow redirects changes on http request node and manual execution of previously existing workflow with older node versions * Adding default headers according to body on httpRequest helper * Spec error handling and fixed workflows with older node versions * Showcase how to export errors in a standard format * Merging master * Refactored mattermost node to keep files in a uniform structure. Also fix bugs with merges * Reverting changes to http request node * Changed nullish comparison and removed repeated code from nodes * Renamed queryString back to qs and simplified node output * Simplified some comparisons * Changed header names to be uc first * Added default user agent to requests and patch http method support * Fixed indentation, remove unnecessary file and console log * Fixed mattermost node name * Fixed lint issues * Further fix linting issues * Further fix lint issues * Fixed http request helper's return type Co-authored-by: ahsan-virani <ahsan.virani@gmail.com> Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
This commit is contained in:
parent
53fbf664b5
commit
443c2a4d51
|
@ -170,6 +170,7 @@ export class ExecuteBatch extends Command {
|
||||||
'missing a required parameter',
|
'missing a required parameter',
|
||||||
'insufficient credit balance',
|
'insufficient credit balance',
|
||||||
'request timed out',
|
'request timed out',
|
||||||
|
'status code 401',
|
||||||
];
|
];
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
|
|
@ -30,6 +30,9 @@ const mockNodeTypes: INodeTypes = {
|
||||||
getByName: (nodeType: string): INodeType | undefined => {
|
getByName: (nodeType: string): INodeType | undefined => {
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
getByNameAndVersion: (): INodeType | undefined => {
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export class CredentialsHelper extends ICredentialsHelper {
|
export class CredentialsHelper extends ICredentialsHelper {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
ILogger,
|
ILogger,
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeTypeData,
|
INodeTypeData,
|
||||||
|
INodeVersionedType,
|
||||||
LoggerProxy,
|
LoggerProxy,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
@ -181,13 +182,14 @@ class LoadNodesAndCredentialsClass {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async loadNodeFromFile(packageName: string, nodeName: string, filePath: string): Promise<void> {
|
async loadNodeFromFile(packageName: string, nodeName: string, filePath: string): Promise<void> {
|
||||||
let tempNode: INodeType;
|
let tempNode: INodeType | INodeVersionedType;
|
||||||
let fullNodeName: string;
|
let fullNodeName: string;
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires
|
// eslint-disable-next-line import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires
|
||||||
const tempModule = require(filePath);
|
const tempModule = require(filePath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
tempNode = new tempModule[nodeName]() as INodeType;
|
tempNode = new tempModule[nodeName]();
|
||||||
this.addCodex({ node: tempNode, filePath, isCustom: packageName === 'CUSTOM' });
|
this.addCodex({ node: tempNode, filePath, isCustom: packageName === 'CUSTOM' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -207,13 +209,36 @@ class LoadNodesAndCredentialsClass {
|
||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tempNode.executeSingle) {
|
if (tempNode.hasOwnProperty('executeSingle')) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`"executeSingle" will get deprecated soon. Please update the code of node "${packageName}.${nodeName}" to use "execute" instead!`,
|
`"executeSingle" will get deprecated soon. Please update the code of node "${packageName}.${nodeName}" to use "execute" instead!`,
|
||||||
{ filePath },
|
{ filePath },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tempNode.hasOwnProperty('nodeVersions')) {
|
||||||
|
const versionedNodeType = (tempNode as INodeVersionedType).getNodeType();
|
||||||
|
this.addCodex({ node: versionedNodeType, filePath, isCustom: packageName === 'CUSTOM' });
|
||||||
|
|
||||||
|
if (
|
||||||
|
versionedNodeType.description.icon !== undefined &&
|
||||||
|
versionedNodeType.description.icon.startsWith('file:')
|
||||||
|
) {
|
||||||
|
// If a file icon gets used add the full path
|
||||||
|
versionedNodeType.description.icon = `file:${path.join(
|
||||||
|
path.dirname(filePath),
|
||||||
|
versionedNodeType.description.icon.substr(5),
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (versionedNodeType.hasOwnProperty('executeSingle')) {
|
||||||
|
this.logger.warn(
|
||||||
|
`"executeSingle" will get deprecated soon. Please update the code of node "${packageName}.${nodeName}" to use "execute" instead!`,
|
||||||
|
{ filePath },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.includeNodes !== undefined && !this.includeNodes.includes(fullNodeName)) {
|
if (this.includeNodes !== undefined && !this.includeNodes.includes(fullNodeName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -257,7 +282,15 @@ class LoadNodesAndCredentialsClass {
|
||||||
* @param obj.isCustom Whether the node is custom
|
* @param obj.isCustom Whether the node is custom
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
addCodex({ node, filePath, isCustom }: { node: INodeType; filePath: string; isCustom: boolean }) {
|
addCodex({
|
||||||
|
node,
|
||||||
|
filePath,
|
||||||
|
isCustom,
|
||||||
|
}: {
|
||||||
|
node: INodeType | INodeVersionedType;
|
||||||
|
filePath: string;
|
||||||
|
isCustom: boolean;
|
||||||
|
}) {
|
||||||
try {
|
try {
|
||||||
const codex = this.getCodex(filePath);
|
const codex = this.getCodex(filePath);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
import { INodeType, INodeTypeData, INodeTypes, NodeHelpers } from 'n8n-workflow';
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
import {
|
||||||
|
INodeType,
|
||||||
|
INodeTypeData,
|
||||||
|
INodeTypes,
|
||||||
|
INodeVersionedType,
|
||||||
|
NodeHelpers,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
class NodeTypesClass implements INodeTypes {
|
class NodeTypesClass implements INodeTypes {
|
||||||
nodeTypes: INodeTypeData = {};
|
nodeTypes: INodeTypeData = {};
|
||||||
|
@ -8,29 +18,30 @@ class NodeTypesClass implements INodeTypes {
|
||||||
// polling nodes the polling times
|
// polling nodes the polling times
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const nodeTypeData of Object.values(nodeTypes)) {
|
for (const nodeTypeData of Object.values(nodeTypes)) {
|
||||||
const applyParameters = NodeHelpers.getSpecialNodeParameters(nodeTypeData.type);
|
const nodeType = NodeHelpers.getVersionedTypeNode(nodeTypeData.type);
|
||||||
|
const applyParameters = NodeHelpers.getSpecialNodeParameters(nodeType);
|
||||||
|
|
||||||
if (applyParameters.length) {
|
if (applyParameters.length) {
|
||||||
// eslint-disable-next-line prefer-spread
|
nodeType.description.properties.unshift(...applyParameters);
|
||||||
nodeTypeData.type.description.properties.unshift.apply(
|
|
||||||
nodeTypeData.type.description.properties,
|
|
||||||
applyParameters,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.nodeTypes = nodeTypes;
|
this.nodeTypes = nodeTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAll(): INodeType[] {
|
getAll(): Array<INodeType | INodeVersionedType> {
|
||||||
return Object.values(this.nodeTypes).map((data) => data.type);
|
return Object.values(this.nodeTypes).map((data) => data.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
getByName(nodeType: string): INodeType | undefined {
|
getByName(nodeType: string): INodeType | INodeVersionedType | undefined {
|
||||||
if (this.nodeTypes[nodeType] === undefined) {
|
if (this.nodeTypes[nodeType] === undefined) {
|
||||||
throw new Error(`The node-type "${nodeType}" is not known!`);
|
throw new Error(`The node-type "${nodeType}" is not known!`);
|
||||||
}
|
}
|
||||||
return this.nodeTypes[nodeType].type;
|
return this.nodeTypes[nodeType].type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
||||||
|
return NodeHelpers.getVersionedTypeNode(this.nodeTypes[nodeType].type, version);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let nodeTypesInstance: NodeTypesClass | undefined;
|
let nodeTypesInstance: NodeTypesClass | undefined;
|
||||||
|
|
|
@ -68,17 +68,23 @@ import {
|
||||||
INodeCredentials,
|
INodeCredentials,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
|
INodeType,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
|
INodeTypeNameVersion,
|
||||||
IRunData,
|
IRunData,
|
||||||
|
INodeVersionedType,
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
IWorkflowCredentials,
|
IWorkflowCredentials,
|
||||||
LoggerProxy,
|
LoggerProxy,
|
||||||
NodeCredentialTestRequest,
|
NodeCredentialTestRequest,
|
||||||
NodeCredentialTestResult,
|
NodeCredentialTestResult,
|
||||||
|
NodeHelpers,
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { NodeVersionedType } from 'n8n-nodes-base';
|
||||||
|
|
||||||
import * as basicAuth from 'basic-auth';
|
import * as basicAuth from 'basic-auth';
|
||||||
import * as compression from 'compression';
|
import * as compression from 'compression';
|
||||||
import * as jwt from 'jsonwebtoken';
|
import * as jwt from 'jsonwebtoken';
|
||||||
|
@ -882,7 +888,6 @@ class App {
|
||||||
await this.externalHooks.run('workflow.delete', [id]);
|
await this.externalHooks.run('workflow.delete', [id]);
|
||||||
|
|
||||||
const isActive = await this.activeWorkflowRunner.isActive(id);
|
const isActive = await this.activeWorkflowRunner.isActive(id);
|
||||||
|
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
// Before deleting a workflow deactivate it
|
// Before deleting a workflow deactivate it
|
||||||
await this.activeWorkflowRunner.remove(id);
|
await this.activeWorkflowRunner.remove(id);
|
||||||
|
@ -1060,7 +1065,9 @@ class App {
|
||||||
`/${this.restEndpoint}/node-parameter-options`,
|
`/${this.restEndpoint}/node-parameter-options`,
|
||||||
ResponseHelper.send(
|
ResponseHelper.send(
|
||||||
async (req: express.Request, res: express.Response): Promise<INodePropertyOptions[]> => {
|
async (req: express.Request, res: express.Response): Promise<INodePropertyOptions[]> => {
|
||||||
const nodeType = req.query.nodeType as string;
|
const nodeTypeAndVersion = JSON.parse(
|
||||||
|
`${req.query.nodeTypeAndVersion}`,
|
||||||
|
) as INodeTypeNameVersion;
|
||||||
const path = req.query.path as string;
|
const path = req.query.path as string;
|
||||||
let credentials: INodeCredentials | undefined;
|
let credentials: INodeCredentials | undefined;
|
||||||
const currentNodeParameters = JSON.parse(
|
const currentNodeParameters = JSON.parse(
|
||||||
|
@ -1075,10 +1082,10 @@ class App {
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const loadDataInstance = new LoadNodeParameterOptions(
|
const loadDataInstance = new LoadNodeParameterOptions(
|
||||||
nodeType,
|
nodeTypeAndVersion,
|
||||||
nodeTypes,
|
nodeTypes,
|
||||||
path,
|
path,
|
||||||
JSON.parse(`${req.query.currentNodeParameters}`),
|
currentNodeParameters,
|
||||||
credentials,
|
credentials,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1095,46 +1102,58 @@ class App {
|
||||||
ResponseHelper.send(
|
ResponseHelper.send(
|
||||||
async (req: express.Request, res: express.Response): Promise<INodeTypeDescription[]> => {
|
async (req: express.Request, res: express.Response): Promise<INodeTypeDescription[]> => {
|
||||||
const returnData: INodeTypeDescription[] = [];
|
const returnData: INodeTypeDescription[] = [];
|
||||||
|
const onlyLatest = req.query.onlyLatest === 'true';
|
||||||
|
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
|
|
||||||
const allNodes = nodeTypes.getAll();
|
const allNodes = nodeTypes.getAll();
|
||||||
|
|
||||||
allNodes.forEach((nodeData) => {
|
const getNodeDescription = (nodeType: INodeType): INodeTypeDescription => {
|
||||||
// Make a copy of the object. If we don't do this, then when
|
const nodeInfo: INodeTypeDescription = { ...nodeType.description };
|
||||||
// The method below is called the properties are removed for good
|
|
||||||
// This happens because nodes are returned as reference.
|
|
||||||
const nodeInfo: INodeTypeDescription = { ...nodeData.description };
|
|
||||||
if (req.query.includeProperties !== 'true') {
|
if (req.query.includeProperties !== 'true') {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
delete nodeInfo.properties;
|
delete nodeInfo.properties;
|
||||||
}
|
}
|
||||||
|
return nodeInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (onlyLatest) {
|
||||||
|
allNodes.forEach((nodeData) => {
|
||||||
|
const nodeType = NodeHelpers.getVersionedTypeNode(nodeData);
|
||||||
|
const nodeInfo: INodeTypeDescription = getNodeDescription(nodeType);
|
||||||
returnData.push(nodeInfo);
|
returnData.push(nodeInfo);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
allNodes.forEach((nodeData) => {
|
||||||
|
const allNodeTypes = NodeHelpers.getVersionedTypeNodeAll(nodeData);
|
||||||
|
allNodeTypes.forEach((element) => {
|
||||||
|
const nodeInfo: INodeTypeDescription = getNodeDescription(element);
|
||||||
|
returnData.push(nodeInfo);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Returns node information baesd on namese
|
// Returns node information based on node names and versions
|
||||||
this.app.post(
|
this.app.post(
|
||||||
`/${this.restEndpoint}/node-types`,
|
`/${this.restEndpoint}/node-types`,
|
||||||
ResponseHelper.send(
|
ResponseHelper.send(
|
||||||
async (req: express.Request, res: express.Response): Promise<INodeTypeDescription[]> => {
|
async (req: express.Request, res: express.Response): Promise<INodeTypeDescription[]> => {
|
||||||
const nodeNames = _.get(req, 'body.nodeNames', []) as string[];
|
const nodeInfos = _.get(req, 'body.nodeInfos', []) as INodeTypeNameVersion[];
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
|
|
||||||
return nodeNames
|
const returnData: INodeTypeDescription[] = [];
|
||||||
.map((name) => {
|
nodeInfos.forEach((nodeInfo) => {
|
||||||
try {
|
const nodeType = nodeTypes.getByNameAndVersion(nodeInfo.name, nodeInfo.version);
|
||||||
return nodeTypes.getByName(name);
|
if (nodeType?.description) {
|
||||||
} catch (e) {
|
returnData.push(nodeType.description);
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.filter((nodeData) => !!nodeData)
|
|
||||||
.map((nodeData) => nodeData!.description);
|
return returnData;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1156,7 +1175,7 @@ class App {
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
const nodeType = nodeTypes.getByName(nodeTypeName);
|
const nodeType = nodeTypes.getByNameAndVersion(nodeTypeName);
|
||||||
|
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
res.status(404).send('The nodeType is not known.');
|
res.status(404).send('The nodeType is not known.');
|
||||||
|
@ -1342,14 +1361,42 @@ class App {
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const credentialTestable = node.description.credentials?.find((credential) => {
|
|
||||||
|
if (node instanceof NodeVersionedType) {
|
||||||
|
const versionNames = Object.keys((node as INodeVersionedType).nodeVersions);
|
||||||
|
for (const versionName of versionNames) {
|
||||||
|
const nodeType = (node as INodeVersionedType).nodeVersions[
|
||||||
|
versionName as unknown as number
|
||||||
|
];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
|
const credentialTestable = nodeType.description.credentials?.find((credential) => {
|
||||||
const testFunctionSearch =
|
const testFunctionSearch =
|
||||||
credential.name === credentialType && !!credential.testedBy;
|
credential.name === credentialType && !!credential.testedBy;
|
||||||
if (testFunctionSearch) {
|
if (testFunctionSearch) {
|
||||||
foundTestFunction = node.methods!.credentialTest![credential.testedBy!];
|
foundTestFunction = (node as unknown as INodeType).methods!.credentialTest![
|
||||||
|
credential.testedBy!
|
||||||
|
];
|
||||||
}
|
}
|
||||||
return testFunctionSearch;
|
return testFunctionSearch;
|
||||||
});
|
});
|
||||||
|
if (credentialTestable) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const credentialTestable = (node as INodeType).description.credentials?.find(
|
||||||
|
(credential) => {
|
||||||
|
const testFunctionSearch =
|
||||||
|
credential.name === credentialType && !!credential.testedBy;
|
||||||
|
if (testFunctionSearch) {
|
||||||
|
foundTestFunction = (node as INodeType).methods!.credentialTest![
|
||||||
|
credential.testedBy!
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return testFunctionSearch;
|
||||||
|
},
|
||||||
|
);
|
||||||
return !!credentialTestable;
|
return !!credentialTestable;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,10 @@ export async function executeWebhook(
|
||||||
responseCallback: (error: Error | null, data: IResponseCallbackData) => void,
|
responseCallback: (error: Error | null, data: IResponseCallbackData) => void,
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
// Get the nodeType to know which responseMode is set
|
// Get the nodeType to know which responseMode is set
|
||||||
const nodeType = workflow.nodeTypes.getByName(workflowStartNode.type);
|
const nodeType = workflow.nodeTypes.getByNameAndVersion(
|
||||||
|
workflowStartNode.type,
|
||||||
|
workflowStartNode.typeVersion,
|
||||||
|
);
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
const errorMessage = `The type of the webhook node "${workflowStartNode.name}" is not known.`;
|
const errorMessage = `The type of the webhook node "${workflowStartNode.name}" is not known.`;
|
||||||
responseCallback(new Error(errorMessage), {});
|
responseCallback(new Error(errorMessage), {});
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
/* eslint-disable no-underscore-dangle */
|
/* eslint-disable no-underscore-dangle */
|
||||||
/* eslint-disable no-continue */
|
/* eslint-disable no-continue */
|
||||||
|
@ -226,13 +229,13 @@ export function getNodeTypeData(nodes: INode[]): ITransferNodeTypes {
|
||||||
// can be loaded again in the process
|
// can be loaded again in the process
|
||||||
const returnData: ITransferNodeTypes = {};
|
const returnData: ITransferNodeTypes = {};
|
||||||
for (const nodeTypeName of neededNodeTypes) {
|
for (const nodeTypeName of neededNodeTypes) {
|
||||||
if (nodeTypes.nodeTypes[nodeTypeName] === undefined) {
|
if (nodeTypes.nodeTypes[nodeTypeName.type] === undefined) {
|
||||||
throw new Error(`The NodeType "${nodeTypeName}" could not be found!`);
|
throw new Error(`The NodeType "${nodeTypeName.type}" could not be found!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
returnData[nodeTypeName] = {
|
returnData[nodeTypeName.type] = {
|
||||||
className: nodeTypes.nodeTypes[nodeTypeName].type.constructor.name,
|
className: nodeTypes.nodeTypes[nodeTypeName.type].type.constructor.name,
|
||||||
sourcePath: nodeTypes.nodeTypes[nodeTypeName].sourcePath,
|
sourcePath: nodeTypes.nodeTypes[nodeTypeName.type].sourcePath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,12 +309,12 @@ export function getCredentialsDataByNodes(nodes: INode[]): ICredentialsTypeData
|
||||||
* @param {INode[]} nodes
|
* @param {INode[]} nodes
|
||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
export function getNeededNodeTypes(nodes: INode[]): string[] {
|
export function getNeededNodeTypes(nodes: INode[]): Array<{ type: string; version: number }> {
|
||||||
// Check which node-types have to be loaded
|
// Check which node-types have to be loaded
|
||||||
const neededNodeTypes: string[] = [];
|
const neededNodeTypes: Array<{ type: string; version: number }> = [];
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
if (!neededNodeTypes.includes(node.type)) {
|
if (neededNodeTypes.find((neededNodes) => node.type === neededNodes.type) === undefined) {
|
||||||
neededNodeTypes.push(node.type);
|
neededNodeTypes.push({ type: node.type, version: node.typeVersion });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,15 @@ export class WorkflowRunnerProcess {
|
||||||
const tempModule = require(filePath);
|
const tempModule = require(filePath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
|
const nodeObject = new tempModule[className]();
|
||||||
|
if (nodeObject.getNodeType !== undefined) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
|
tempNode = nodeObject.getNodeType();
|
||||||
|
} else {
|
||||||
|
tempNode = nodeObject;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
tempNode = new tempModule[className]() as INodeType;
|
tempNode = new tempModule[className]() as INodeType;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Error loading node "${nodeTypeName}" from: "${filePath}"`);
|
throw new Error(`Error loading node "${nodeTypeName}" from: "${filePath}"`);
|
||||||
|
|
|
@ -42,15 +42,18 @@
|
||||||
"typescript": "~4.3.5"
|
"typescript": "~4.3.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^0.21.1",
|
||||||
"client-oauth2": "^4.2.5",
|
"client-oauth2": "^4.2.5",
|
||||||
"cron": "^1.7.2",
|
"cron": "^1.7.2",
|
||||||
"crypto-js": "~4.1.1",
|
"crypto-js": "~4.1.1",
|
||||||
"file-type": "^14.6.2",
|
"file-type": "^14.6.2",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"mime-types": "^2.1.27",
|
"mime-types": "^2.1.27",
|
||||||
"n8n-workflow": "~0.69.0",
|
"n8n-workflow": "~0.69.0",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"p-cancelable": "^2.0.0",
|
"p-cancelable": "^2.0.0",
|
||||||
|
"qs": "^6.10.1",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"request-promise-native": "^1.0.7"
|
"request-promise-native": "^1.0.7"
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
IExecuteFunctions as IExecuteFunctionsBase,
|
IExecuteFunctions as IExecuteFunctionsBase,
|
||||||
IExecuteSingleFunctions as IExecuteSingleFunctionsBase,
|
IExecuteSingleFunctions as IExecuteSingleFunctionsBase,
|
||||||
IHookFunctions as IHookFunctionsBase,
|
IHookFunctions as IHookFunctionsBase,
|
||||||
|
IHttpRequestOptions,
|
||||||
ILoadOptionsFunctions as ILoadOptionsFunctionsBase,
|
ILoadOptionsFunctions as ILoadOptionsFunctionsBase,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodeType,
|
INodeType,
|
||||||
|
@ -34,13 +35,14 @@ export interface IProcessMessage {
|
||||||
|
|
||||||
export interface IExecuteFunctions extends IExecuteFunctionsBase {
|
export interface IExecuteFunctions extends IExecuteFunctionsBase {
|
||||||
helpers: {
|
helpers: {
|
||||||
|
httpRequest(requestOptions: IHttpRequestOptions): Promise<any>; // tslint:disable-line:no-any
|
||||||
prepareBinaryData(
|
prepareBinaryData(
|
||||||
binaryData: Buffer,
|
binaryData: Buffer,
|
||||||
filePath?: string,
|
filePath?: string,
|
||||||
mimeType?: string,
|
mimeType?: string,
|
||||||
): Promise<IBinaryData>;
|
): Promise<IBinaryData>;
|
||||||
getBinaryDataBuffer(itemIndex: number, propertyName: string): Promise<Buffer>;
|
getBinaryDataBuffer(itemIndex: number, propertyName: string): Promise<Buffer>;
|
||||||
request: requestPromise.RequestPromiseAPI;
|
request: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||||
requestOAuth2(
|
requestOAuth2(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
@ -58,12 +60,13 @@ export interface IExecuteFunctions extends IExecuteFunctionsBase {
|
||||||
|
|
||||||
export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
|
export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
|
||||||
helpers: {
|
helpers: {
|
||||||
|
httpRequest(requestOptions: IHttpRequestOptions): Promise<any>; // tslint:disable-line:no-any
|
||||||
prepareBinaryData(
|
prepareBinaryData(
|
||||||
binaryData: Buffer,
|
binaryData: Buffer,
|
||||||
filePath?: string,
|
filePath?: string,
|
||||||
mimeType?: string,
|
mimeType?: string,
|
||||||
): Promise<IBinaryData>;
|
): Promise<IBinaryData>;
|
||||||
request: requestPromise.RequestPromiseAPI;
|
request: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||||
requestOAuth2(
|
requestOAuth2(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
@ -80,12 +83,13 @@ export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
|
||||||
|
|
||||||
export interface IPollFunctions extends IPollFunctionsBase {
|
export interface IPollFunctions extends IPollFunctionsBase {
|
||||||
helpers: {
|
helpers: {
|
||||||
|
httpRequest(requestOptions: IHttpRequestOptions): Promise<any>; // tslint:disable-line:no-any
|
||||||
prepareBinaryData(
|
prepareBinaryData(
|
||||||
binaryData: Buffer,
|
binaryData: Buffer,
|
||||||
filePath?: string,
|
filePath?: string,
|
||||||
mimeType?: string,
|
mimeType?: string,
|
||||||
): Promise<IBinaryData>;
|
): Promise<IBinaryData>;
|
||||||
request: requestPromise.RequestPromiseAPI;
|
request: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||||
requestOAuth2(
|
requestOAuth2(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
@ -107,12 +111,13 @@ export interface IResponseError extends Error {
|
||||||
|
|
||||||
export interface ITriggerFunctions extends ITriggerFunctionsBase {
|
export interface ITriggerFunctions extends ITriggerFunctionsBase {
|
||||||
helpers: {
|
helpers: {
|
||||||
|
httpRequest(requestOptions: IHttpRequestOptions): Promise<any>; // tslint:disable-line:no-any
|
||||||
prepareBinaryData(
|
prepareBinaryData(
|
||||||
binaryData: Buffer,
|
binaryData: Buffer,
|
||||||
filePath?: string,
|
filePath?: string,
|
||||||
mimeType?: string,
|
mimeType?: string,
|
||||||
): Promise<IBinaryData>;
|
): Promise<IBinaryData>;
|
||||||
request: requestPromise.RequestPromiseAPI;
|
request: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||||
requestOAuth2(
|
requestOAuth2(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
@ -144,7 +149,8 @@ export interface IUserSettings {
|
||||||
|
|
||||||
export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase {
|
export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase {
|
||||||
helpers: {
|
helpers: {
|
||||||
request?: requestPromise.RequestPromiseAPI;
|
httpRequest(requestOptions: IHttpRequestOptions): Promise<any>; // tslint:disable-line:no-any
|
||||||
|
request?: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||||
requestOAuth2?: (
|
requestOAuth2?: (
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
@ -167,7 +173,8 @@ export interface ICredentialTestFunctions extends ICredentialTestFunctionsBase {
|
||||||
|
|
||||||
export interface IHookFunctions extends IHookFunctionsBase {
|
export interface IHookFunctions extends IHookFunctionsBase {
|
||||||
helpers: {
|
helpers: {
|
||||||
request: requestPromise.RequestPromiseAPI;
|
httpRequest(requestOptions: IHttpRequestOptions): Promise<any>; // tslint:disable-line:no-any
|
||||||
|
request: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||||
requestOAuth2(
|
requestOAuth2(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
@ -184,12 +191,13 @@ export interface IHookFunctions extends IHookFunctionsBase {
|
||||||
|
|
||||||
export interface IWebhookFunctions extends IWebhookFunctionsBase {
|
export interface IWebhookFunctions extends IWebhookFunctionsBase {
|
||||||
helpers: {
|
helpers: {
|
||||||
|
httpRequest(requestOptions: IHttpRequestOptions): Promise<any>; // tslint:disable-line:no-any
|
||||||
prepareBinaryData(
|
prepareBinaryData(
|
||||||
binaryData: Buffer,
|
binaryData: Buffer,
|
||||||
filePath?: string,
|
filePath?: string,
|
||||||
mimeType?: string,
|
mimeType?: string,
|
||||||
): Promise<IBinaryData>;
|
): Promise<IBinaryData>;
|
||||||
request: requestPromise.RequestPromiseAPI;
|
request: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||||
requestOAuth2(
|
requestOAuth2(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
import {
|
import {
|
||||||
INode,
|
INode,
|
||||||
INodeCredentials,
|
INodeCredentials,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
|
INodeTypeNameVersion,
|
||||||
INodeTypes,
|
INodeTypes,
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
Workflow,
|
Workflow,
|
||||||
|
@ -21,27 +27,30 @@ export class LoadNodeParameterOptions {
|
||||||
workflow: Workflow;
|
workflow: Workflow;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
nodeTypeName: string,
|
nodeTypeNameAndVersion: INodeTypeNameVersion,
|
||||||
nodeTypes: INodeTypes,
|
nodeTypes: INodeTypes,
|
||||||
path: string,
|
path: string,
|
||||||
currentNodeParameters: INodeParameters,
|
currentNodeParameters: INodeParameters,
|
||||||
credentials?: INodeCredentials,
|
credentials?: INodeCredentials,
|
||||||
) {
|
) {
|
||||||
|
const nodeType = nodeTypes.getByNameAndVersion(
|
||||||
|
nodeTypeNameAndVersion.name,
|
||||||
|
nodeTypeNameAndVersion.version,
|
||||||
|
);
|
||||||
this.path = path;
|
this.path = path;
|
||||||
const nodeType = nodeTypes.getByName(nodeTypeName);
|
|
||||||
|
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
throw new Error(`The node-type "${nodeTypeName}" is not known!`);
|
throw new Error(
|
||||||
|
`The node-type "${nodeTypeNameAndVersion.name} v${nodeTypeNameAndVersion.version}" is not known!`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeData: INode = {
|
const nodeData: INode = {
|
||||||
parameters: currentNodeParameters,
|
parameters: currentNodeParameters,
|
||||||
name: TEMP_NODE_NAME,
|
name: TEMP_NODE_NAME,
|
||||||
type: nodeTypeName,
|
type: nodeTypeNameAndVersion.name,
|
||||||
typeVersion: 1,
|
typeVersion: nodeTypeNameAndVersion.version,
|
||||||
position: [0, 0],
|
position: [0, 0],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (credentials) {
|
if (credentials) {
|
||||||
nodeData.credentials = credentials;
|
nodeData.credentials = credentials;
|
||||||
}
|
}
|
||||||
|
@ -91,12 +100,13 @@ export class LoadNodeParameterOptions {
|
||||||
): Promise<INodePropertyOptions[]> {
|
): Promise<INodePropertyOptions[]> {
|
||||||
const node = this.workflow.getNode(TEMP_NODE_NAME);
|
const node = this.workflow.getNode(TEMP_NODE_NAME);
|
||||||
|
|
||||||
const nodeType = this.workflow.nodeTypes.getByName(node!.type);
|
const nodeType = this.workflow.nodeTypes.getByNameAndVersion(node!.type, node?.typeVersion);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
nodeType!.methods === undefined ||
|
!nodeType ||
|
||||||
nodeType!.methods.loadOptions === undefined ||
|
nodeType.methods === undefined ||
|
||||||
nodeType!.methods.loadOptions[methodName] === undefined
|
nodeType.methods.loadOptions === undefined ||
|
||||||
|
nodeType.methods.loadOptions[methodName] === undefined
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The node-type "${node!.type}" does not have the method "${methodName}" defined!`,
|
`The node-type "${node!.type}" does not have the method "${methodName}" defined!`,
|
||||||
|
@ -110,6 +120,6 @@ export class LoadNodeParameterOptions {
|
||||||
additionalData,
|
additionalData,
|
||||||
);
|
);
|
||||||
|
|
||||||
return nodeType!.methods.loadOptions[methodName].call(thisArgs);
|
return nodeType.methods.loadOptions[methodName].call(thisArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable no-lonely-if */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable no-prototype-builtins */
|
/* eslint-disable no-prototype-builtins */
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
@ -13,6 +14,7 @@
|
||||||
/* eslint-disable @typescript-eslint/no-shadow */
|
/* eslint-disable @typescript-eslint/no-shadow */
|
||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import {
|
import {
|
||||||
|
GenericValue,
|
||||||
IAllExecuteFunctions,
|
IAllExecuteFunctions,
|
||||||
IBinaryData,
|
IBinaryData,
|
||||||
IContextObject,
|
IContextObject,
|
||||||
|
@ -22,6 +24,9 @@ import {
|
||||||
IExecuteFunctions,
|
IExecuteFunctions,
|
||||||
IExecuteSingleFunctions,
|
IExecuteSingleFunctions,
|
||||||
IExecuteWorkflowInfo,
|
IExecuteWorkflowInfo,
|
||||||
|
IHttpRequestOptions,
|
||||||
|
IN8nHttpFullResponse,
|
||||||
|
IN8nHttpResponse,
|
||||||
INode,
|
INode,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
|
@ -48,6 +53,8 @@ import {
|
||||||
LoggerProxy as Logger,
|
LoggerProxy as Logger,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { Agent } from 'https';
|
||||||
|
import { stringify } from 'qs';
|
||||||
import * as clientOAuth1 from 'oauth-1.0a';
|
import * as clientOAuth1 from 'oauth-1.0a';
|
||||||
import { Token } from 'oauth-1.0a';
|
import { Token } from 'oauth-1.0a';
|
||||||
import * as clientOAuth2 from 'client-oauth2';
|
import * as clientOAuth2 from 'client-oauth2';
|
||||||
|
@ -55,6 +62,7 @@ import * as clientOAuth2 from 'client-oauth2';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
|
import * as FormData from 'form-data';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { OptionsWithUri, OptionsWithUrl } from 'request';
|
import { OptionsWithUri, OptionsWithUrl } from 'request';
|
||||||
import * as requestPromise from 'request-promise-native';
|
import * as requestPromise from 'request-promise-native';
|
||||||
|
@ -62,6 +70,8 @@ import { createHmac } from 'crypto';
|
||||||
import { fromBuffer } from 'file-type';
|
import { fromBuffer } from 'file-type';
|
||||||
import { lookup } from 'mime-types';
|
import { lookup } from 'mime-types';
|
||||||
|
|
||||||
|
import axios, { AxiosProxyConfig, AxiosRequestConfig, Method } from 'axios';
|
||||||
|
import { URLSearchParams } from 'url';
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import {
|
import {
|
||||||
BINARY_ENCODING,
|
BINARY_ENCODING,
|
||||||
|
@ -73,10 +83,425 @@ import {
|
||||||
PLACEHOLDER_EMPTY_EXECUTION_ID,
|
PLACEHOLDER_EMPTY_EXECUTION_ID,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
|
||||||
|
axios.defaults.timeout = 300000;
|
||||||
|
// Prevent axios from adding x-form-www-urlencoded headers by default
|
||||||
|
axios.defaults.headers.post = {};
|
||||||
|
|
||||||
const requestPromiseWithDefaults = requestPromise.defaults({
|
const requestPromiseWithDefaults = requestPromise.defaults({
|
||||||
timeout: 300000, // 5 minutes
|
timeout: 300000, // 5 minutes
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function parseRequestObject(requestObject: IDataObject) {
|
||||||
|
// This function is a temporary implementation
|
||||||
|
// That translates all http requests done via
|
||||||
|
// the request library to axios directly
|
||||||
|
// We are not using n8n's interface as it would
|
||||||
|
// an unnecessary step, considering the `request`
|
||||||
|
// helper can be deprecated and removed.
|
||||||
|
const axiosConfig: AxiosRequestConfig = {};
|
||||||
|
|
||||||
|
if (requestObject.headers !== undefined) {
|
||||||
|
axiosConfig.headers = requestObject.headers as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's start parsing the hardest part, which is the request body.
|
||||||
|
// The process here is as following?
|
||||||
|
// - Check if we have a `content-type` header. If this was set,
|
||||||
|
// we will follow
|
||||||
|
// - Check if the `form` property was set. If yes, then it's x-www-form-urlencoded
|
||||||
|
// - Check if the `formData` property exists. If yes, then it's multipart/form-data
|
||||||
|
// - Lastly, we should have a regular `body` that is probably a JSON.
|
||||||
|
|
||||||
|
const contentTypeHeaderKeyName =
|
||||||
|
axiosConfig.headers &&
|
||||||
|
Object.keys(axiosConfig.headers).find(
|
||||||
|
(headerName) => headerName.toLowerCase() === 'content-type',
|
||||||
|
);
|
||||||
|
const contentType =
|
||||||
|
contentTypeHeaderKeyName &&
|
||||||
|
(axiosConfig.headers[contentTypeHeaderKeyName] as string | undefined);
|
||||||
|
if (contentType === 'application/x-www-form-urlencoded' && requestObject.formData === undefined) {
|
||||||
|
// there are nodes incorrectly created, informing the content type header
|
||||||
|
// and also using formData. Request lib takes precedence for the formData.
|
||||||
|
// We will do the same.
|
||||||
|
// Merge body and form properties.
|
||||||
|
// @ts-ignore
|
||||||
|
axiosConfig.data =
|
||||||
|
typeof requestObject.body === 'string'
|
||||||
|
? requestObject.body
|
||||||
|
: new URLSearchParams(
|
||||||
|
Object.assign(requestObject.body || {}, requestObject.form || {}) as Record<
|
||||||
|
string,
|
||||||
|
string
|
||||||
|
>,
|
||||||
|
);
|
||||||
|
} else if (contentType && contentType.includes('multipart/form-data') !== false) {
|
||||||
|
if (requestObject.formData !== undefined && requestObject.formData instanceof FormData) {
|
||||||
|
axiosConfig.data = requestObject.formData;
|
||||||
|
} else {
|
||||||
|
const allData = Object.assign(requestObject.body || {}, requestObject.formData || {});
|
||||||
|
|
||||||
|
const objectKeys = Object.keys(allData);
|
||||||
|
if (objectKeys.length > 0) {
|
||||||
|
// Should be a standard object. We must convert to formdata
|
||||||
|
const form = new FormData();
|
||||||
|
|
||||||
|
objectKeys.forEach((key) => {
|
||||||
|
const formField = (allData as IDataObject)[key] as IDataObject;
|
||||||
|
if (formField.hasOwnProperty('value') && formField.value instanceof Buffer) {
|
||||||
|
let filename;
|
||||||
|
// @ts-ignore
|
||||||
|
if (!!formField.options && formField.options.filename !== undefined) {
|
||||||
|
filename = (formField.options as IDataObject).filename as string;
|
||||||
|
}
|
||||||
|
form.append(key, formField.value, filename);
|
||||||
|
} else {
|
||||||
|
form.append(key, formField);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
axiosConfig.data = form;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// replace the existing header with a new one that
|
||||||
|
// contains the boundary property.
|
||||||
|
// @ts-ignore
|
||||||
|
delete axiosConfig.headers[contentTypeHeaderKeyName];
|
||||||
|
const headers = axiosConfig.data.getHeaders();
|
||||||
|
axiosConfig.headers = Object.assign(axiosConfig.headers || {}, headers);
|
||||||
|
} else {
|
||||||
|
// When using the `form` property it means the content should be x-www-form-urlencoded.
|
||||||
|
if (requestObject.form !== undefined && requestObject.body === undefined) {
|
||||||
|
// If we have only form
|
||||||
|
axiosConfig.data = new URLSearchParams(requestObject.form as Record<string, string>);
|
||||||
|
if (axiosConfig.headers !== undefined) {
|
||||||
|
// remove possibly existing content-type headers
|
||||||
|
const headers = Object.keys(axiosConfig.headers);
|
||||||
|
headers.forEach((header) =>
|
||||||
|
header.toLowerCase() === 'content-type' ? delete axiosConfig.headers[header] : null,
|
||||||
|
);
|
||||||
|
axiosConfig.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||||
|
} else {
|
||||||
|
axiosConfig.headers = {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (requestObject.formData !== undefined) {
|
||||||
|
// remove any "content-type" that might exist.
|
||||||
|
if (axiosConfig.headers !== undefined) {
|
||||||
|
const headers = Object.keys(axiosConfig.headers);
|
||||||
|
headers.forEach((header) =>
|
||||||
|
header.toLowerCase() === 'content-type' ? delete axiosConfig.headers[header] : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestObject.formData instanceof FormData) {
|
||||||
|
axiosConfig.data = requestObject.formData;
|
||||||
|
} else {
|
||||||
|
const objectKeys = Object.keys(requestObject.formData as object);
|
||||||
|
if (objectKeys.length > 0) {
|
||||||
|
// Should be a standard object. We must convert to formdata
|
||||||
|
const form = new FormData();
|
||||||
|
|
||||||
|
objectKeys.forEach((key) => {
|
||||||
|
const formField = (requestObject.formData as IDataObject)[key] as IDataObject;
|
||||||
|
if (formField.hasOwnProperty('value') && formField.value instanceof Buffer) {
|
||||||
|
let filename;
|
||||||
|
// @ts-ignore
|
||||||
|
if (!!formField.options && formField.options.filename !== undefined) {
|
||||||
|
filename = (formField.options as IDataObject).filename as string;
|
||||||
|
}
|
||||||
|
form.append(key, formField.value, filename);
|
||||||
|
} else {
|
||||||
|
form.append(key, formField);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
axiosConfig.data = form;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Mix in headers as FormData creates the boundary.
|
||||||
|
const headers = axiosConfig.data.getHeaders();
|
||||||
|
axiosConfig.headers = Object.assign(axiosConfig.headers || {}, headers);
|
||||||
|
} else if (requestObject.body !== undefined) {
|
||||||
|
// If we have body and possibly form
|
||||||
|
if (requestObject.form !== undefined) {
|
||||||
|
// merge both objects when exist.
|
||||||
|
requestObject.body = Object.assign(requestObject.body, requestObject.form);
|
||||||
|
}
|
||||||
|
axiosConfig.data = requestObject.body as FormData | GenericValue | GenericValue[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestObject.uri !== undefined) {
|
||||||
|
axiosConfig.url = requestObject.uri as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestObject.url !== undefined) {
|
||||||
|
axiosConfig.url = requestObject.url as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestObject.method !== undefined) {
|
||||||
|
axiosConfig.method = requestObject.method as Method;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestObject.qs !== undefined && Object.keys(requestObject.qs as object).length > 0) {
|
||||||
|
axiosConfig.params = requestObject.qs as IDataObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestObject.useQuerystring === true) {
|
||||||
|
axiosConfig.paramsSerializer = (params) => {
|
||||||
|
return stringify(params, { arrayFormat: 'repeat' });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestObject.auth !== undefined) {
|
||||||
|
// Check support for sendImmediately
|
||||||
|
if ((requestObject.auth as IDataObject).bearer !== undefined) {
|
||||||
|
axiosConfig.headers = Object.assign(axiosConfig.headers || {}, {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
|
Authorization: `Bearer ${(requestObject.auth as IDataObject).bearer}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const authObj = requestObject.auth as IDataObject;
|
||||||
|
// Request accepts both user/username and pass/password
|
||||||
|
axiosConfig.auth = {
|
||||||
|
username: (authObj.user || authObj.username) as string,
|
||||||
|
password: (authObj.password || authObj.pass) as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only set header if we have a body, otherwise it may fail
|
||||||
|
if (requestObject.json === true) {
|
||||||
|
// Add application/json headers - do not set charset as it breaks a lot of stuff
|
||||||
|
// only add if no other accept headers was sent.
|
||||||
|
const acceptHeaderExists =
|
||||||
|
axiosConfig.headers === undefined
|
||||||
|
? false
|
||||||
|
: Object.keys(axiosConfig.headers)
|
||||||
|
.map((headerKey) => headerKey.toLowerCase())
|
||||||
|
.includes('accept');
|
||||||
|
if (!acceptHeaderExists) {
|
||||||
|
axiosConfig.headers = Object.assign(axiosConfig.headers || {}, {
|
||||||
|
Accept: 'application/json',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (requestObject.json === false) {
|
||||||
|
// Prevent json parsing
|
||||||
|
axiosConfig.transformResponse = (res) => res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Axios will follow redirects by default, so we simply tell it otherwise if needed.
|
||||||
|
if (
|
||||||
|
requestObject.followRedirect === false &&
|
||||||
|
((requestObject.method as string | undefined) || 'get').toLowerCase() === 'get'
|
||||||
|
) {
|
||||||
|
axiosConfig.maxRedirects = 0;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
requestObject.followAllRedirect === false &&
|
||||||
|
((requestObject.method as string | undefined) || 'get').toLowerCase() !== 'get'
|
||||||
|
) {
|
||||||
|
axiosConfig.maxRedirects = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestObject.rejectUnauthorized === false) {
|
||||||
|
axiosConfig.httpsAgent = new Agent({
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestObject.timeout !== undefined) {
|
||||||
|
axiosConfig.timeout = requestObject.timeout as number;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestObject.proxy !== undefined) {
|
||||||
|
axiosConfig.proxy = requestObject.proxy as AxiosProxyConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestObject.encoding === null) {
|
||||||
|
// When downloading files, return an arrayBuffer.
|
||||||
|
axiosConfig.responseType = 'arraybuffer';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't set an accept header
|
||||||
|
// Axios forces "application/json, text/plan, */*"
|
||||||
|
// Which causes some nodes like NextCloud to break
|
||||||
|
// as the service returns XML unless requested otherwise.
|
||||||
|
const allHeaders = axiosConfig.headers ? Object.keys(axiosConfig.headers) : [];
|
||||||
|
if (!allHeaders.some((headerKey) => headerKey.toLowerCase() === 'accept')) {
|
||||||
|
axiosConfig.headers = Object.assign(axiosConfig.headers || {}, { accept: '*/*' });
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
axiosConfig.data !== undefined &&
|
||||||
|
!(axiosConfig.data instanceof Buffer) &&
|
||||||
|
!allHeaders.some((headerKey) => headerKey.toLowerCase() === 'content-type')
|
||||||
|
) {
|
||||||
|
// Use default header for application/json
|
||||||
|
// If we don't specify this here, axios will add
|
||||||
|
// application/json; charset=utf-8
|
||||||
|
// and this breaks a lot of stuff
|
||||||
|
axiosConfig.headers = Object.assign(axiosConfig.headers || {}, {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Missing properties:
|
||||||
|
* encoding (need testing)
|
||||||
|
* gzip (ignored - default already works)
|
||||||
|
* resolveWithFullResponse (implemented elsewhere)
|
||||||
|
* simple (???)
|
||||||
|
*/
|
||||||
|
|
||||||
|
return axiosConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function proxyRequestToAxios(
|
||||||
|
uriOrObject: string | IDataObject,
|
||||||
|
options?: IDataObject,
|
||||||
|
): Promise<any> {
|
||||||
|
// tslint:disable-line:no-any
|
||||||
|
|
||||||
|
// Check if there's a better way of getting this config here
|
||||||
|
if (process.env.N8N_USE_DEPRECATED_REQUEST_LIB) {
|
||||||
|
// @ts-ignore
|
||||||
|
return requestPromiseWithDefaults.call(null, uriOrObject, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
let axiosConfig: AxiosRequestConfig = {};
|
||||||
|
|
||||||
|
let configObject: IDataObject;
|
||||||
|
if (uriOrObject !== undefined && typeof uriOrObject === 'string') {
|
||||||
|
axiosConfig.url = uriOrObject;
|
||||||
|
}
|
||||||
|
if (uriOrObject !== undefined && typeof uriOrObject === 'object') {
|
||||||
|
configObject = uriOrObject;
|
||||||
|
} else {
|
||||||
|
configObject = options || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
axiosConfig = Object.assign(axiosConfig, await parseRequestObject(configObject));
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axios(axiosConfig)
|
||||||
|
.then((response) => {
|
||||||
|
if (configObject.resolveWithFullResponse === true) {
|
||||||
|
resolve({
|
||||||
|
body: response.data,
|
||||||
|
headers: response.headers,
|
||||||
|
statusCode: response.status,
|
||||||
|
statusMessage: response.statusText,
|
||||||
|
request: response.request,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve(response.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchForHeader(headers: IDataObject, headerName: string) {
|
||||||
|
if (headers === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerNames = Object.keys(headers);
|
||||||
|
headerName = headerName.toLowerCase();
|
||||||
|
return headerNames.find((thisHeader) => thisHeader.toLowerCase() === headerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertN8nRequestToAxios(n8nRequest: IHttpRequestOptions): AxiosRequestConfig {
|
||||||
|
// Destructure properties with the same name first.
|
||||||
|
const { headers, method, timeout, auth, proxy, url } = n8nRequest;
|
||||||
|
|
||||||
|
const axiosRequest = {
|
||||||
|
headers: headers ?? {},
|
||||||
|
method,
|
||||||
|
timeout,
|
||||||
|
auth,
|
||||||
|
proxy,
|
||||||
|
url,
|
||||||
|
} as AxiosRequestConfig;
|
||||||
|
|
||||||
|
axiosRequest.params = n8nRequest.qs;
|
||||||
|
|
||||||
|
if (n8nRequest.disableFollowRedirect === true) {
|
||||||
|
axiosRequest.maxRedirects = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n8nRequest.encoding !== undefined) {
|
||||||
|
axiosRequest.responseType = n8nRequest.encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n8nRequest.skipSslCertificateValidation === true) {
|
||||||
|
axiosRequest.httpsAgent = new Agent({
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n8nRequest.arrayFormat !== undefined) {
|
||||||
|
axiosRequest.paramsSerializer = (params) => {
|
||||||
|
return stringify(params, { arrayFormat: n8nRequest.arrayFormat });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n8nRequest.body) {
|
||||||
|
axiosRequest.data = n8nRequest.body;
|
||||||
|
// Let's add some useful header standards here.
|
||||||
|
const existingContentTypeHeaderKey = searchForHeader(axiosRequest.headers, 'content-type');
|
||||||
|
if (existingContentTypeHeaderKey === undefined) {
|
||||||
|
// We are only setting content type headers if the user did
|
||||||
|
// not set it already manually. We're not overriding, even if it's wrong.
|
||||||
|
if (axiosRequest.data instanceof FormData) {
|
||||||
|
axiosRequest.headers = axiosRequest.headers || {};
|
||||||
|
axiosRequest.headers['Content-Type'] = 'multipart/form-data';
|
||||||
|
} else if (axiosRequest.data instanceof URLSearchParams) {
|
||||||
|
axiosRequest.headers = axiosRequest.headers || {};
|
||||||
|
axiosRequest.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n8nRequest.json) {
|
||||||
|
const key = searchForHeader(axiosRequest.headers, 'accept');
|
||||||
|
// If key exists, then the user has set both accept
|
||||||
|
// header and the json flag. Header should take precedence.
|
||||||
|
if (!key) {
|
||||||
|
axiosRequest.headers.Accept = 'application/json';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userAgentHeader = searchForHeader(axiosRequest.headers, 'user-agent');
|
||||||
|
// If key exists, then the user has set both accept
|
||||||
|
// header and the json flag. Header should take precedence.
|
||||||
|
if (!userAgentHeader) {
|
||||||
|
axiosRequest.headers['User-Agent'] = 'n8n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return axiosRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function httpRequest(
|
||||||
|
requestParams: IHttpRequestOptions,
|
||||||
|
): Promise<IN8nHttpFullResponse | IN8nHttpResponse> {
|
||||||
|
// tslint:disable-line:no-any
|
||||||
|
const axiosRequest = convertN8nRequestToAxios(requestParams);
|
||||||
|
const result = await axios(axiosRequest);
|
||||||
|
if (requestParams.returnFullResponse) {
|
||||||
|
return {
|
||||||
|
body: result.data,
|
||||||
|
headers: result.headers,
|
||||||
|
statusCode: result.status,
|
||||||
|
statusMessage: result.statusText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns binary data buffer for given item index and property name.
|
* Returns binary data buffer for given item index and property name.
|
||||||
*
|
*
|
||||||
|
@ -412,7 +837,7 @@ export async function getCredentials(
|
||||||
itemIndex?: number,
|
itemIndex?: number,
|
||||||
): Promise<ICredentialDataDecryptedObject | undefined> {
|
): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||||
// Get the NodeType as it has the information if the credentials are required
|
// Get the NodeType as it has the information if the credentials are required
|
||||||
const nodeType = workflow.nodeTypes.getByName(node.type);
|
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
node,
|
node,
|
||||||
|
@ -543,7 +968,7 @@ export function getNodeParameter(
|
||||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object {
|
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object {
|
||||||
const nodeType = workflow.nodeTypes.getByName(node.type);
|
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
throw new Error(`Node type "${node.type}" is not known so can not return paramter value!`);
|
throw new Error(`Node type "${node.type}" is not known so can not return paramter value!`);
|
||||||
}
|
}
|
||||||
|
@ -669,7 +1094,7 @@ export function getWebhookDescription(
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
node: INode,
|
node: INode,
|
||||||
): IWebhookDescription | undefined {
|
): IWebhookDescription | undefined {
|
||||||
const nodeType = workflow.nodeTypes.getByName(node.type) as INodeType;
|
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion) as INodeType;
|
||||||
|
|
||||||
if (nodeType.description.webhooks === undefined) {
|
if (nodeType.description.webhooks === undefined) {
|
||||||
// Node does not have any webhooks so return
|
// Node does not have any webhooks so return
|
||||||
|
@ -776,8 +1201,9 @@ export function getExecutePollFunctions(
|
||||||
return workflow.getStaticData(type, node);
|
return workflow.getStaticData(type, node);
|
||||||
},
|
},
|
||||||
helpers: {
|
helpers: {
|
||||||
|
httpRequest,
|
||||||
prepareBinaryData,
|
prepareBinaryData,
|
||||||
request: requestPromiseWithDefaults,
|
request: proxyRequestToAxios,
|
||||||
async requestOAuth2(
|
async requestOAuth2(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
@ -881,8 +1307,10 @@ export function getExecuteTriggerFunctions(
|
||||||
return workflow.getStaticData(type, node);
|
return workflow.getStaticData(type, node);
|
||||||
},
|
},
|
||||||
helpers: {
|
helpers: {
|
||||||
|
httpRequest,
|
||||||
prepareBinaryData,
|
prepareBinaryData,
|
||||||
request: requestPromiseWithDefaults,
|
|
||||||
|
request: proxyRequestToAxios,
|
||||||
async requestOAuth2(
|
async requestOAuth2(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
@ -1072,6 +1500,7 @@ export function getExecuteFunctions(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
helpers: {
|
helpers: {
|
||||||
|
httpRequest,
|
||||||
prepareBinaryData,
|
prepareBinaryData,
|
||||||
async getBinaryDataBuffer(
|
async getBinaryDataBuffer(
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
|
@ -1080,7 +1509,7 @@ export function getExecuteFunctions(
|
||||||
): Promise<Buffer> {
|
): Promise<Buffer> {
|
||||||
return getBinaryDataBuffer.call(this, inputData, itemIndex, propertyName, inputIndex);
|
return getBinaryDataBuffer.call(this, inputData, itemIndex, propertyName, inputIndex);
|
||||||
},
|
},
|
||||||
request: requestPromiseWithDefaults,
|
request: proxyRequestToAxios,
|
||||||
async requestOAuth2(
|
async requestOAuth2(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
@ -1252,8 +1681,9 @@ export function getExecuteSingleFunctions(
|
||||||
return workflow.getStaticData(type, node);
|
return workflow.getStaticData(type, node);
|
||||||
},
|
},
|
||||||
helpers: {
|
helpers: {
|
||||||
|
httpRequest,
|
||||||
prepareBinaryData,
|
prepareBinaryData,
|
||||||
request: requestPromiseWithDefaults,
|
request: proxyRequestToAxios,
|
||||||
async requestOAuth2(
|
async requestOAuth2(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
@ -1366,7 +1796,8 @@ export function getLoadOptionsFunctions(
|
||||||
return additionalData.restApiUrl;
|
return additionalData.restApiUrl;
|
||||||
},
|
},
|
||||||
helpers: {
|
helpers: {
|
||||||
request: requestPromiseWithDefaults,
|
httpRequest,
|
||||||
|
request: proxyRequestToAxios,
|
||||||
async requestOAuth2(
|
async requestOAuth2(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
@ -1485,7 +1916,8 @@ export function getExecuteHookFunctions(
|
||||||
return workflow.getStaticData(type, node);
|
return workflow.getStaticData(type, node);
|
||||||
},
|
},
|
||||||
helpers: {
|
helpers: {
|
||||||
request: requestPromiseWithDefaults,
|
httpRequest,
|
||||||
|
request: proxyRequestToAxios,
|
||||||
async requestOAuth2(
|
async requestOAuth2(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
@ -1630,8 +2062,9 @@ export function getExecuteWebhookFunctions(
|
||||||
},
|
},
|
||||||
prepareOutputData: NodeHelpers.prepareOutputData,
|
prepareOutputData: NodeHelpers.prepareOutputData,
|
||||||
helpers: {
|
helpers: {
|
||||||
|
httpRequest,
|
||||||
prepareBinaryData,
|
prepareBinaryData,
|
||||||
request: requestPromiseWithDefaults,
|
request: proxyRequestToAxios,
|
||||||
async requestOAuth2(
|
async requestOAuth2(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
credentialsType: string,
|
credentialsType: string,
|
||||||
|
|
|
@ -27,6 +27,8 @@ import {
|
||||||
IWaitingForExecution,
|
IWaitingForExecution,
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
LoggerProxy as Logger,
|
LoggerProxy as Logger,
|
||||||
|
NodeApiError,
|
||||||
|
NodeOperationError,
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
WorkflowOperationError,
|
WorkflowOperationError,
|
||||||
|
@ -624,9 +626,9 @@ export class WorkflowExecute {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Set the error that it can be saved correctly
|
// Set the error that it can be saved correctly
|
||||||
executionError = {
|
executionError = {
|
||||||
...error,
|
...(error as NodeOperationError | NodeApiError),
|
||||||
message: error.message,
|
message: (error as NodeOperationError | NodeApiError).message,
|
||||||
stack: error.stack,
|
stack: (error as NodeOperationError | NodeApiError).stack,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set the incoming data of the node that it can be saved correctly
|
// Set the incoming data of the node that it can be saved correctly
|
||||||
|
@ -837,9 +839,9 @@ export class WorkflowExecute {
|
||||||
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
|
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
|
||||||
|
|
||||||
executionError = {
|
executionError = {
|
||||||
...error,
|
...(error as NodeOperationError | NodeApiError),
|
||||||
message: error.message,
|
message: (error as NodeOperationError | NodeApiError).message,
|
||||||
stack: error.stack,
|
stack: (error as NodeOperationError | NodeApiError).stack,
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.debug(`Running node "${executionNode.name}" finished with error`, {
|
Logger.debug(`Running node "${executionNode.name}" finished with error`, {
|
||||||
|
@ -889,6 +891,22 @@ export class WorkflowExecute {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge error information to default output for now
|
||||||
|
// As the new nodes can report the errors in
|
||||||
|
// the `error` property.
|
||||||
|
for (const execution of nodeSuccessData!) {
|
||||||
|
for (const lineResult of execution) {
|
||||||
|
if (lineResult.json.$error !== undefined && lineResult.json.$json !== undefined) {
|
||||||
|
lineResult.error = lineResult.json.$error as NodeApiError | NodeOperationError;
|
||||||
|
lineResult.json = {
|
||||||
|
error: (lineResult.json.$error as NodeApiError | NodeOperationError).message,
|
||||||
|
};
|
||||||
|
} else if (lineResult.error !== undefined) {
|
||||||
|
lineResult.json = { error: lineResult.error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Node executed successfully. So add data and go on.
|
// Node executed successfully. So add data and go on.
|
||||||
taskData.data = {
|
taskData.data = {
|
||||||
main: nodeSuccessData,
|
main: nodeSuccessData,
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
ITaskData,
|
ITaskData,
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
|
NodeHelpers,
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
WorkflowHooks,
|
WorkflowHooks,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
@ -720,11 +721,15 @@ class NodeTypesClass implements INodeTypes {
|
||||||
async init(nodeTypes: INodeTypeData): Promise<void> {}
|
async init(nodeTypes: INodeTypeData): Promise<void> {}
|
||||||
|
|
||||||
getAll(): INodeType[] {
|
getAll(): INodeType[] {
|
||||||
return Object.values(this.nodeTypes).map((data) => data.type);
|
return Object.values(this.nodeTypes).map((data) => NodeHelpers.getVersionedTypeNode(data.type));
|
||||||
}
|
}
|
||||||
|
|
||||||
getByName(nodeType: string): INodeType {
|
getByName(nodeType: string): INodeType {
|
||||||
return this.nodeTypes[nodeType].type;
|
return this.getByNameAndVersion(nodeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
||||||
|
return NodeHelpers.getVersionedTypeNode(this.nodeTypes[nodeType].type, version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
|
INodeTypeNameVersion,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
IRun,
|
IRun,
|
||||||
IRunData,
|
IRunData,
|
||||||
|
@ -129,9 +130,9 @@ 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
|
||||||
getSettings(): Promise<IN8nUISettings>;
|
getSettings(): Promise<IN8nUISettings>;
|
||||||
getNodeTypes(): Promise<INodeTypeDescription[]>;
|
getNodeTypes(onlyLatest?: boolean): Promise<INodeTypeDescription[]>;
|
||||||
getNodesInformation(nodeList: string[]): Promise<INodeTypeDescription[]>;
|
getNodesInformation(nodeInfos: INodeTypeNameVersion[]): Promise<INodeTypeDescription[]>;
|
||||||
getNodeParameterOptions(nodeType: string, path: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise<INodePropertyOptions[]>;
|
getNodeParameterOptions(nodeTypeAndVersion: INodeTypeNameVersion, path: string, methodName: string, 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>;
|
||||||
|
|
|
@ -88,7 +88,6 @@ export default mixins(externalHooks).extend({
|
||||||
filteredNodeTypes(): INodeCreateElement[] {
|
filteredNodeTypes(): INodeCreateElement[] {
|
||||||
const nodeTypes: INodeCreateElement[] = this.searchItems;
|
const nodeTypes: INodeCreateElement[] = this.searchItems;
|
||||||
const filter = this.searchFilter;
|
const filter = this.searchFilter;
|
||||||
|
|
||||||
const returnData = nodeTypes.filter((el: INodeCreateElement) => {
|
const returnData = nodeTypes.filter((el: INodeCreateElement) => {
|
||||||
const nodeType = (el.properties as INodeItemProps).nodeType;
|
const nodeType = (el.properties as INodeItemProps).nodeType;
|
||||||
return filter && matchesSelectType(el, this.selectedType) && matchesNodeType(el, filter);
|
return filter && matchesSelectType(el, this.selectedType) && matchesNodeType(el, filter);
|
||||||
|
|
|
@ -42,7 +42,19 @@ export default Vue.extend({
|
||||||
return this.allNodeTypes
|
return this.allNodeTypes
|
||||||
.filter((nodeType: INodeTypeDescription) => {
|
.filter((nodeType: INodeTypeDescription) => {
|
||||||
return !HIDDEN_NODES.includes(nodeType.name);
|
return !HIDDEN_NODES.includes(nodeType.name);
|
||||||
});
|
}).reduce((accumulator: INodeTypeDescription[], currentValue: INodeTypeDescription) => {
|
||||||
|
// keep only latest version of the nodes
|
||||||
|
// accumulator starts as an empty array.
|
||||||
|
const exists = accumulator.findIndex(nodes => nodes.name === currentValue.name);
|
||||||
|
if (exists >= 0 && accumulator[exists].version < currentValue.version) {
|
||||||
|
// This must be a versioned node and we've found a newer version.
|
||||||
|
// Replace the previous one with this one.
|
||||||
|
accumulator[exists] = currentValue;
|
||||||
|
} else {
|
||||||
|
accumulator.push(currentValue);
|
||||||
|
}
|
||||||
|
return accumulator;
|
||||||
|
}, []);
|
||||||
},
|
},
|
||||||
categoriesWithNodes(): ICategoriesWithNodes {
|
categoriesWithNodes(): ICategoriesWithNodes {
|
||||||
return getCategoriesWithNodes(this.visibleNodeTypes);
|
return getCategoriesWithNodes(this.visibleNodeTypes);
|
||||||
|
|
|
@ -80,7 +80,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);
|
return this.$store.getters.nodeType(this.node.type, this.node.typeVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -571,7 +571,7 @@ export default mixins(
|
||||||
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters) as INodeParameters;
|
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters) as INodeParameters;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const options = await this.restApi().getNodeParameterOptions(this.node.type, this.path, this.remoteMethod, resolvedNodeParameters, this.node.credentials);
|
const options = await this.restApi().getNodeParameterOptions({name: this.node.type, version: this.node.typeVersion}, this.path, this.remoteMethod, resolvedNodeParameters, 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;
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
|
INodeTypeNameVersion,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { makeRestApiRequest } from '@/api/helpers';
|
import { makeRestApiRequest } from '@/api/helpers';
|
||||||
|
|
||||||
|
@ -82,18 +83,18 @@ export const restApi = Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
// Returns all node-types
|
// Returns all node-types
|
||||||
getNodeTypes: (): Promise<INodeTypeDescription[]> => {
|
getNodeTypes: (onlyLatest = false): Promise<INodeTypeDescription[]> => {
|
||||||
return self.restApi().makeRestApiRequest('GET', `/node-types`);
|
return self.restApi().makeRestApiRequest('GET', `/node-types`, {onlyLatest});
|
||||||
},
|
},
|
||||||
|
|
||||||
getNodesInformation: (nodeList: string[]): Promise<INodeTypeDescription[]> => {
|
getNodesInformation: (nodeInfos: INodeTypeNameVersion[]): Promise<INodeTypeDescription[]> => {
|
||||||
return self.restApi().makeRestApiRequest('POST', `/node-types`, {nodeNames: nodeList});
|
return self.restApi().makeRestApiRequest('POST', `/node-types`, {nodeInfos});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Returns all the parameter options from the server
|
// Returns all the parameter options from the server
|
||||||
getNodeParameterOptions: (nodeType: string, path: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise<INodePropertyOptions[]> => {
|
getNodeParameterOptions: (nodeTypeAndVersion: INodeTypeNameVersion, path: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise<INodePropertyOptions[]> => {
|
||||||
const sendData = {
|
const sendData = {
|
||||||
nodeType,
|
nodeTypeAndVersion,
|
||||||
path,
|
path,
|
||||||
methodName,
|
methodName,
|
||||||
credentials,
|
credentials,
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
INodeTypes,
|
INodeTypes,
|
||||||
INodeTypeData,
|
INodeTypeData,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
|
INodeVersionedType,
|
||||||
IRunData,
|
IRunData,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
IWorfklowIssues,
|
IWorfklowIssues,
|
||||||
|
@ -158,7 +159,7 @@ export const workflowHelpers = mixins(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeType = workflow.nodeTypes.getByName(node.type);
|
nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
|
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
// Node type is not known
|
// Node type is not known
|
||||||
|
@ -189,17 +190,28 @@ export const workflowHelpers = mixins(
|
||||||
const nodeTypes: INodeTypes = {
|
const nodeTypes: INodeTypes = {
|
||||||
nodeTypes: {},
|
nodeTypes: {},
|
||||||
init: async (nodeTypes?: INodeTypeData): Promise<void> => { },
|
init: async (nodeTypes?: INodeTypeData): Promise<void> => { },
|
||||||
getAll: (): INodeType[] => {
|
getAll: (): Array<INodeType | INodeVersionedType> => {
|
||||||
// Does not get used in Workflow so no need to return it
|
// Does not get used in Workflow so no need to return it
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
getByName: (nodeType: string): INodeType | undefined => {
|
getByName: (nodeType: string): INodeType | INodeVersionedType | undefined => {
|
||||||
const nodeTypeDescription = this.$store.getters.nodeType(nodeType);
|
const nodeTypeDescription = this.$store.getters.nodeType(nodeType);
|
||||||
|
|
||||||
if (nodeTypeDescription === null) {
|
if (nodeTypeDescription === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
description: nodeTypeDescription,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getByNameAndVersion: (nodeType: string, version?: number): INodeType | undefined => {
|
||||||
|
const nodeTypeDescription = this.$store.getters.nodeType(nodeType, version);
|
||||||
|
|
||||||
|
if (nodeTypeDescription === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description: nodeTypeDescription,
|
description: nodeTypeDescription,
|
||||||
};
|
};
|
||||||
|
@ -283,7 +295,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) as INodeTypeDescription;
|
const nodeType = this.$store.getters.nodeType(node.type, node.typeVersion) as INodeTypeDescription;
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -6,6 +6,7 @@ export const PLACEHOLDER_FILLED_AT_EXECUTION_TIME = '[filled at execution time]'
|
||||||
|
|
||||||
// workflows
|
// workflows
|
||||||
export const PLACEHOLDER_EMPTY_WORKFLOW_ID = '__EMPTY__';
|
export const PLACEHOLDER_EMPTY_WORKFLOW_ID = '__EMPTY__';
|
||||||
|
export const DEFAULT_NODETYPE_VERSION = 1;
|
||||||
export const DEFAULT_NEW_WORKFLOW_NAME = 'My workflow';
|
export const DEFAULT_NEW_WORKFLOW_NAME = 'My workflow';
|
||||||
export const MIN_WORKFLOW_NAME_LENGTH = 1;
|
export const MIN_WORKFLOW_NAME_LENGTH = 1;
|
||||||
export const MAX_WORKFLOW_NAME_LENGTH = 128;
|
export const MAX_WORKFLOW_NAME_LENGTH = 128;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
|
|
||||||
import { PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
|
import { PLACEHOLDER_EMPTY_WORKFLOW_ID, DEFAULT_NODETYPE_VERSION } from '@/constants';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IConnection,
|
IConnection,
|
||||||
|
@ -589,11 +589,10 @@ export const store = new Vuex.Store({
|
||||||
},
|
},
|
||||||
|
|
||||||
updateNodeTypes (state, nodeTypes: INodeTypeDescription[]) {
|
updateNodeTypes (state, nodeTypes: INodeTypeDescription[]) {
|
||||||
const updatedNodeNames = nodeTypes.map(node => node.name) as string[];
|
const oldNodesToKeep = state.nodeTypes.filter(node => !nodeTypes.find(n => n.name === node.name && n.version === node.version));
|
||||||
const oldNodesNotChanged = state.nodeTypes.filter(node => !updatedNodeNames.includes(node.name));
|
const newNodesState = [...oldNodesToKeep, ...nodeTypes];
|
||||||
const updatedNodes = [...oldNodesNotChanged, ...nodeTypes];
|
Vue.set(state, 'nodeTypes', newNodesState);
|
||||||
Vue.set(state, 'nodeTypes', updatedNodes);
|
state.nodeTypes = newNodesState;
|
||||||
state.nodeTypes = updatedNodes;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addSidebarMenuItems (state, menuItems: IMenuItem[]) {
|
addSidebarMenuItems (state, menuItems: IMenuItem[]) {
|
||||||
|
@ -754,9 +753,9 @@ export const store = new Vuex.Store({
|
||||||
allNodeTypes: (state): INodeTypeDescription[] => {
|
allNodeTypes: (state): INodeTypeDescription[] => {
|
||||||
return state.nodeTypes;
|
return state.nodeTypes;
|
||||||
},
|
},
|
||||||
nodeType: (state) => (nodeType: string): INodeTypeDescription | null => {
|
nodeType: (state, getters) => (nodeType: string, typeVersion?: number): INodeTypeDescription | null => {
|
||||||
const foundType = state.nodeTypes.find(typeData => {
|
const foundType = state.nodeTypes.find(typeData => {
|
||||||
return typeData.name === nodeType;
|
return typeData.name === nodeType && typeData.version === (typeVersion || typeData.defaultVersion || DEFAULT_NODETYPE_VERSION);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (foundType === undefined) {
|
if (foundType === undefined) {
|
||||||
|
|
|
@ -142,6 +142,8 @@ import {
|
||||||
INodeConnections,
|
INodeConnections,
|
||||||
INodeIssues,
|
INodeIssues,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
|
INodeTypeNameVersion,
|
||||||
|
NodeInputConnections,
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
Workflow,
|
Workflow,
|
||||||
IRun,
|
IRun,
|
||||||
|
@ -1867,13 +1869,13 @@ export default mixins(
|
||||||
// Before proceeding we must check if all nodes contain the `properties` attribute.
|
// Before proceeding we must check if all nodes contain the `properties` attribute.
|
||||||
// Nodes are loaded without this information so we must make sure that all nodes
|
// Nodes are loaded without this information so we must make sure that all nodes
|
||||||
// being added have this information.
|
// being added have this information.
|
||||||
await this.loadNodesProperties(nodes.map(node => node.type));
|
await this.loadNodesProperties(nodes.map(node => ({name: node.type, version: node.typeVersion})));
|
||||||
|
|
||||||
// Add the node to the node-list
|
// Add the node to the node-list
|
||||||
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);
|
nodeType = this.$store.getters.nodeType(node.type, node.typeVersion);
|
||||||
|
|
||||||
// Make sure that some properties always exist
|
// Make sure that some properties always exist
|
||||||
if (!node.hasOwnProperty('disabled')) {
|
if (!node.hasOwnProperty('disabled')) {
|
||||||
|
@ -1980,7 +1982,7 @@ export default mixins(
|
||||||
let newName: string;
|
let newName: string;
|
||||||
const createNodes: INode[] = [];
|
const createNodes: INode[] = [];
|
||||||
|
|
||||||
await this.loadNodesProperties(data.nodes.map(node => node.type));
|
await this.loadNodesProperties(data.nodes.map(node => ({name: node.type, version: node.typeVersion})));
|
||||||
|
|
||||||
data.nodes.forEach(node => {
|
data.nodes.forEach(node => {
|
||||||
if (nodeTypesCount[node.type] !== undefined) {
|
if (nodeTypesCount[node.type] !== undefined) {
|
||||||
|
@ -2206,9 +2208,19 @@ export default mixins(
|
||||||
async loadCredentials (): Promise<void> {
|
async loadCredentials (): Promise<void> {
|
||||||
await this.$store.dispatch('credentials/fetchAllCredentials');
|
await this.$store.dispatch('credentials/fetchAllCredentials');
|
||||||
},
|
},
|
||||||
async loadNodesProperties(nodeNames: string[]): Promise<void> {
|
async loadNodesProperties(nodeInfos: INodeTypeNameVersion[]): Promise<void> {
|
||||||
const allNodes = this.$store.getters.allNodeTypes;
|
const allNodes:INodeTypeDescription[] = this.$store.getters.allNodeTypes;
|
||||||
const nodesToBeFetched = allNodes.filter((node: INodeTypeDescription) => nodeNames.includes(node.name) && !node.hasOwnProperty('properties')).map((node: INodeTypeDescription) => node.name) as string[];
|
|
||||||
|
const nodesToBeFetched:INodeTypeNameVersion[] = [];
|
||||||
|
allNodes.forEach(node => {
|
||||||
|
if(!!nodeInfos.find(n => n.name === node.name && n.version === node.version) && !node.hasOwnProperty('properties')) {
|
||||||
|
nodesToBeFetched.push({
|
||||||
|
name: node.name,
|
||||||
|
version: node.version,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
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();
|
||||||
|
|
|
@ -133,7 +133,7 @@ export class DeepL implements INodeType {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
responseData.push({ error: error.message });
|
responseData.push({ $error: error, $json: this.getInputData(i)});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
{
|
{
|
||||||
"node": "n8n-nodes-base.httpRequest",
|
"node": "n8n-nodes-base.httpRequest",
|
||||||
"nodeVersion": "1.0",
|
"nodeVersion": "1.0",
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
import {
|
|
||||||
IExecuteFunctions,
|
|
||||||
IHookFunctions,
|
|
||||||
ILoadOptionsFunctions,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
|
||||||
OptionsWithUri,
|
|
||||||
} from 'request';
|
|
||||||
|
|
||||||
import {
|
|
||||||
IDataObject, NodeApiError, NodeOperationError,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
export interface IAttachment {
|
|
||||||
fields: {
|
|
||||||
item?: object[];
|
|
||||||
};
|
|
||||||
actions: {
|
|
||||||
item?: object[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make an API request to Telegram
|
|
||||||
*
|
|
||||||
* @param {IHookFunctions} this
|
|
||||||
* @param {string} method
|
|
||||||
* @param {string} url
|
|
||||||
* @param {object} body
|
|
||||||
* @returns {Promise<any>}
|
|
||||||
*/
|
|
||||||
export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, query?: IDataObject): Promise<any> { // tslint:disable-line:no-any
|
|
||||||
const credentials = await this.getCredentials('mattermostApi');
|
|
||||||
|
|
||||||
if (credentials === undefined) {
|
|
||||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
|
||||||
}
|
|
||||||
|
|
||||||
query = query || {};
|
|
||||||
|
|
||||||
const options: OptionsWithUri = {
|
|
||||||
method,
|
|
||||||
body,
|
|
||||||
qs: query,
|
|
||||||
uri: `${credentials.baseUrl}/api/v4/${endpoint}`,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${credentials.accessToken}`,
|
|
||||||
'content-type': 'application/json; charset=utf-8',
|
|
||||||
},
|
|
||||||
json: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await this.helpers.request!(options);
|
|
||||||
} catch (error) {
|
|
||||||
throw new NodeApiError(this.getNode(), error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function apiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
|
||||||
|
|
||||||
const returnData: IDataObject[] = [];
|
|
||||||
|
|
||||||
let responseData;
|
|
||||||
query.page = 0;
|
|
||||||
query.per_page = 100;
|
|
||||||
|
|
||||||
do {
|
|
||||||
responseData = await apiRequest.call(this, method, endpoint, body, query);
|
|
||||||
query.page++;
|
|
||||||
returnData.push.apply(returnData, responseData);
|
|
||||||
} while (
|
|
||||||
responseData.length !== 0
|
|
||||||
);
|
|
||||||
|
|
||||||
return returnData;
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
34
packages/nodes-base/nodes/Mattermost/v1/MattermostV1.node.ts
Normal file
34
packages/nodes-base/nodes/Mattermost/v1/MattermostV1.node.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
INodeType,
|
||||||
|
INodeTypeBaseDescription,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { versionDescription } from './actions/versionDescription';
|
||||||
|
import { loadOptions } from './methods';
|
||||||
|
import { router } from './actions/router';
|
||||||
|
|
||||||
|
export class MattermostV1 implements INodeType {
|
||||||
|
|
||||||
|
description: INodeTypeDescription;
|
||||||
|
|
||||||
|
constructor(baseDescription: INodeTypeBaseDescription) {
|
||||||
|
this.description = {
|
||||||
|
...baseDescription,
|
||||||
|
...versionDescription,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
methods = { loadOptions };
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions) {
|
||||||
|
// Router returns INodeExecutionData[]
|
||||||
|
// We need to output INodeExecutionData[][]
|
||||||
|
// So we wrap in []
|
||||||
|
return [await router.call(this)];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import {
|
||||||
|
AllEntities,
|
||||||
|
Entity,
|
||||||
|
PropertiesOf,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
type MattermostMap = {
|
||||||
|
channel: 'addUser' | 'create' | 'delete' | 'members' | 'restore' | 'statistics';
|
||||||
|
message: 'delete' | 'post' | 'postEphemeral';
|
||||||
|
reaction: 'create' | 'delete' | 'getAll';
|
||||||
|
user: 'create' | 'deactive' | 'getAll' | 'getByEmail' | 'getById' | 'invite';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Mattermost = AllEntities<MattermostMap>;
|
||||||
|
|
||||||
|
export type MattermostChannel = Entity<MattermostMap, 'channel'>;
|
||||||
|
export type MattermostMessage = Entity<MattermostMap, 'message'>;
|
||||||
|
export type MattermostReaction = Entity<MattermostMap, 'reaction'>;
|
||||||
|
export type MattermostUser = Entity<MattermostMap, 'user'>;
|
||||||
|
|
||||||
|
export type ChannelProperties = PropertiesOf<MattermostChannel>;
|
||||||
|
export type MessageProperties = PropertiesOf<MattermostMessage>;
|
||||||
|
export type ReactionProperties = PropertiesOf<MattermostReaction>;
|
||||||
|
export type UserProperties = PropertiesOf<MattermostUser>;
|
||||||
|
|
||||||
|
export interface IAttachment {
|
||||||
|
fields: {
|
||||||
|
item?: object[];
|
||||||
|
};
|
||||||
|
actions: {
|
||||||
|
item?: object[];
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
import {
|
||||||
|
ChannelProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const channelAddUserDescription: ChannelProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Channel ID',
|
||||||
|
name: 'channelId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getChannels',
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'addUser',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The ID of the channel to invite user to.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'User ID',
|
||||||
|
name: 'userId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getUsers',
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'addUser',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The ID of the user to invite into channel.',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,27 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function addUser(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const channelId = this.getNodeParameter('channelId', index) as string;
|
||||||
|
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'POST';
|
||||||
|
const endpoint = `channels/${channelId}/members`;
|
||||||
|
|
||||||
|
body.user_id = this.getNodeParameter('userId', index) as string;
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { addUser as execute } from './execute';
|
||||||
|
import { channelAddUserDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,93 @@
|
||||||
|
import {
|
||||||
|
ChannelProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const channelCreateDescription: ChannelProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Team ID',
|
||||||
|
name: 'teamId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTeams',
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The Mattermost Team.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Display Name',
|
||||||
|
name: 'displayName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'Announcements',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
description: 'The non-unique UI name for the channel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'channel',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'announcements',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
description: 'The unique handle for the channel, will be present in the channel URL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Type',
|
||||||
|
name: 'type',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Private',
|
||||||
|
value: 'private',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Public',
|
||||||
|
value: 'public',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'public',
|
||||||
|
description: 'The type of channel to create.',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,30 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function create(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'POST';
|
||||||
|
const endpoint = 'channels';
|
||||||
|
|
||||||
|
const type = this.getNodeParameter('type', index) as string;
|
||||||
|
|
||||||
|
body.team_id = this.getNodeParameter('teamId', index) as string;
|
||||||
|
body.display_name = this.getNodeParameter('displayName', index) as string;
|
||||||
|
body.name = this.getNodeParameter('channel', index) as string;
|
||||||
|
body.type = type === 'public' ? 'O' : 'P';
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { create as execute } from './execute';
|
||||||
|
import { channelCreateDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
import {
|
||||||
|
ChannelProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const channelDeleteDescription: ChannelProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Channel ID',
|
||||||
|
name: 'channelId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getChannels',
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'delete',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The ID of the channel to soft delete',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,25 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function del(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const channelId = this.getNodeParameter('channelId', index) as string;
|
||||||
|
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'DELETE';
|
||||||
|
const endpoint = `channels/${channelId}`;
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { del as execute } from './execute';
|
||||||
|
import { channelDeleteDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,72 @@
|
||||||
|
import * as create from './create';
|
||||||
|
import * as del from './del';
|
||||||
|
import * as members from './members';
|
||||||
|
import * as restore from './restore';
|
||||||
|
import * as addUser from './addUser';
|
||||||
|
import * as statistics from './statistics';
|
||||||
|
import { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export {
|
||||||
|
create,
|
||||||
|
del as delete,
|
||||||
|
members,
|
||||||
|
restore,
|
||||||
|
addUser,
|
||||||
|
statistics,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const descriptions = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Add User',
|
||||||
|
value: 'addUser',
|
||||||
|
description: 'Add a user to a channel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a new channel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Soft delete a channel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Member',
|
||||||
|
value: 'members',
|
||||||
|
description: 'Get a page of members for a channel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Restore',
|
||||||
|
value: 'restore',
|
||||||
|
description: 'Restores a soft deleted channel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Statistics',
|
||||||
|
value: 'statistics',
|
||||||
|
description: 'Get statistics for a channel',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
...create.description,
|
||||||
|
...del.description,
|
||||||
|
...members.description,
|
||||||
|
...restore.description,
|
||||||
|
...addUser.description,
|
||||||
|
...statistics.description,
|
||||||
|
] as INodeProperties[];
|
|
@ -0,0 +1,112 @@
|
||||||
|
import {
|
||||||
|
ChannelProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const channelMembersDescription: ChannelProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Team ID',
|
||||||
|
name: 'teamId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTeams',
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'members',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The Mattermost Team.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Channel ID',
|
||||||
|
name: 'channelId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getChannelsInTeam',
|
||||||
|
loadOptionsDependsOn: [
|
||||||
|
'teamId',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'members',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The Mattermost Team.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Resolve Data',
|
||||||
|
name: 'resolveData',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'members',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: true,
|
||||||
|
description: 'By default the response only contain the ID of the user.<br />If this option gets activated it will resolve the user automatically.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'members',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: true,
|
||||||
|
description: 'If all results should be returned or only up to a given limit.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'members',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
returnAll: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 100,
|
||||||
|
},
|
||||||
|
default: 100,
|
||||||
|
description: 'How many results to return.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
apiRequestAllItems,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function members(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const channelId = this.getNodeParameter('channelId', index) as string;
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', index) as boolean;
|
||||||
|
const resolveData = this.getNodeParameter('resolveData', index) as boolean;
|
||||||
|
const limit = this.getNodeParameter('limit', index, 0) as number;
|
||||||
|
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'GET';
|
||||||
|
const endpoint = `channels/${channelId}/members`;
|
||||||
|
|
||||||
|
if (returnAll === false) {
|
||||||
|
qs.per_page = this.getNodeParameter('limit', index) as number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await apiRequestAllItems.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
} else {
|
||||||
|
responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
if (limit) {
|
||||||
|
responseData = responseData.slice(0, limit);
|
||||||
|
}
|
||||||
|
if (resolveData) {
|
||||||
|
const userIds: string[] = [];
|
||||||
|
for (const data of responseData) {
|
||||||
|
userIds.push(data.user_id);
|
||||||
|
}
|
||||||
|
if (userIds.length > 0) {
|
||||||
|
responseData = await apiRequest.call(this, 'POST', 'users/ids', userIds, qs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { members as execute } from './execute';
|
||||||
|
import { channelMembersDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
import {
|
||||||
|
ChannelProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const channelRestoreDescription: ChannelProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Channel ID',
|
||||||
|
name: 'channelId',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'restore',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The ID of the channel to restore.',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,27 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
apiRequestAllItems,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function restore(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const channelId = this.getNodeParameter('channelId', index) as string;
|
||||||
|
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'POST';
|
||||||
|
const endpoint = `channels/${channelId}/restore`;
|
||||||
|
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { restore as execute } from './execute';
|
||||||
|
import { channelRestoreDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,29 @@
|
||||||
|
import {
|
||||||
|
ChannelProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const channelStatisticsDescription: ChannelProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Channel ID',
|
||||||
|
name: 'channelId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getChannels',
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'statistics',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The ID of the channel to get the statistics from.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function statistics(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const channelId = this.getNodeParameter('channelId', index) as string;
|
||||||
|
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'GET';
|
||||||
|
const endpoint = `channels/${channelId}/stats`;
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { statistics as execute } from './execute';
|
||||||
|
import { channelStatisticsDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
import {
|
||||||
|
MessageProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const messageDeleteDescription: MessageProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Post ID',
|
||||||
|
name: 'postId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'delete',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'ID of the post to delete',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,25 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function del(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const postId = this.getNodeParameter('postId', index) as string;
|
||||||
|
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'DELETE';
|
||||||
|
const endpoint = `posts/${postId}`;
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { del as execute } from './execute';
|
||||||
|
import { messageDeleteDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,48 @@
|
||||||
|
import * as del from './del';
|
||||||
|
import * as post from './post';
|
||||||
|
import * as postEphemeral from './postEphemeral';
|
||||||
|
|
||||||
|
import { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export {
|
||||||
|
del as delete,
|
||||||
|
post,
|
||||||
|
postEphemeral,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const descriptions = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Soft delete a post, by marking the post as deleted in the database',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Post',
|
||||||
|
value: 'post',
|
||||||
|
description: 'Post a message into a channel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Post Ephemeral',
|
||||||
|
value: 'postEphemeral',
|
||||||
|
description: 'Post an ephemeral message into a channel',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'post',
|
||||||
|
description: 'The operation to perform',
|
||||||
|
},
|
||||||
|
...del.description,
|
||||||
|
...post.description,
|
||||||
|
...postEphemeral.description,
|
||||||
|
] as INodeProperties[];
|
|
@ -0,0 +1,440 @@
|
||||||
|
import {
|
||||||
|
MessageProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const messagePostDescription: MessageProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Channel ID',
|
||||||
|
name: 'channelId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getChannels',
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'post',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The ID of the channel to post to.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message',
|
||||||
|
name: 'message',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'post',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The text to send.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Attachments',
|
||||||
|
name: 'attachments',
|
||||||
|
type: 'collection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
multipleValueButtonText: 'Add attachment',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'post',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
description: 'The attachment to add',
|
||||||
|
placeholder: 'Add attachment item',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Actions',
|
||||||
|
name: 'actions',
|
||||||
|
placeholder: 'Add Actions',
|
||||||
|
description: 'Actions to add to message. More information can be found <a href="https://docs.mattermost.com/developer/interactive-messages.html" target="_blank">here</a>',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Item',
|
||||||
|
name: 'item',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Type',
|
||||||
|
name: 'type',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Button',
|
||||||
|
value: 'button',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Select',
|
||||||
|
value: 'select',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'button',
|
||||||
|
description: 'The type of the action.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Data Source',
|
||||||
|
name: 'data_source',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
type: [
|
||||||
|
'select',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Channels',
|
||||||
|
value: 'channels',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Custom',
|
||||||
|
value: 'custom',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Users',
|
||||||
|
value: 'users',
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
default: 'custom',
|
||||||
|
description: 'The type of the action.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
description: 'Adds a new option to select field.',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
data_source: [
|
||||||
|
'custom',
|
||||||
|
],
|
||||||
|
type: [
|
||||||
|
'select',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'option',
|
||||||
|
displayName: 'Option',
|
||||||
|
default: {},
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Option Text',
|
||||||
|
name: 'text',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Text of the option.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Option Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value of the option.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the Action.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Integration',
|
||||||
|
name: 'integration',
|
||||||
|
placeholder: 'Add Integration',
|
||||||
|
description: 'Integration to add to message.',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: false,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Item',
|
||||||
|
name: 'item',
|
||||||
|
default: {},
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'URL of the Integration.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Context',
|
||||||
|
name: 'context',
|
||||||
|
placeholder: 'Add Context to Integration',
|
||||||
|
description: 'Adds a Context values set.',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'property',
|
||||||
|
displayName: 'Property',
|
||||||
|
default: {},
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Property Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the property to set.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Property Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value of the property to set.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Author Icon',
|
||||||
|
name: 'author_icon',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Icon which should appear for the user.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Author Link',
|
||||||
|
name: 'author_link',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Link for the author.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Author Name',
|
||||||
|
name: 'author_name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name that should appear.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Color',
|
||||||
|
name: 'color',
|
||||||
|
type: 'color',
|
||||||
|
default: '#ff0000',
|
||||||
|
description: 'Color of the line left of text.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Fallback Text',
|
||||||
|
name: 'fallback',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Required plain-text summary of the attachment.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
placeholder: 'Add Fields',
|
||||||
|
description: 'Fields to add to message.',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'item',
|
||||||
|
displayName: 'Item',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Title',
|
||||||
|
name: 'title',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Title of the item.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value of the item.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Short',
|
||||||
|
name: 'short',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description: 'If items can be displayed next to each other.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Footer',
|
||||||
|
name: 'footer',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Text of footer to add.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Footer Icon',
|
||||||
|
name: 'footer_icon',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Icon which should appear next to footer.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Image URL',
|
||||||
|
name: 'image_url',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'URL of image.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Pretext',
|
||||||
|
name: 'pretext',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Text which appears before the message block.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Text',
|
||||||
|
name: 'text',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Text to send.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Thumbnail URL',
|
||||||
|
name: 'thumb_url',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'URL of thumbnail.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Title',
|
||||||
|
name: 'title',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Title of the message.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Title Link',
|
||||||
|
name: 'title_link',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Link of the title.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Other Options',
|
||||||
|
name: 'otherOptions',
|
||||||
|
type: 'collection',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'post',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
description: 'Other options to set',
|
||||||
|
placeholder: 'Add options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Make Comment',
|
||||||
|
name: 'root_id',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The post ID to comment on',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,98 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IAttachment,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export async function post(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'POST';
|
||||||
|
const endpoint = `posts`;
|
||||||
|
|
||||||
|
body.channel_id = this.getNodeParameter('channelId', index) as string;
|
||||||
|
body.message = this.getNodeParameter('message', index) as string;
|
||||||
|
|
||||||
|
const attachments = this.getNodeParameter('attachments', index, []) as unknown as IAttachment[];
|
||||||
|
// The node does save the fields data differently than the API
|
||||||
|
// expects so fix the data befre we send the request
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
if (attachment.fields !== undefined) {
|
||||||
|
if (attachment.fields.item !== undefined) {
|
||||||
|
// Move the field-content up
|
||||||
|
// @ts-ignore
|
||||||
|
attachment.fields = attachment.fields.item;
|
||||||
|
} else {
|
||||||
|
// If it does not have any items set remove it
|
||||||
|
// @ts-ignore
|
||||||
|
delete attachment.fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
if (attachment.actions !== undefined) {
|
||||||
|
if (attachment.actions.item !== undefined) {
|
||||||
|
// Move the field-content up
|
||||||
|
// @ts-ignore
|
||||||
|
attachment.actions = attachment.actions.item;
|
||||||
|
} else {
|
||||||
|
// If it does not have any items set remove it
|
||||||
|
// @ts-ignore
|
||||||
|
delete attachment.actions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
if (Array.isArray(attachment.actions)) {
|
||||||
|
for (const attaction of attachment.actions) {
|
||||||
|
|
||||||
|
if (attaction.type === 'button') {
|
||||||
|
delete attaction.type;
|
||||||
|
}
|
||||||
|
if (attaction.data_source === 'custom') {
|
||||||
|
delete attaction.data_source;
|
||||||
|
}
|
||||||
|
if (attaction.options) {
|
||||||
|
attaction.options = attaction.options.option;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attaction.integration.item !== undefined) {
|
||||||
|
attaction.integration = attaction.integration.item;
|
||||||
|
if (Array.isArray(attaction.integration.context.property)) {
|
||||||
|
const tmpcontex = {};
|
||||||
|
for (const attactionintegprop of attaction.integration.context.property) {
|
||||||
|
Object.assign(tmpcontex, { [attactionintegprop.name]: attactionintegprop.value });
|
||||||
|
}
|
||||||
|
delete attaction.integration.context;
|
||||||
|
attaction.integration.context = tmpcontex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.props = {
|
||||||
|
attachments,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add all the other options to the request
|
||||||
|
const otherOptions = this.getNodeParameter('otherOptions', index) as IDataObject;
|
||||||
|
Object.assign(body, otherOptions);
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { post as execute } from './execute';
|
||||||
|
import { messagePostDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,69 @@
|
||||||
|
import {
|
||||||
|
MessageProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const messagePostEphemeralDescription: MessageProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'User ID',
|
||||||
|
name: 'userId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getUsers',
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'postEphemeral',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'ID of the user to send the ephemeral message to.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Channel ID',
|
||||||
|
name: 'channelId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getChannels',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'postEphemeral',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'ID of the channel to send the ephemeral message in.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message',
|
||||||
|
name: 'message',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'postEphemeral',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'Text to send in the ephemeral message.',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,30 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function postEphemeral(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'POST';
|
||||||
|
const endpoint = `posts/ephemeral`;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
user_id: this.getNodeParameter('userId', index),
|
||||||
|
post: {
|
||||||
|
channel_id: this.getNodeParameter('channelId', index),
|
||||||
|
message: this.getNodeParameter('message', index),
|
||||||
|
},
|
||||||
|
} as IDataObject;
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { postEphemeral as execute } from './execute';
|
||||||
|
import { messagePostEphemeralDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,65 @@
|
||||||
|
import {
|
||||||
|
ReactionProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const reactionCreateDescription: ReactionProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'User ID',
|
||||||
|
name: 'userId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getUsers',
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'reaction',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'ID of the user sending the reaction.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Post ID',
|
||||||
|
name: 'postId',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: '3moacfqxmbdw38r38fjprh6zsr',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'reaction',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'ID of the post to react to.<br>Obtainable from the post link:<br><code>https://mattermost.internal.n8n.io/[server]/pl/[postId]</code>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Emoji Name',
|
||||||
|
name: 'emojiName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'reaction',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'Emoji to use for this reaction.',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,29 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function create(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'POST';
|
||||||
|
const endpoint = 'reactions';
|
||||||
|
const body = {
|
||||||
|
user_id: this.getNodeParameter('userId', index),
|
||||||
|
post_id: this.getNodeParameter('postId', index),
|
||||||
|
emoji_name: (this.getNodeParameter('emojiName', index) as string).replace(/:/g, ''),
|
||||||
|
create_at: Date.now(),
|
||||||
|
} as { user_id: string; post_id: string; emoji_name: string; create_at: number };
|
||||||
|
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { create as execute } from './execute';
|
||||||
|
import { reactionCreateDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,65 @@
|
||||||
|
import {
|
||||||
|
ReactionProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const reactionDeleteDescription: ReactionProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'User ID',
|
||||||
|
name: 'userId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getUsers',
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'reaction',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'delete',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'ID of the user whose reaction to delete.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Post ID',
|
||||||
|
name: 'postId',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: '3moacfqxmbdw38r38fjprh6zsr',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'reaction',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'delete',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'ID of the post whose reaction to delete.<br>Obtainable from the post link:<br><code>https://mattermost.internal.n8n.io/[server]/pl/[postId]</code>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Emoji Name',
|
||||||
|
name: 'emojiName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'reaction',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'delete',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'Name of the emoji to delete.',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,27 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function del(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const userId = this.getNodeParameter('userId', index) as string;
|
||||||
|
const postId = this.getNodeParameter('postId', index) as string;
|
||||||
|
const emojiName = (this.getNodeParameter('emojiName', index) as string).replace(/:/g, '');
|
||||||
|
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'DELETE';
|
||||||
|
const endpoint = `users/${userId}/posts/${postId}/reactions/${emojiName}`;
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { del as execute } from './execute';
|
||||||
|
import { reactionDeleteDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,65 @@
|
||||||
|
import {
|
||||||
|
ReactionProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const reactionGetAllDescription: ReactionProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Post ID',
|
||||||
|
name: 'postId',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'reaction',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'One or more (comma-separated) posts to retrieve reactions from.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'reaction',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: true,
|
||||||
|
description: 'If all results should be returned or only up to a given limit.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'reaction',
|
||||||
|
],
|
||||||
|
returnAll: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 100,
|
||||||
|
},
|
||||||
|
default: 100,
|
||||||
|
description: 'How many results to return.',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,28 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function getAll(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const postId = this.getNodeParameter('postId', index) as string;
|
||||||
|
const limit = this.getNodeParameter('limit', 0, 0) as number;
|
||||||
|
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'GET';
|
||||||
|
const endpoint = `posts/${postId}/reactions`;
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
|
||||||
|
let responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
if (limit > 0) {
|
||||||
|
responseData = responseData.slice(0, limit);
|
||||||
|
}
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { getAll as execute } from './execute';
|
||||||
|
import { reactionGetAllDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,49 @@
|
||||||
|
import * as create from './create';
|
||||||
|
import * as del from './del';
|
||||||
|
import * as getAll from './getAll';
|
||||||
|
|
||||||
|
import { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
create,
|
||||||
|
del as delete,
|
||||||
|
getAll,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const descriptions = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'reaction',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Add a reaction to a post.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Remove a reaction from a post',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all the reactions to one or more posts',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
description: 'The operation to perform',
|
||||||
|
},
|
||||||
|
...create.description,
|
||||||
|
...del.description,
|
||||||
|
...getAll.description,
|
||||||
|
] as INodeProperties[];
|
53
packages/nodes-base/nodes/Mattermost/v1/actions/router.ts
Normal file
53
packages/nodes-base/nodes/Mattermost/v1/actions/router.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import * as channel from './channel';
|
||||||
|
import * as message from './message';
|
||||||
|
import * as reaction from './reaction';
|
||||||
|
import * as user from './user';
|
||||||
|
import { Mattermost } from './Interfaces';
|
||||||
|
|
||||||
|
export async function router(this: IExecuteFunctions): Promise<INodeExecutionData[]> {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const operationResult: INodeExecutionData[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const resource = this.getNodeParameter<Mattermost>('resource', i);
|
||||||
|
let operation = this.getNodeParameter('operation', i);
|
||||||
|
if (operation === 'del') {
|
||||||
|
operation = 'delete';
|
||||||
|
} else if (operation === 'desactive') {
|
||||||
|
operation = 'deactive';
|
||||||
|
}
|
||||||
|
|
||||||
|
const mattermost = {
|
||||||
|
resource,
|
||||||
|
operation,
|
||||||
|
} as Mattermost;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (mattermost.resource === 'channel') {
|
||||||
|
operationResult.push(...await channel[mattermost.operation].execute.call(this, i));
|
||||||
|
} else if (mattermost.resource === 'message') {
|
||||||
|
operationResult.push(...await message[mattermost.operation].execute.call(this, i));
|
||||||
|
} else if (mattermost.resource === 'reaction') {
|
||||||
|
operationResult.push(...await reaction[mattermost.operation].execute.call(this, i));
|
||||||
|
} else if (mattermost.resource === 'user') {
|
||||||
|
operationResult.push(...await user[mattermost.operation].execute.call(this, i));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (this.continueOnFail()) {
|
||||||
|
operationResult.push({json: this.getInputData(i)[0].json, error: err});
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return operationResult;
|
||||||
|
}
|
|
@ -0,0 +1,270 @@
|
||||||
|
import {
|
||||||
|
UserProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const userCreateDescription: UserProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Username',
|
||||||
|
name: 'username',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Auth Service',
|
||||||
|
name: 'authService',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Email',
|
||||||
|
value: 'email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Gitlab',
|
||||||
|
value: 'gitlab',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Google',
|
||||||
|
value: 'google',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'LDAP',
|
||||||
|
value: 'ldap',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Office365',
|
||||||
|
value: 'office365',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'SAML',
|
||||||
|
value: 'saml',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Auth Data',
|
||||||
|
name: 'authData',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
hide: {
|
||||||
|
authService: [
|
||||||
|
'email',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
authService: [
|
||||||
|
'email',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Password',
|
||||||
|
name: 'password',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
authService: [
|
||||||
|
'email',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The password used for email authentication.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'First Name',
|
||||||
|
name: 'first_name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Last Name',
|
||||||
|
name: 'last_name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Locale',
|
||||||
|
name: 'locale',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Nickname',
|
||||||
|
name: 'nickname',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Notification Settings',
|
||||||
|
name: 'notificationUi',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Notification Setting',
|
||||||
|
default: {},
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: false,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Notify',
|
||||||
|
name: 'notificationValues',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Channel',
|
||||||
|
name: 'channel',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description: `Set to "true" to enable channel-wide notifications (@channel, @all, etc.), "false" to disable. Defaults to "true".`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Desktop',
|
||||||
|
name: 'desktop',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'All',
|
||||||
|
value: 'all',
|
||||||
|
description: 'Notifications for all activity',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mention',
|
||||||
|
value: 'mention',
|
||||||
|
description: 'Mentions and direct messages only',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'None',
|
||||||
|
value: 'none',
|
||||||
|
description: 'Mentions and direct messages only',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'all',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Desktop Sound',
|
||||||
|
name: 'desktop_sound',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description: `Set to "true" to enable sound on desktop notifications, "false" to disable. Defaults to "true".`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: `Set to "true" to enable email notifications, "false" to disable. Defaults to "true".`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'First Name',
|
||||||
|
name: 'first_name',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: `Set to "true" to enable mentions for first name. Defaults to "true" if a first name is set, "false" otherwise.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Mention Keys',
|
||||||
|
name: 'mention_keys',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: `A comma-separated list of words to count as mentions. Defaults to username and @username.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Push',
|
||||||
|
name: 'push',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'All',
|
||||||
|
value: 'all',
|
||||||
|
description: 'Notifications for all activity',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mention',
|
||||||
|
value: 'mention',
|
||||||
|
description: 'Mentions and direct messages only',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'None',
|
||||||
|
value: 'none',
|
||||||
|
description: 'Mentions and direct messages only',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'mention',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,43 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function create(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const username = this.getNodeParameter('username', index) as string;
|
||||||
|
const authService = this.getNodeParameter('authService', index) as string;
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', index) as IDataObject;
|
||||||
|
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'POST';
|
||||||
|
const endpoint = 'users';
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
|
||||||
|
body.auth_service = authService;
|
||||||
|
|
||||||
|
body.username = username;
|
||||||
|
Object.assign(body, additionalFields);
|
||||||
|
|
||||||
|
if (body.notificationUi) {
|
||||||
|
body.notify_props = (body.notificationUi as IDataObject).notificationValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authService === 'email') {
|
||||||
|
body.email = this.getNodeParameter('email', index) as string;
|
||||||
|
body.password = this.getNodeParameter('password', index) as string;
|
||||||
|
} else {
|
||||||
|
body.auth_data = this.getNodeParameter('authData', index) as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { create as execute } from './execute';
|
||||||
|
import { userCreateDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
import {
|
||||||
|
UserProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const userDeactiveDescription: UserProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'User ID',
|
||||||
|
name: 'userId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'deactive',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'User GUID',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,24 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function deactive(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const userId = this.getNodeParameter('userId', index) as string;
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'DELETE';
|
||||||
|
const endpoint = `users/${userId}`;
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { deactive as execute } from './execute';
|
||||||
|
import { userDeactiveDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,119 @@
|
||||||
|
import {
|
||||||
|
UserProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const userGetAllDescription: UserProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: true,
|
||||||
|
description: 'If all results should be returned or only up to a given limit.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
returnAll: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 100,
|
||||||
|
},
|
||||||
|
default: 100,
|
||||||
|
description: 'How many results to return.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'In Channel',
|
||||||
|
name: 'inChannel',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The ID of the channel to get users for.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'In Team',
|
||||||
|
name: 'inTeam',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The ID of the team to get users for.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Not In Team',
|
||||||
|
name: 'notInTeam',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The ID of the team to exclude users for.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Not In Channel',
|
||||||
|
name: 'notInChannel',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The ID of the channel to exclude users for.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sort',
|
||||||
|
name: 'sort',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Created At',
|
||||||
|
value: 'createdAt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Last Activity At',
|
||||||
|
value: 'lastActivityAt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Status',
|
||||||
|
value: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'username',
|
||||||
|
value: 'username',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'username',
|
||||||
|
description: 'The ID of the channel to exclude users for.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,98 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
NodeOperationError,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
apiRequestAllItems,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
import {
|
||||||
|
snakeCase,
|
||||||
|
} from 'change-case';
|
||||||
|
|
||||||
|
export async function getAll(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', index) as boolean;
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', index) as IDataObject;
|
||||||
|
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'GET';
|
||||||
|
const endpoint = '/users';
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
|
||||||
|
if (additionalFields.inTeam) {
|
||||||
|
qs.in_team = additionalFields.inTeam;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.notInTeam) {
|
||||||
|
qs.not_in_team = additionalFields.notInTeam;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.inChannel) {
|
||||||
|
qs.in_channel = additionalFields.inChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.notInChannel) {
|
||||||
|
qs.not_in_channel = additionalFields.notInChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.sort) {
|
||||||
|
qs.sort = snakeCase(additionalFields.sort as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
const validRules = {
|
||||||
|
inTeam: ['last_activity_at', 'created_at', 'username'],
|
||||||
|
inChannel: ['status', 'username'],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (additionalFields.sort) {
|
||||||
|
if (additionalFields.inTeam !== undefined || additionalFields.inChannel !== undefined) {
|
||||||
|
|
||||||
|
if (additionalFields.inTeam !== undefined
|
||||||
|
&& !validRules.inTeam.includes(snakeCase(additionalFields.sort as string))) {
|
||||||
|
throw new NodeOperationError(this.getNode(), `When In Team is set the only valid values for sorting are ${validRules.inTeam.join(',')}`);
|
||||||
|
}
|
||||||
|
if (additionalFields.inChannel !== undefined
|
||||||
|
&& !validRules.inChannel.includes(snakeCase(additionalFields.sort as string))) {
|
||||||
|
throw new NodeOperationError(this.getNode(), `When In Channel is set the only valid values for sorting are ${validRules.inChannel.join(',')}`);
|
||||||
|
}
|
||||||
|
if (additionalFields.inChannel === ''
|
||||||
|
&& additionalFields.sort !== 'username') {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'When sort is different than username In Channel must be set');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.inTeam === ''
|
||||||
|
&& additionalFields.sort !== 'username') {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'When sort is different than username In Team must be set');
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new NodeOperationError(this.getNode(), `When sort is defined either 'in team' or 'in channel' must be defined`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.sort === 'username') {
|
||||||
|
qs.sort = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnAll === false) {
|
||||||
|
qs.per_page = this.getNodeParameter('limit', index) as number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await apiRequestAllItems.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
} else {
|
||||||
|
responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { getAll as execute } from './execute';
|
||||||
|
import { userGetAllDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
import {
|
||||||
|
UserProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const userGetByEmailDescription: UserProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getByEmail',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: `User's email`,
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,25 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function getByEmail(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const email = this.getNodeParameter('email', index) as string;
|
||||||
|
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'GET';
|
||||||
|
const endpoint = `users/email/${email}`;
|
||||||
|
const body = {} as IDataObject;
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { getByEmail as execute } from './execute';
|
||||||
|
import { userGetByEmailDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,50 @@
|
||||||
|
import {
|
||||||
|
UserProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const userGetByIdDescription: UserProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'User IDs',
|
||||||
|
name: 'userIds',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getById',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: `User's ID`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getById',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Since',
|
||||||
|
name: 'since',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description: 'Only return users that have been modified since the given Unix timestamp (in milliseconds).',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,29 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function getById(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'POST';
|
||||||
|
const endpoint = 'users/ids';
|
||||||
|
const userIds = (this.getNodeParameter('userIds', index) as string).split(',') as string[];
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', index) as IDataObject;
|
||||||
|
const body = userIds;
|
||||||
|
|
||||||
|
if (additionalFields.since) {
|
||||||
|
qs.since = new Date(additionalFields.since as string).getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { getById as execute } from './execute';
|
||||||
|
import { userGetByIdDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,74 @@
|
||||||
|
import * as create from './create';
|
||||||
|
import * as deactive from './deactive';
|
||||||
|
import * as getAll from './getAll';
|
||||||
|
import * as getByEmail from './getByEmail';
|
||||||
|
import * as getById from './getById';
|
||||||
|
import * as invite from './invite';
|
||||||
|
|
||||||
|
import { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export {
|
||||||
|
create,
|
||||||
|
deactive,
|
||||||
|
getAll,
|
||||||
|
getByEmail,
|
||||||
|
getById,
|
||||||
|
invite,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const descriptions = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a new user',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Deactive',
|
||||||
|
value: 'deactive',
|
||||||
|
description: 'Deactivates the user and revokes all its sessions by archiving its user object.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Retrieve all users',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get By Email',
|
||||||
|
value: 'getByEmail',
|
||||||
|
description: 'Get a user by email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get By ID',
|
||||||
|
value: 'getById',
|
||||||
|
description: 'Get a user by id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Invite',
|
||||||
|
value: 'invite',
|
||||||
|
description: 'Invite user to team',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
...create.description,
|
||||||
|
...deactive.description,
|
||||||
|
...getAll.description,
|
||||||
|
...getByEmail.description,
|
||||||
|
...getById.description,
|
||||||
|
...invite.description,
|
||||||
|
] as INodeProperties[];
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import {
|
||||||
|
UserProperties,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
export const userInviteDescription: UserProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Team ID',
|
||||||
|
name: 'teamId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTeams',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'invite',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Emails',
|
||||||
|
name: 'emails',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'user',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'invite',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: `User's email. Multiple can be set separated by comma.`,
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,28 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../../../transport';
|
||||||
|
|
||||||
|
export async function invite(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
|
||||||
|
const teamId = this.getNodeParameter('teamId', index) as string;
|
||||||
|
|
||||||
|
const emails = (this.getNodeParameter('emails', index) as string).split(',');
|
||||||
|
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
const requestMethod = 'POST';
|
||||||
|
const endpoint = `teams/${teamId}/invite/email`;
|
||||||
|
const body = emails;
|
||||||
|
|
||||||
|
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { invite as execute } from './execute';
|
||||||
|
import { userInviteDescription as description } from './description';
|
||||||
|
|
||||||
|
export {
|
||||||
|
description,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -0,0 +1,61 @@
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import * as channel from './channel';
|
||||||
|
import * as message from './message';
|
||||||
|
import * as reaction from './reaction';
|
||||||
|
import * as user from './user';
|
||||||
|
|
||||||
|
export const versionDescription: INodeTypeDescription = {
|
||||||
|
displayName: 'Mattermost',
|
||||||
|
name: 'mattermost',
|
||||||
|
icon: 'file:mattermost.svg',
|
||||||
|
group: ['output'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
description: 'Sends data to Mattermost',
|
||||||
|
defaults: {
|
||||||
|
name: 'Mattermost',
|
||||||
|
color: '#000000',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'mattermostApi',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Channel',
|
||||||
|
value: 'channel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Message',
|
||||||
|
value: 'message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Reaction',
|
||||||
|
value: 'reaction',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'User',
|
||||||
|
value: 'user',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'message',
|
||||||
|
description: 'The resource to operate on',
|
||||||
|
},
|
||||||
|
...channel.descriptions,
|
||||||
|
...message.descriptions,
|
||||||
|
...reaction.descriptions,
|
||||||
|
...user.descriptions,
|
||||||
|
],
|
||||||
|
};
|
1
packages/nodes-base/nodes/Mattermost/v1/mattermost.svg
Normal file
1
packages/nodes-base/nodes/Mattermost/v1/mattermost.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.6 KiB |
1
packages/nodes-base/nodes/Mattermost/v1/methods/index.ts
Normal file
1
packages/nodes-base/nodes/Mattermost/v1/methods/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * as loadOptions from './loadOptions';
|
148
packages/nodes-base/nodes/Mattermost/v1/methods/loadOptions.ts
Normal file
148
packages/nodes-base/nodes/Mattermost/v1/methods/loadOptions.ts
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INodePropertyOptions,
|
||||||
|
NodeOperationError,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiRequest,
|
||||||
|
} from '../transport';
|
||||||
|
|
||||||
|
// Get all the available channels
|
||||||
|
export async function getChannels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const endpoint = 'channels';
|
||||||
|
const responseData = await apiRequest.call(this, 'GET', endpoint, {});
|
||||||
|
|
||||||
|
if (responseData === undefined) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'No data got returned');
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
let name: string;
|
||||||
|
for (const data of responseData) {
|
||||||
|
if (data.delete_at !== 0 || (!data.display_name || !data.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = `${data.team_display_name} - ${data.display_name || data.name} (${data.type === 'O' ? 'public' : 'private'})`;
|
||||||
|
|
||||||
|
returnData.push({
|
||||||
|
name,
|
||||||
|
value: data.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
returnData.sort((a, b) => {
|
||||||
|
if (a.name < b.name) { return -1; }
|
||||||
|
if (a.name > b.name) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all the channels in a team
|
||||||
|
export async function getChannelsInTeam(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const teamId = this.getCurrentNodeParameter('teamId');
|
||||||
|
const endpoint = `users/me/teams/${teamId}/channels`;
|
||||||
|
const responseData = await apiRequest.call(this, 'GET', endpoint, {});
|
||||||
|
|
||||||
|
if (responseData === undefined) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'No data got returned');
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
let name: string;
|
||||||
|
for (const data of responseData) {
|
||||||
|
if (data.delete_at !== 0 || (!data.display_name || !data.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelTypes: IDataObject = {
|
||||||
|
'D': 'direct',
|
||||||
|
'G': 'group',
|
||||||
|
'O': 'public',
|
||||||
|
'P': 'private',
|
||||||
|
};
|
||||||
|
|
||||||
|
name = `${data.display_name} (${channelTypes[data.type as string]})`;
|
||||||
|
|
||||||
|
returnData.push({
|
||||||
|
name,
|
||||||
|
value: data.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
returnData.sort((a, b) => {
|
||||||
|
if (a.name < b.name) { return -1; }
|
||||||
|
if (a.name > b.name) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTeams(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const endpoint = 'users/me/teams';
|
||||||
|
const responseData = await apiRequest.call(this, 'GET', endpoint, {});
|
||||||
|
|
||||||
|
if (responseData === undefined) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'No data got returned');
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
let name: string;
|
||||||
|
for (const data of responseData) {
|
||||||
|
|
||||||
|
if (data.delete_at !== 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = `${data.display_name} (${data.type === 'O' ? 'public' : 'private'})`;
|
||||||
|
|
||||||
|
returnData.push({
|
||||||
|
name,
|
||||||
|
value: data.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
returnData.sort((a, b) => {
|
||||||
|
if (a.name < b.name) { return -1; }
|
||||||
|
if (a.name > b.name) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const endpoint = 'users';
|
||||||
|
const responseData = await apiRequest.call(this, 'GET', endpoint, {});
|
||||||
|
|
||||||
|
if (responseData === undefined) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'No data got returned');
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
for (const data of responseData) {
|
||||||
|
|
||||||
|
if (data.delete_at !== 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
returnData.push({
|
||||||
|
name: data.username,
|
||||||
|
value: data.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
returnData.sort((a, b) => {
|
||||||
|
if (a.name < b.name) { return -1; }
|
||||||
|
if (a.name > b.name) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
72
packages/nodes-base/nodes/Mattermost/v1/transport/index.ts
Normal file
72
packages/nodes-base/nodes/Mattermost/v1/transport/index.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
GenericValue,
|
||||||
|
IDataObject,
|
||||||
|
IHttpRequestOptions,
|
||||||
|
NodeApiError,
|
||||||
|
NodeOperationError,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make an API request to Mattermost
|
||||||
|
*/
|
||||||
|
export async function apiRequest(
|
||||||
|
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD',
|
||||||
|
endpoint: string,
|
||||||
|
body: IDataObject | GenericValue | GenericValue[] = {},
|
||||||
|
query: IDataObject = {},
|
||||||
|
) {
|
||||||
|
const credentials = await this.getCredentials('mattermostApi');
|
||||||
|
|
||||||
|
if (!credentials) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'No credentials returned!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: IHttpRequestOptions = {
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
qs: query,
|
||||||
|
url: `${credentials.baseUrl}/api/v4/${endpoint}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${credentials.accessToken}`,
|
||||||
|
'content-type': 'application/json; charset=utf-8',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await this.helpers.httpRequest(options);
|
||||||
|
} catch (error) {
|
||||||
|
throw new NodeApiError(this.getNode(), error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function apiRequestAllItems(
|
||||||
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD',
|
||||||
|
endpoint: string,
|
||||||
|
body: IDataObject = {},
|
||||||
|
query: IDataObject = {},
|
||||||
|
) {
|
||||||
|
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
query.page = 0;
|
||||||
|
query.per_page = 100;
|
||||||
|
|
||||||
|
do {
|
||||||
|
responseData = await apiRequest.call(this, method, endpoint, body, query);
|
||||||
|
query.page++;
|
||||||
|
returnData.push.apply(returnData, responseData);
|
||||||
|
} while (
|
||||||
|
responseData.length !== 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
25
packages/nodes-base/src/NodeVersionedType.ts
Normal file
25
packages/nodes-base/src/NodeVersionedType.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { INodeType, INodeTypeBaseDescription, INodeVersionedType } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class NodeVersionedType implements INodeVersionedType {
|
||||||
|
currentVersion: number;
|
||||||
|
nodeVersions: INodeVersionedType['nodeVersions'];
|
||||||
|
description: INodeTypeBaseDescription;
|
||||||
|
|
||||||
|
constructor(nodeVersions: INodeVersionedType['nodeVersions'], description: INodeTypeBaseDescription) {
|
||||||
|
this.nodeVersions = nodeVersions;
|
||||||
|
this.currentVersion = description.defaultVersion ?? this.getLatestVersion();
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLatestVersion() {
|
||||||
|
return Math.max(...Object.keys(this.nodeVersions).map(Number));
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeType(version?: number): INodeType {
|
||||||
|
if (version) {
|
||||||
|
return this.nodeVersions[version];
|
||||||
|
} else {
|
||||||
|
return this.nodeVersions[this.currentVersion];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { NodeVersionedType } from './NodeVersionedType';
|
||||||
|
|
||||||
|
export { NodeVersionedType };
|
|
@ -4,6 +4,8 @@
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
// eslint-disable-next-line max-classes-per-file
|
// eslint-disable-next-line max-classes-per-file
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
|
import * as FormData from 'form-data';
|
||||||
|
import { URLSearchParams } from 'url';
|
||||||
import { Workflow } from './Workflow';
|
import { Workflow } from './Workflow';
|
||||||
import { WorkflowHooks } from './WorkflowHooks';
|
import { WorkflowHooks } from './WorkflowHooks';
|
||||||
import { WorkflowOperationError } from './WorkflowErrors';
|
import { WorkflowOperationError } from './WorkflowErrors';
|
||||||
|
@ -191,6 +193,11 @@ export interface IDataObject {
|
||||||
[key: string]: GenericValue | IDataObject | GenericValue[] | IDataObject[];
|
[key: string]: GenericValue | IDataObject | GenericValue[] | IDataObject[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface INodeTypeNameVersion {
|
||||||
|
name: string;
|
||||||
|
version: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IGetExecutePollFunctions {
|
export interface IGetExecutePollFunctions {
|
||||||
(
|
(
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
|
@ -274,6 +281,43 @@ export interface IExecuteContextData {
|
||||||
[key: string]: IContextObject;
|
[key: string]: IContextObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IHttpRequestOptions {
|
||||||
|
url: string;
|
||||||
|
headers?: IDataObject;
|
||||||
|
method?: 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT';
|
||||||
|
body?: FormData | GenericValue | GenericValue[] | Buffer | URLSearchParams;
|
||||||
|
qs?: IDataObject;
|
||||||
|
arrayFormat?: 'indices' | 'brackets' | 'repeat' | 'comma';
|
||||||
|
auth?: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
disableFollowRedirect?: boolean;
|
||||||
|
encoding?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream';
|
||||||
|
skipSslCertificateValidation?: boolean;
|
||||||
|
returnFullResponse?: boolean;
|
||||||
|
proxy?: {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
auth?: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
protocol?: string;
|
||||||
|
};
|
||||||
|
timeout?: number;
|
||||||
|
json?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IN8nHttpResponse = IDataObject | Buffer | GenericValue | GenericValue[];
|
||||||
|
|
||||||
|
export interface IN8nHttpFullResponse {
|
||||||
|
body: IN8nHttpResponse;
|
||||||
|
headers: IDataObject;
|
||||||
|
statusCode: number;
|
||||||
|
statusMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IExecuteFunctions {
|
export interface IExecuteFunctions {
|
||||||
continueOnFail(): boolean;
|
continueOnFail(): boolean;
|
||||||
evaluateExpression(
|
evaluateExpression(
|
||||||
|
@ -292,6 +336,11 @@ export interface IExecuteFunctions {
|
||||||
getInputData(inputIndex?: number, inputName?: string): INodeExecutionData[];
|
getInputData(inputIndex?: number, inputName?: string): INodeExecutionData[];
|
||||||
getMode(): WorkflowExecuteMode;
|
getMode(): WorkflowExecuteMode;
|
||||||
getNode(): INode;
|
getNode(): INode;
|
||||||
|
getNodeParameter<T extends { resource: string }>(
|
||||||
|
parameterName: 'resource',
|
||||||
|
itemIndex?: number,
|
||||||
|
): T['resource'];
|
||||||
|
// getNodeParameter(parameterName: 'operation', itemIndex?: number): string;
|
||||||
getNodeParameter(
|
getNodeParameter(
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
|
@ -309,7 +358,10 @@ export interface IExecuteFunctions {
|
||||||
putExecutionToWait(waitTill: Date): Promise<void>;
|
putExecutionToWait(waitTill: Date): Promise<void>;
|
||||||
sendMessageToUI(message: any): void; // tslint:disable-line:no-any
|
sendMessageToUI(message: any): void; // tslint:disable-line:no-any
|
||||||
helpers: {
|
helpers: {
|
||||||
[key: string]: (...args: any[]) => any;
|
httpRequest(
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||||
|
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,7 +386,10 @@ export interface IExecuteSingleFunctions {
|
||||||
getWorkflowDataProxy(): IWorkflowDataProxyData;
|
getWorkflowDataProxy(): IWorkflowDataProxyData;
|
||||||
getWorkflowStaticData(type: string): IDataObject;
|
getWorkflowStaticData(type: string): IDataObject;
|
||||||
helpers: {
|
helpers: {
|
||||||
[key: string]: (...args: any[]) => any;
|
httpRequest(
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||||
|
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,7 +424,10 @@ export interface ILoadOptionsFunctions {
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
getRestApiUrl(): string;
|
getRestApiUrl(): string;
|
||||||
helpers: {
|
helpers: {
|
||||||
[key: string]: ((...args: any[]) => any) | undefined;
|
httpRequest(
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||||
|
[key: string]: ((...args: any[]) => any) | undefined; // tslint:disable-line:no-any
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,7 +447,10 @@ export interface IHookFunctions {
|
||||||
getWorkflow(): IWorkflowMetadata;
|
getWorkflow(): IWorkflowMetadata;
|
||||||
getWorkflowStaticData(type: string): IDataObject;
|
getWorkflowStaticData(type: string): IDataObject;
|
||||||
helpers: {
|
helpers: {
|
||||||
[key: string]: (...args: any[]) => any;
|
httpRequest(
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||||
|
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,7 +469,10 @@ export interface IPollFunctions {
|
||||||
getWorkflow(): IWorkflowMetadata;
|
getWorkflow(): IWorkflowMetadata;
|
||||||
getWorkflowStaticData(type: string): IDataObject;
|
getWorkflowStaticData(type: string): IDataObject;
|
||||||
helpers: {
|
helpers: {
|
||||||
[key: string]: (...args: any[]) => any;
|
httpRequest(
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||||
|
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,7 +491,10 @@ export interface ITriggerFunctions {
|
||||||
getWorkflow(): IWorkflowMetadata;
|
getWorkflow(): IWorkflowMetadata;
|
||||||
getWorkflowStaticData(type: string): IDataObject;
|
getWorkflowStaticData(type: string): IDataObject;
|
||||||
helpers: {
|
helpers: {
|
||||||
[key: string]: (...args: any[]) => any;
|
httpRequest(
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||||
|
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,7 +522,10 @@ export interface IWebhookFunctions {
|
||||||
outputIndex?: number,
|
outputIndex?: number,
|
||||||
): Promise<INodeExecutionData[][]>;
|
): Promise<INodeExecutionData[][]>;
|
||||||
helpers: {
|
helpers: {
|
||||||
[key: string]: (...args: any[]) => any;
|
httpRequest(
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||||
|
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -496,12 +566,10 @@ export interface IBinaryKeyData {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INodeExecutionData {
|
export interface INodeExecutionData {
|
||||||
[key: string]: IDataObject | IBinaryKeyData | undefined;
|
[key: string]: IDataObject | IBinaryKeyData | NodeApiError | NodeOperationError | undefined;
|
||||||
// TODO: Rename this one as json does not really fit as it is not json (which is a string) it is actually a JS object
|
|
||||||
json: IDataObject;
|
json: IDataObject;
|
||||||
// json: object;
|
|
||||||
// json?: object;
|
|
||||||
binary?: IBinaryKeyData;
|
binary?: IBinaryKeyData;
|
||||||
|
error?: NodeApiError | NodeOperationError;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INodeExecuteFunctions {
|
export interface INodeExecuteFunctions {
|
||||||
|
@ -557,10 +625,10 @@ export interface INodePropertyTypeOptions {
|
||||||
|
|
||||||
export interface IDisplayOptions {
|
export interface IDisplayOptions {
|
||||||
hide?: {
|
hide?: {
|
||||||
[key: string]: NodeParameterValue[];
|
[key: string]: NodeParameterValue[] | undefined;
|
||||||
};
|
};
|
||||||
show?: {
|
show?: {
|
||||||
[key: string]: NodeParameterValue[];
|
[key: string]: NodeParameterValue[] | undefined;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -634,6 +702,14 @@ export interface INodeType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface INodeVersionedType {
|
||||||
|
nodeVersions: {
|
||||||
|
[key: number]: INodeType;
|
||||||
|
};
|
||||||
|
currentVersion: number;
|
||||||
|
description: INodeTypeBaseDescription;
|
||||||
|
getNodeType: (version?: number) => INodeType;
|
||||||
|
}
|
||||||
export interface NodeCredentialTestResult {
|
export interface NodeCredentialTestResult {
|
||||||
status: 'OK' | 'Error';
|
status: 'OK' | 'Error';
|
||||||
message: string;
|
message: string;
|
||||||
|
@ -684,15 +760,21 @@ export interface IWorfklowIssues {
|
||||||
[key: string]: INodeIssues;
|
[key: string]: INodeIssues;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INodeTypeDescription {
|
export interface INodeTypeBaseDescription {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
name: string;
|
name: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
group: string[];
|
group: string[];
|
||||||
version: number;
|
|
||||||
description: string;
|
description: string;
|
||||||
defaults: INodeParameters;
|
|
||||||
documentationUrl?: string;
|
documentationUrl?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
defaultVersion?: number;
|
||||||
|
codex?: CodexData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeTypeDescription extends INodeTypeBaseDescription {
|
||||||
|
version: number;
|
||||||
|
defaults: INodeParameters;
|
||||||
inputs: string[];
|
inputs: string[];
|
||||||
inputNames?: string[];
|
inputNames?: string[];
|
||||||
outputs: string[];
|
outputs: string[];
|
||||||
|
@ -701,14 +783,12 @@ export interface INodeTypeDescription {
|
||||||
credentials?: INodeCredentialDescription[];
|
credentials?: INodeCredentialDescription[];
|
||||||
maxNodes?: number; // How many nodes of that type can be created in a workflow
|
maxNodes?: number; // How many nodes of that type can be created in a workflow
|
||||||
polling?: boolean;
|
polling?: boolean;
|
||||||
subtitle?: string;
|
|
||||||
hooks?: {
|
hooks?: {
|
||||||
[key: string]: INodeHookDescription[] | undefined;
|
[key: string]: INodeHookDescription[] | undefined;
|
||||||
activate?: INodeHookDescription[];
|
activate?: INodeHookDescription[];
|
||||||
deactivate?: INodeHookDescription[];
|
deactivate?: INodeHookDescription[];
|
||||||
};
|
};
|
||||||
webhooks?: IWebhookDescription[];
|
webhooks?: IWebhookDescription[];
|
||||||
codex?: CodexData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INodeHookDescription {
|
export interface INodeHookDescription {
|
||||||
|
@ -777,13 +857,14 @@ export type WebhookResponseMode = 'onReceived' | 'lastNode';
|
||||||
export interface INodeTypes {
|
export interface INodeTypes {
|
||||||
nodeTypes: INodeTypeData;
|
nodeTypes: INodeTypeData;
|
||||||
init(nodeTypes?: INodeTypeData): Promise<void>;
|
init(nodeTypes?: INodeTypeData): Promise<void>;
|
||||||
getAll(): INodeType[];
|
getAll(): Array<INodeType | INodeVersionedType>;
|
||||||
getByName(nodeType: string): INodeType | undefined;
|
getByName(nodeType: string): INodeType | INodeVersionedType | undefined;
|
||||||
|
getByNameAndVersion(nodeType: string, version?: number): INodeType | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INodeTypeData {
|
export interface INodeTypeData {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
type: INodeType;
|
type: INodeType | INodeVersionedType;
|
||||||
sourcePath: string;
|
sourcePath: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -949,3 +1030,19 @@ export type CodexData = {
|
||||||
export type JsonValue = string | number | boolean | null | JsonObject | JsonValue[];
|
export type JsonValue = string | number | boolean | null | JsonObject | JsonValue[];
|
||||||
|
|
||||||
export type JsonObject = { [key: string]: JsonValue };
|
export type JsonObject = { [key: string]: JsonValue };
|
||||||
|
|
||||||
|
export type AllEntities<M> = M extends { [key: string]: string } ? Entity<M, keyof M> : never;
|
||||||
|
|
||||||
|
export type Entity<M, K> = K extends keyof M ? { resource: K; operation: M[K] } : never;
|
||||||
|
|
||||||
|
export type PropertiesOf<M extends { resource: string; operation: string }> = Array<
|
||||||
|
Omit<INodeProperties, 'displayOptions'> & {
|
||||||
|
displayOptions?: {
|
||||||
|
[key in 'show' | 'hide']?: {
|
||||||
|
resource?: Array<M['resource']>;
|
||||||
|
operation?: Array<M['operation']>;
|
||||||
|
[otherKey: string]: NodeParameterValue[] | undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||||
|
@ -23,6 +26,7 @@ import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
INodePropertyCollection,
|
INodePropertyCollection,
|
||||||
INodeType,
|
INodeType,
|
||||||
|
INodeVersionedType,
|
||||||
IParameterDependencies,
|
IParameterDependencies,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
IWebhookData,
|
IWebhookData,
|
||||||
|
@ -41,7 +45,7 @@ import { Workflow } from './Workflow';
|
||||||
* @param {INodeType} nodeType
|
* @param {INodeType} nodeType
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function getSpecialNodeParameters(nodeType: INodeType) {
|
export function getSpecialNodeParameters(nodeType: INodeType): INodeProperties[] {
|
||||||
if (nodeType.description.polling === true) {
|
if (nodeType.description.polling === true) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -296,7 +300,7 @@ export function displayParameter(
|
||||||
|
|
||||||
if (
|
if (
|
||||||
values.length === 0 ||
|
values.length === 0 ||
|
||||||
!parameter.displayOptions.show[propertyName].some((v) => values.includes(v))
|
!parameter.displayOptions.show[propertyName]!.some((v) => values.includes(v))
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -323,7 +327,7 @@ export function displayParameter(
|
||||||
|
|
||||||
if (
|
if (
|
||||||
values.length !== 0 &&
|
values.length !== 0 &&
|
||||||
parameter.displayOptions.hide[propertyName].some((v) => values.includes(v))
|
parameter.displayOptions.hide[propertyName]!.some((v) => values.includes(v))
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -844,7 +848,7 @@ export function getNodeWebhooks(
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeType = workflow.nodeTypes.getByName(node.type) as INodeType;
|
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion) as INodeType;
|
||||||
|
|
||||||
if (nodeType.description.webhooks === undefined) {
|
if (nodeType.description.webhooks === undefined) {
|
||||||
// Node does not have any webhooks so return
|
// Node does not have any webhooks so return
|
||||||
|
@ -940,7 +944,7 @@ export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookD
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeType = workflow.nodeTypes.getByName(node.type) as INodeType;
|
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion) as INodeType;
|
||||||
|
|
||||||
if (nodeType.description.webhooks === undefined) {
|
if (nodeType.description.webhooks === undefined) {
|
||||||
// Node does not have any webhooks so return
|
// Node does not have any webhooks so return
|
||||||
|
@ -1385,3 +1389,27 @@ export function mergeNodeProperties(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getVersionedTypeNode(
|
||||||
|
object: INodeVersionedType | INodeType,
|
||||||
|
version?: number,
|
||||||
|
): INodeType {
|
||||||
|
if (isNodeTypeVersioned(object)) {
|
||||||
|
return (object as INodeVersionedType).getNodeType(version);
|
||||||
|
}
|
||||||
|
return object as INodeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVersionedTypeNodeAll(object: INodeVersionedType | INodeType): INodeType[] {
|
||||||
|
if (isNodeTypeVersioned(object)) {
|
||||||
|
return Object.values((object as INodeVersionedType).nodeVersions).map((element) => {
|
||||||
|
element.description.name = object.description.name;
|
||||||
|
return element;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [object as INodeType];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNodeTypeVersioned(object: INodeVersionedType | INodeType): boolean {
|
||||||
|
return !!('getNodeType' in object);
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
@ -85,7 +88,8 @@ export class Workflow {
|
||||||
let nodeType: INodeType | undefined;
|
let nodeType: INodeType | undefined;
|
||||||
for (const node of parameters.nodes) {
|
for (const node of parameters.nodes) {
|
||||||
this.nodes[node.name] = node;
|
this.nodes[node.name] = node;
|
||||||
nodeType = this.nodeTypes.getByName(node.type);
|
|
||||||
|
nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
|
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
// Go on to next node when its type is not known.
|
// Go on to next node when its type is not known.
|
||||||
|
@ -197,7 +201,7 @@ export class Workflow {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeType = this.nodeTypes.getByName(node.type);
|
nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
|
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
// Type is not known so check is not possible
|
// Type is not known so check is not possible
|
||||||
|
@ -241,7 +245,7 @@ export class Workflow {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeType = this.nodeTypes.getByName(node.type);
|
nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
|
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
// Node type is not known
|
// Node type is not known
|
||||||
|
@ -342,7 +346,7 @@ export class Workflow {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeType = this.nodeTypes.getByName(node.type);
|
nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
|
|
||||||
if (nodeType !== undefined && checkFunction(nodeType)) {
|
if (nodeType !== undefined && checkFunction(nodeType)) {
|
||||||
returnNodes.push(node);
|
returnNodes.push(node);
|
||||||
|
@ -712,7 +716,7 @@ export class Workflow {
|
||||||
if (node === null) {
|
if (node === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const nodeType = this.nodeTypes.getByName(node.type) as INodeType;
|
const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion) as INodeType;
|
||||||
if (nodeType.description.outputs.length === 1) {
|
if (nodeType.description.outputs.length === 1) {
|
||||||
// If the parent node has only one output, it can only be connected
|
// If the parent node has only one output, it can only be connected
|
||||||
// to that one. So no further checking is required.
|
// to that one. So no further checking is required.
|
||||||
|
@ -787,7 +791,8 @@ export class Workflow {
|
||||||
let nodeType: INodeType;
|
let nodeType: INodeType;
|
||||||
for (const nodeName of nodeNames) {
|
for (const nodeName of nodeNames) {
|
||||||
node = this.nodes[nodeName];
|
node = this.nodes[nodeName];
|
||||||
nodeType = this.nodeTypes.getByName(node.type) as INodeType;
|
|
||||||
|
nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion) as INodeType;
|
||||||
|
|
||||||
if (nodeType.trigger !== undefined || nodeType.poll !== undefined) {
|
if (nodeType.trigger !== undefined || nodeType.poll !== undefined) {
|
||||||
if (node.disabled === true) {
|
if (node.disabled === true) {
|
||||||
|
@ -860,7 +865,7 @@ export class Workflow {
|
||||||
isTest?: boolean,
|
isTest?: boolean,
|
||||||
): Promise<boolean | undefined> {
|
): Promise<boolean | undefined> {
|
||||||
const node = this.getNode(webhookData.node) as INode;
|
const node = this.getNode(webhookData.node) as INode;
|
||||||
const nodeType = this.nodeTypes.getByName(node.type) as INodeType;
|
const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion) as INodeType;
|
||||||
|
|
||||||
if (nodeType.webhookMethods === undefined) {
|
if (nodeType.webhookMethods === undefined) {
|
||||||
return;
|
return;
|
||||||
|
@ -907,7 +912,7 @@ export class Workflow {
|
||||||
): Promise<ITriggerResponse | undefined> {
|
): Promise<ITriggerResponse | undefined> {
|
||||||
const triggerFunctions = getTriggerFunctions(this, node, additionalData, mode, activation);
|
const triggerFunctions = getTriggerFunctions(this, node, additionalData, mode, activation);
|
||||||
|
|
||||||
const nodeType = this.nodeTypes.getByName(node.type);
|
const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
|
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
throw new Error(`The node type "${node.type}" of node "${node.name}" is not known.`);
|
throw new Error(`The node type "${node.type}" of node "${node.name}" is not known.`);
|
||||||
|
@ -947,11 +952,12 @@ export class Workflow {
|
||||||
* @returns
|
* @returns
|
||||||
* @memberof Workflow
|
* @memberof Workflow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async runPoll(
|
async runPoll(
|
||||||
node: INode,
|
node: INode,
|
||||||
pollFunctions: IPollFunctions,
|
pollFunctions: IPollFunctions,
|
||||||
): Promise<INodeExecutionData[][] | null> {
|
): Promise<INodeExecutionData[][] | null> {
|
||||||
const nodeType = this.nodeTypes.getByName(node.type);
|
const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
|
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
throw new Error(`The node type "${node.type}" of node "${node.name}" is not known.`);
|
throw new Error(`The node type "${node.type}" of node "${node.name}" is not known.`);
|
||||||
|
@ -984,7 +990,7 @@ export class Workflow {
|
||||||
nodeExecuteFunctions: INodeExecuteFunctions,
|
nodeExecuteFunctions: INodeExecuteFunctions,
|
||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
): Promise<IWebhookResponseData> {
|
): Promise<IWebhookResponseData> {
|
||||||
const nodeType = this.nodeTypes.getByName(node.type);
|
const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
throw new Error(`The type of the webhook node "${node.name}" is not known.`);
|
throw new Error(`The type of the webhook node "${node.name}" is not known.`);
|
||||||
} else if (nodeType.webhook === undefined) {
|
} else if (nodeType.webhook === undefined) {
|
||||||
|
@ -1036,7 +1042,7 @@ export class Workflow {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeType = this.nodeTypes.getByName(node.type);
|
const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
throw new Error(`Node type "${node.type}" is not known so can not run it!`);
|
throw new Error(`Node type "${node.type}" is not known so can not run it!`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import { INodeType, INodeTypeData, INodeTypes } from '../src';
|
import {
|
||||||
|
INodeType,
|
||||||
|
INodeTypeData,
|
||||||
|
INodeTypes,
|
||||||
|
NodeHelpers,
|
||||||
|
} from '../src';
|
||||||
|
|
||||||
export interface INodeTypesObject {
|
export interface INodeTypesObject {
|
||||||
[key: string]: INodeType;
|
[key: string]: INodeType;
|
||||||
|
@ -94,11 +99,15 @@ class NodeTypesClass implements INodeTypes {
|
||||||
async init(nodeTypes: INodeTypeData): Promise<void> {}
|
async init(nodeTypes: INodeTypeData): Promise<void> {}
|
||||||
|
|
||||||
getAll(): INodeType[] {
|
getAll(): INodeType[] {
|
||||||
return Object.values(this.nodeTypes).map((data) => data.type);
|
return Object.values(this.nodeTypes).map((data) => NodeHelpers.getVersionedTypeNode(data.type));
|
||||||
}
|
}
|
||||||
|
|
||||||
getByName(nodeType: string): INodeType {
|
getByName(nodeType: string): INodeType {
|
||||||
return this.nodeTypes[nodeType].type;
|
return this.getByNameAndVersion(nodeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
||||||
|
return NodeHelpers.getVersionedTypeNode(this.nodeTypes[nodeType].type, version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue