n8n/packages/workflow/test/Helpers.ts
Mutasem Aldmour 3af0abd9e0
feat(editor): Add input panel to NDV (#3204)
* refactor tabs out

* refactor execute button

* refactor header

* add more views

* fix error view

* fix workflow rename bug

* rename component

* fix small screen bug

* move items, fix positions

* add hover state

* show selector on empty state

* add empty run state

* fix binary view

* 1 item

* add vjs styles

* show empty row for every item

* refactor tabs

* add branch names

* fix spacing

* fix up spacing

* add run selector

* fix positioning

* clean up

* increase width of selector

* fix up spacing

* fix copy button

* fix branch naming; type issues

* fix docs in custom nodes

* add type

* hide items when run selector is shown

* increase selector size

* add select prepend

* clean up a bit

* Add pagination

* add stale icon

* enable stale data in execution run

* Revert "enable stale data in execution run"

8edb68dbff

* move metadata to its own state

* fix smaller size

* add scroll buttons

* update tabs on resize

* update stale data on rename

* remove metadata on delete

* hide x

* change title colors

* binary data classes

* remove duplicate css

* add colors

* delete unused keys

* use event bus

* refactor header out

* support different nodes

* update selector

* add immediate input

* add branch overrides

* split output input run index

* clean up unnessary data

* add missing keys

* update key names

* remove unnessary css/js

* fix outputs panel

* set max width on input selector

* fix selector to show parent nodes

* fix bug when switching between nodes

* add linking and refactor

* add linking

* fix minor issues

* hide linking when cannot link

* fix type

* fix error state

* clean up import

* fix linking edge cases

* hide input panel for triggers

* disable for start node

* format file

* refactor output panel

* add empty input hint

* update too much data view

* update slot, message under branch

* no input data view

* add node not run/no output data views

* add tooltip support on execute prev

* fix spacing in view

* address output views

* fix run node hint view

* fix spinner

* center button

* update message to use node name

* update title of no output data message

* implement loading states

* fix sizes

* fix sizes

* update spinner

* add wire me up image

* update link

* update panels design

* fix unclickable area bug

* revert change

* fix clickable bg

* fix up positioning

* ensure bg is clickable

* fix up borders

* fix height

* move border to wrapper

* set box shadow

* set box shadow

* add drag button

* add dragging for main panel

* set max width of panels

* set min width in js

* keep showing drag while dragging

* fix dragging leaving modal

* update trigger position of main panel

* move main panel position into store

* clear metadata after changing workflow

* center grid correctly

* add drag arrows

* add dragging hover

* fix cursor behavior

* update no output state

* show last run on open

* always set to latest run

* fix padding

* add I wish this node would

* clean up unsued data

* inject run info into run

* refactor out drag button

* fix dragging issue

* fix arrow bug

* increase width of panel

* change run logic

* set label font sizes

* update radiobutton pos

* address header issues

* fix prev spacing bug

* fix input order

* set package lock

* add close modal event

* complete close modal event

* add input change event

* add dragging event

* add event on view change

* add page size event

* rename event

* add event on page change

* add link click event

* add linking event

* rename var

* add run change event

* add button events

* add branch event

* add structure for open event

* add input type

* set session id

* set sessionid/source for expression events

* add params to expression events

* make display modes global

* add display mode to tracking

* add more event tracking

* add has_mapping param

* make main panel position global

* dedupe list

* fix cursor while dragging

* address feedback

* reduce bottom scrim

* remove empty option hint

* add hint tooltip

* add tritary button

* update param names

* update parameter buttons

* center empty states

* move feature request message

* increase max width for inputs selector

* fix error dispaly padding

* remove immediate

* refactor search logic to return object

* fix console errors

* fix console errors

* add node distance

* refactor how input nodes listed

* remove console log

* set package lock

* refactor recursive logic

* handle overrides

* handle default case without inputs

* fix bug width link

* fix tabs arrow bug

* handle binary data case

* update node execution

* fix merge logic

* remove console log

* delete func

* update package lock

* add hover area

* switch first input node

* keep recursive order

* make breadth first traversal

* fix overflow bug, add pluralization

* update docs url

* update drop shadow

* set background color for button

* update input

* fix truncation

* update index of input dropdown

* fix binary background

* update telemetry

* fix binary data switching

* check all parent connections for executed node

* check current state for executing node

* fix executing states

* update loading states

* use pluralization for items

* rename modal

* update pluralization

* update package lock

* update empty messagE

* format file

* refactor out dragging logic

* refactor out dragging

* add back panel position

* add telemetry params

* add survey url as const

* remove extra space, add dot

* rename tabs, update telemetery, fix telemetry bug

* update execute prev button

* rename workflow func

* rename workflow func

* delete unnessary component

* fix build issue

* add tests for workflow search

* format + add tests

* remove todo comment

* update iconnection type to match workflows

* Revert "update iconnection type to match workflows"

3772487d98

* update func comment

* fix formatting issues

* add tertiary story

* add spinner story

* remove todo comment

* remove eslint check

* update empty messagE
2022-05-23 17:56:15 +02:00

700 lines
17 KiB
TypeScript

import get from 'lodash.get';
import {
CredentialInformation,
IAdditionalCredentialOptions,
IAllExecuteFunctions,
IContextObject,
ICredentialDataDecryptedObject,
ICredentials,
ICredentialsEncrypted,
ICredentialsHelper,
IDataObject,
IExecuteFunctions,
IExecuteResponsePromiseData,
IExecuteSingleFunctions,
IExecuteWorkflowInfo,
IHttpRequestOptions,
IN8nHttpFullResponse,
IN8nHttpResponse,
INode,
INodeCredentialsDetails,
INodeExecutionData,
INodeParameters,
INodeType,
INodeTypeData,
INodeTypes,
INodeVersionedType,
IRunExecutionData,
ITaskDataConnections,
IWorkflowBase,
IWorkflowDataProxyAdditionalKeys,
IWorkflowDataProxyData,
IWorkflowExecuteAdditionalData,
NodeHelpers,
NodeParameterValue,
Workflow,
WorkflowDataProxy,
WorkflowExecuteMode,
WorkflowHooks,
} from '../src';
export interface INodeTypesObject {
[key: string]: INodeType;
}
export class Credentials extends ICredentials {
hasNodeAccess(nodeType: string): boolean {
return true;
}
setData(data: ICredentialDataDecryptedObject, encryptionKey: string): void {
this.data = JSON.stringify(data);
}
setDataKey(key: string, data: CredentialInformation, encryptionKey: string): void {
let fullData;
try {
fullData = this.getData(encryptionKey);
} catch (e) {
fullData = {};
}
fullData[key] = data;
return this.setData(fullData, encryptionKey);
}
getData(encryptionKey: string, nodeType?: string): ICredentialDataDecryptedObject {
if (this.data === undefined) {
throw new Error('No data is set so nothing can be returned.');
}
return JSON.parse(this.data);
}
getDataKey(key: string, encryptionKey: string, nodeType?: string): CredentialInformation {
const fullData = this.getData(encryptionKey, nodeType);
if (fullData === null) {
throw new Error(`No data was set.`);
}
// eslint-disable-next-line no-prototype-builtins
if (!fullData.hasOwnProperty(key)) {
throw new Error(`No data for key "${key}" exists.`);
}
return fullData[key];
}
getDataToSave(): ICredentialsEncrypted {
if (this.data === undefined) {
throw new Error(`No credentials were set to save.`);
}
return {
id: this.id,
name: this.name,
type: this.type,
data: this.data,
nodesAccess: this.nodesAccess,
};
}
}
export class CredentialsHelper extends ICredentialsHelper {
async authenticate(
credentials: ICredentialDataDecryptedObject,
typeName: string,
requestParams: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
return requestParams;
}
getParentTypes(name: string): string[] {
return [];
}
async getDecrypted(
nodeCredentials: INodeCredentialsDetails,
type: string,
): Promise<ICredentialDataDecryptedObject> {
return {};
}
async getCredentials(
nodeCredentials: INodeCredentialsDetails,
type: string,
): Promise<ICredentials> {
return new Credentials({ id: null, name: '' }, '', [], '');
}
async updateCredentials(
nodeCredentials: INodeCredentialsDetails,
type: string,
data: ICredentialDataDecryptedObject,
): Promise<void> {}
}
export function getNodeParameter(
workflow: Workflow,
runExecutionData: IRunExecutionData | null,
runIndex: number,
connectionInputData: INodeExecutionData[],
node: INode,
parameterName: string,
itemIndex: number,
mode: WorkflowExecuteMode,
timezone: string,
additionalKeys: IWorkflowDataProxyAdditionalKeys,
fallbackValue?: any,
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object {
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
if (nodeType === undefined) {
throw new Error(`Node type "${node.type}" is not known so can not return paramter value!`);
}
const value = get(node.parameters, parameterName, fallbackValue);
if (value === undefined) {
throw new Error(`Could not get parameter "${parameterName}"!`);
}
let returnData;
try {
returnData = workflow.expression.getParameterValue(
value,
runExecutionData,
runIndex,
itemIndex,
node.name,
connectionInputData,
mode,
timezone,
additionalKeys,
);
} catch (e) {
e.message += ` [Error in parameter: "${parameterName}"]`;
throw e;
}
return returnData;
}
export function getExecuteFunctions(
workflow: Workflow,
runExecutionData: IRunExecutionData,
runIndex: number,
connectionInputData: INodeExecutionData[],
inputData: ITaskDataConnections,
node: INode,
itemIndex: number,
additionalData: IWorkflowExecuteAdditionalData,
mode: WorkflowExecuteMode,
): IExecuteFunctions {
return ((workflow, runExecutionData, connectionInputData, inputData, node) => {
return {
continueOnFail: () => {
return false;
},
evaluateExpression: (expression: string, itemIndex: number) => {
return expression;
},
async executeWorkflow(
workflowInfo: IExecuteWorkflowInfo,
inputData?: INodeExecutionData[],
): Promise<any> {
return additionalData.executeWorkflow(workflowInfo, additionalData, inputData);
},
getContext(type: string): IContextObject {
return NodeHelpers.getContext(runExecutionData, type, node);
},
async getCredentials(
type: string,
itemIndex?: number,
): Promise<ICredentialDataDecryptedObject> {
return {
apiKey: '12345',
};
},
getExecutionId: (): string => {
return additionalData.executionId!;
},
getInputData: (inputIndex = 0, inputName = 'main') => {
if (!inputData.hasOwnProperty(inputName)) {
// Return empty array because else it would throw error when nothing is connected to input
return [];
}
if (inputData[inputName].length < inputIndex) {
throw new Error(`Could not get input index "${inputIndex}" of input "${inputName}"!`);
}
if (inputData[inputName][inputIndex] === null) {
// return [];
throw new Error(`Value "${inputIndex}" of input "${inputName}" did not get set!`);
}
return inputData[inputName][inputIndex] as INodeExecutionData[];
},
getNodeParameter: (
parameterName: string,
itemIndex: number,
fallbackValue?: any,
):
| NodeParameterValue
| INodeParameters
| NodeParameterValue[]
| INodeParameters[]
| object => {
return getNodeParameter(
workflow,
runExecutionData,
runIndex,
connectionInputData,
node,
parameterName,
itemIndex,
mode,
additionalData.timezone,
{},
fallbackValue,
);
},
getMode: (): WorkflowExecuteMode => {
return mode;
},
getNode: () => {
return JSON.parse(JSON.stringify(node));
},
getRestApiUrl: (): string => {
return additionalData.restApiUrl;
},
getTimezone: (): string => {
return additionalData.timezone;
},
getWorkflow: () => {
return {
id: workflow.id,
name: workflow.name,
active: workflow.active,
};
},
getWorkflowDataProxy: (itemIndex: number): IWorkflowDataProxyData => {
const dataProxy = new WorkflowDataProxy(
workflow,
runExecutionData,
runIndex,
itemIndex,
node.name,
connectionInputData,
{},
mode,
additionalData.timezone,
{},
);
return dataProxy.getDataProxy();
},
getWorkflowStaticData(type: string): IDataObject {
return workflow.getStaticData(type, node);
},
prepareOutputData: NodeHelpers.prepareOutputData,
async putExecutionToWait(waitTill: Date): Promise<void> {
runExecutionData.waitTill = waitTill;
},
sendMessageToUI(...args: any[]): void {
if (mode !== 'manual') {
return;
}
try {
if (additionalData.sendMessageToUI) {
additionalData.sendMessageToUI(node.name, args);
}
} catch (error) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
console.error(`There was a problem sending messsage to UI: ${error.message}`);
}
},
async sendResponse(response: IExecuteResponsePromiseData): Promise<void> {
await additionalData.hooks?.executeHookFunctions('sendResponse', [response]);
},
helpers: {
async httpRequest(
requestOptions: IHttpRequestOptions,
): Promise<IN8nHttpFullResponse | IN8nHttpResponse> {
return {
body: {
headers: {},
statusCode: 200,
requestOptions,
},
};
},
async requestWithAuthentication(
this: IAllExecuteFunctions,
credentialsType: string,
requestOptions: IHttpRequestOptions,
additionalCredentialOptions?: IAdditionalCredentialOptions,
): Promise<any> {
return {
body: {
headers: {},
statusCode: 200,
credentialsType,
requestOptions,
additionalCredentialOptions,
},
};
},
async httpRequestWithAuthentication(
this: IAllExecuteFunctions,
credentialsType: string,
requestOptions: IHttpRequestOptions,
additionalCredentialOptions?: IAdditionalCredentialOptions,
): Promise<any> {
return {
body: {
headers: {},
statusCode: 200,
credentialsType,
requestOptions,
additionalCredentialOptions,
},
};
},
},
};
})(workflow, runExecutionData, connectionInputData, inputData, node);
}
export function getExecuteSingleFunctions(
workflow: Workflow,
runExecutionData: IRunExecutionData,
runIndex: number,
connectionInputData: INodeExecutionData[],
inputData: ITaskDataConnections,
node: INode,
itemIndex: number,
additionalData: IWorkflowExecuteAdditionalData,
mode: WorkflowExecuteMode,
): IExecuteSingleFunctions {
return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => {
return {
continueOnFail: () => {
return false;
},
evaluateExpression: (expression: string, evaluateItemIndex: number | undefined) => {
return expression;
},
getContext(type: string): IContextObject {
return NodeHelpers.getContext(runExecutionData, type, node);
},
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject> {
return {
apiKey: '12345',
};
},
getInputData: (inputIndex = 0, inputName = 'main') => {
if (!inputData.hasOwnProperty(inputName)) {
// Return empty array because else it would throw error when nothing is connected to input
return { json: {} };
}
if (inputData[inputName].length < inputIndex) {
throw new Error(`Could not get input index "${inputIndex}" of input "${inputName}"!`);
}
const allItems = inputData[inputName][inputIndex];
if (allItems === null) {
// return [];
throw new Error(`Value "${inputIndex}" of input "${inputName}" did not get set!`);
}
if (allItems[itemIndex] === null) {
// return [];
throw new Error(
`Value "${inputIndex}" of input "${inputName}" with itemIndex "${itemIndex}" did not get set!`,
);
}
return allItems[itemIndex];
},
getMode: (): WorkflowExecuteMode => {
return mode;
},
getNode: () => {
return JSON.parse(JSON.stringify(node));
},
getRestApiUrl: (): string => {
return additionalData.restApiUrl;
},
getTimezone: (): string => {
return additionalData.timezone;
},
getNodeParameter: (
parameterName: string,
fallbackValue?: any,
):
| NodeParameterValue
| INodeParameters
| NodeParameterValue[]
| INodeParameters[]
| object => {
return getNodeParameter(
workflow,
runExecutionData,
runIndex,
connectionInputData,
node,
parameterName,
itemIndex,
mode,
additionalData.timezone,
{},
fallbackValue,
);
},
getWorkflow: () => {
return {
id: workflow.id,
name: workflow.name,
active: workflow.active,
};
},
getWorkflowDataProxy: (): IWorkflowDataProxyData => {
const dataProxy = new WorkflowDataProxy(
workflow,
runExecutionData,
runIndex,
itemIndex,
node.name,
connectionInputData,
{},
mode,
additionalData.timezone,
{},
);
return dataProxy.getDataProxy();
},
getWorkflowStaticData(type: string): IDataObject {
return workflow.getStaticData(type, node);
},
helpers: {
async httpRequest(
requestOptions: IHttpRequestOptions,
): Promise<IN8nHttpFullResponse | IN8nHttpResponse> {
return {
body: {
headers: {},
statusCode: 200,
requestOptions,
},
};
},
async requestWithAuthentication(
this: IAllExecuteFunctions,
credentialsType: string,
requestOptions: IHttpRequestOptions,
additionalCredentialOptions?: IAdditionalCredentialOptions,
): Promise<any> {
return {
body: {
headers: {},
statusCode: 200,
credentialsType,
requestOptions,
additionalCredentialOptions,
},
};
},
async httpRequestWithAuthentication(
this: IAllExecuteFunctions,
credentialsType: string,
requestOptions: IHttpRequestOptions,
additionalCredentialOptions?: IAdditionalCredentialOptions,
): Promise<any> {
return {
body: {
headers: {},
statusCode: 200,
credentialsType,
requestOptions,
additionalCredentialOptions,
},
};
},
},
};
})(workflow, runExecutionData, connectionInputData, inputData, node, itemIndex);
}
class NodeTypesClass implements INodeTypes {
nodeTypes: INodeTypeData = {
'test.set': {
sourcePath: '',
type: {
description: {
displayName: 'Set',
name: 'set',
group: ['input'],
version: 1,
description: 'Sets a value',
defaults: {
name: 'Set',
color: '#0000FF',
},
inputs: ['main'],
outputs: ['main'],
properties: [
{
displayName: 'Value1',
name: 'value1',
type: 'string',
default: 'default-value1',
},
{
displayName: 'Value2',
name: 'value2',
type: 'string',
default: 'default-value2',
},
],
},
},
},
'test.setMulti': {
sourcePath: '',
type: {
description: {
displayName: 'Set Multi',
name: 'setMulti',
group: ['input'],
version: 1,
description: 'Sets multiple values',
defaults: {
name: 'Set Multi',
color: '#0000FF',
},
inputs: ['main'],
outputs: ['main'],
properties: [
{
displayName: 'Values',
name: 'values',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
name: 'string',
displayName: 'String',
values: [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: 'propertyName',
placeholder: 'Name of the property to write data to.',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
placeholder: 'The string value to write in the property.',
},
],
},
],
},
],
},
},
},
'test.switch': {
sourcePath: '',
type: {
description: {
displayName: 'Set',
name: 'set',
group: ['input'],
version: 1,
description: 'Switches',
defaults: {
name: 'Switch',
color: '#0000FF',
},
inputs: ['main'],
outputs: ['main', 'main', 'main', 'main'],
outputNames: ['0', '1', '2', '3'],
properties: [
{
displayName: 'Value1',
name: 'value1',
type: 'string',
default: 'default-value1',
},
{
displayName: 'Value2',
name: 'value2',
type: 'string',
default: 'default-value2',
},
],
},
},
},
};
async init(nodeTypes: INodeTypeData): Promise<void> {}
getAll(): INodeType[] {
return Object.values(this.nodeTypes).map((data) => NodeHelpers.getVersionedNodeType(data.type));
}
getByName(nodeType: string): INodeType | INodeVersionedType | undefined {
return this.getByNameAndVersion(nodeType);
}
getByNameAndVersion(nodeType: string, version?: number): INodeType {
return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType].type, version);
}
}
let nodeTypesInstance: NodeTypesClass | undefined;
export function NodeTypes(): NodeTypesClass {
if (nodeTypesInstance === undefined) {
nodeTypesInstance = new NodeTypesClass();
}
return nodeTypesInstance;
}
export function WorkflowExecuteAdditionalData(): IWorkflowExecuteAdditionalData {
const workflowData: IWorkflowBase = {
name: '',
createdAt: new Date(),
updatedAt: new Date(),
active: true,
nodes: [],
connections: {},
};
return {
credentialsHelper: new CredentialsHelper(''),
hooks: new WorkflowHooks({}, 'trigger', '1', workflowData),
executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise<any> => {},
sendMessageToUI: (message: string) => {},
restApiUrl: '',
encryptionKey: 'test',
timezone: 'America/New_York',
webhookBaseUrl: 'webhook',
webhookWaitingBaseUrl: 'webhook-waiting',
webhookTestBaseUrl: 'webhook-test',
userId: '123',
};
}