mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 21:07:28 -08:00
test: Add expression transform tests (#5497) (no-changelog)
test: add expression transform tests
This commit is contained in:
parent
332d50c5f1
commit
39c871d514
|
@ -14,16 +14,14 @@ import type {
|
||||||
NodeParameterValueType,
|
NodeParameterValueType,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from './Interfaces';
|
} from './Interfaces';
|
||||||
import { ExpressionError, ExpressionExtensionError } from './ExpressionError';
|
import { ExpressionError } from './ExpressionError';
|
||||||
import { WorkflowDataProxy } from './WorkflowDataProxy';
|
import { WorkflowDataProxy } from './WorkflowDataProxy';
|
||||||
import type { Workflow } from './Workflow';
|
import type { Workflow } from './Workflow';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import { extend, extendOptional, hasExpressionExtension, hasNativeMethod } from './Extensions';
|
import { extend, extendOptional } from './Extensions';
|
||||||
import type { ExpressionChunk, ExpressionCode } from './Extensions/ExpressionParser';
|
|
||||||
import { joinExpression, splitExpression } from './Extensions/ExpressionParser';
|
|
||||||
import { extendTransform } from './Extensions/ExpressionExtension';
|
|
||||||
import { extendedFunctions } from './Extensions/ExtendedFunctions';
|
import { extendedFunctions } from './Extensions/ExtendedFunctions';
|
||||||
|
import { extendSyntax } from './Extensions/ExpressionExtension';
|
||||||
|
|
||||||
// Set it to use double curly brackets instead of single ones
|
// Set it to use double curly brackets instead of single ones
|
||||||
tmpl.brackets.set('{{ }}');
|
tmpl.brackets.set('{{ }}');
|
||||||
|
@ -292,7 +290,7 @@ export class Expression {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the expression
|
// Execute the expression
|
||||||
const extendedExpression = this.extendSyntax(parameterValue);
|
const extendedExpression = extendSyntax(parameterValue);
|
||||||
const returnValue = this.renderExpression(extendedExpression, data);
|
const returnValue = this.renderExpression(extendedExpression, data);
|
||||||
if (typeof returnValue === 'function') {
|
if (typeof returnValue === 'function') {
|
||||||
if (returnValue.name === '$') throw new Error('invalid syntax');
|
if (returnValue.name === '$') throw new Error('invalid syntax');
|
||||||
|
@ -358,45 +356,6 @@ export class Expression {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
extendSyntax(bracketedExpression: string): string {
|
|
||||||
const chunks = splitExpression(bracketedExpression);
|
|
||||||
|
|
||||||
const codeChunks = chunks
|
|
||||||
.filter((c) => c.type === 'code')
|
|
||||||
.map((c) => c.text.replace(/("|').*?("|')/, '').trim());
|
|
||||||
|
|
||||||
if (!codeChunks.some(hasExpressionExtension) || hasNativeMethod(bracketedExpression))
|
|
||||||
return bracketedExpression;
|
|
||||||
|
|
||||||
const extendedChunks = chunks.map((chunk): ExpressionChunk => {
|
|
||||||
if (chunk.type === 'code') {
|
|
||||||
const output = extendTransform(chunk.text);
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
||||||
if (!output?.code) {
|
|
||||||
throw new ExpressionExtensionError('invalid syntax');
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = output.code;
|
|
||||||
|
|
||||||
// We need to cut off any trailing semicolons. These cause issues
|
|
||||||
// with certain types of expression and cause the whole expression
|
|
||||||
// to fail.
|
|
||||||
if (text.trim().endsWith(';')) {
|
|
||||||
text = text.trim().slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...chunk,
|
|
||||||
text,
|
|
||||||
} as ExpressionCode;
|
|
||||||
}
|
|
||||||
return chunk;
|
|
||||||
});
|
|
||||||
|
|
||||||
return joinExpression(extendedChunks);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves value of parameter. But does not work for workflow-data.
|
* Resolves value of parameter. But does not work for workflow-data.
|
||||||
*
|
*
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { DateTime } from 'luxon';
|
||||||
import { ExpressionExtensionError } from '../ExpressionError';
|
import { ExpressionExtensionError } from '../ExpressionError';
|
||||||
import { parse, visit, types, print } from 'recast';
|
import { parse, visit, types, print } from 'recast';
|
||||||
import { getOption } from 'recast/lib/util';
|
import { getOption } from 'recast/lib/util';
|
||||||
|
import type { Config as EsprimaConfig } from 'esprima-next';
|
||||||
import { parse as esprimaParse } from 'esprima-next';
|
import { parse as esprimaParse } from 'esprima-next';
|
||||||
|
|
||||||
import { arrayExtensions } from './ArrayExtensions';
|
import { arrayExtensions } from './ArrayExtensions';
|
||||||
|
@ -12,6 +13,9 @@ import { stringExtensions } from './StringExtensions';
|
||||||
import { objectExtensions } from './ObjectExtensions';
|
import { objectExtensions } from './ObjectExtensions';
|
||||||
import type { ExpressionKind } from 'ast-types/gen/kinds';
|
import type { ExpressionKind } from 'ast-types/gen/kinds';
|
||||||
|
|
||||||
|
import type { ExpressionChunk, ExpressionCode } from './ExpressionParser';
|
||||||
|
import { joinExpression, splitExpression } from './ExpressionParser';
|
||||||
|
|
||||||
const EXPRESSION_EXTENDER = 'extend';
|
const EXPRESSION_EXTENDER = 'extend';
|
||||||
const EXPRESSION_EXTENDER_OPTIONAL = 'extendOptional';
|
const EXPRESSION_EXTENDER_OPTIONAL = 'extendOptional';
|
||||||
|
|
||||||
|
@ -89,17 +93,12 @@ function parseWithEsprimaNext(source: string, options?: any): any {
|
||||||
loc: true,
|
loc: true,
|
||||||
locations: true,
|
locations: true,
|
||||||
comment: true,
|
comment: true,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
range: getOption(options, 'range', false) as boolean,
|
||||||
range: getOption(options, 'range', false),
|
tolerant: getOption(options, 'tolerant', true) as boolean,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
||||||
tolerant: getOption(options, 'tolerant', true),
|
|
||||||
tokens: true,
|
tokens: true,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
jsx: getOption(options, 'jsx', false) as boolean,
|
||||||
jsx: getOption(options, 'jsx', false),
|
sourceType: getOption(options, 'sourceType', 'module') as string,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
} as EsprimaConfig);
|
||||||
sourceType: getOption(options, 'sourceType', 'module'),
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
return ast;
|
return ast;
|
||||||
}
|
}
|
||||||
|
@ -124,9 +123,8 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
|
|
||||||
let currentChain = 1;
|
let currentChain = 1;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
// Polyfill optional chaining
|
||||||
visit(ast, {
|
visit(ast, {
|
||||||
// Polyfill optional chaining
|
|
||||||
visitChainExpression(path) {
|
visitChainExpression(path) {
|
||||||
this.traverse(path);
|
this.traverse(path);
|
||||||
const chainNumber = currentChain;
|
const chainNumber = currentChain;
|
||||||
|
@ -138,6 +136,8 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
typeof window !== 'object' ? 'global' : 'window',
|
typeof window !== 'object' ? 'global' : 'window',
|
||||||
);
|
);
|
||||||
|
// We want to define all of our commonly used identifiers and member
|
||||||
|
// expressions now so we don't have to create multiple instances
|
||||||
const undefinedIdentifier = types.builders.identifier('undefined');
|
const undefinedIdentifier = types.builders.identifier('undefined');
|
||||||
const cancelIdentifier = types.builders.identifier(`chainCancelToken${chainNumber}`);
|
const cancelIdentifier = types.builders.identifier(`chainCancelToken${chainNumber}`);
|
||||||
const valueIdentifier = types.builders.identifier(`chainValue${chainNumber}`);
|
const valueIdentifier = types.builders.identifier(`chainValue${chainNumber}`);
|
||||||
|
@ -152,6 +152,8 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
|
|
||||||
const patchedStack: ExpressionKind[] = [];
|
const patchedStack: ExpressionKind[] = [];
|
||||||
|
|
||||||
|
// This builds the cancel check. This lets us slide to the end of the expression
|
||||||
|
// if it's undefined/null at any of the optional points of the chain.
|
||||||
const buildCancelCheckWrapper = (node: ExpressionKind): ExpressionKind => {
|
const buildCancelCheckWrapper = (node: ExpressionKind): ExpressionKind => {
|
||||||
return types.builders.conditionalExpression(
|
return types.builders.conditionalExpression(
|
||||||
types.builders.binaryExpression(
|
types.builders.binaryExpression(
|
||||||
|
@ -164,10 +166,17 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This is just a quick small wrapper to create the assignment expression
|
||||||
|
// for the running value.
|
||||||
const buildValueAssignWrapper = (node: ExpressionKind): ExpressionKind => {
|
const buildValueAssignWrapper = (node: ExpressionKind): ExpressionKind => {
|
||||||
return types.builders.assignmentExpression('=', valueMemberExpression, node);
|
return types.builders.assignmentExpression('=', valueMemberExpression, node);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This builds what actually does the comparison. It wraps the current
|
||||||
|
// chunk of the expression with a nullish coalescing operator that returns
|
||||||
|
// undefined if it's null or undefined. We do this because optional chains
|
||||||
|
// always return undefined if they fail part way, even if the value they
|
||||||
|
// fail on is null.
|
||||||
const buildOptionalWrapper = (node: ExpressionKind): ExpressionKind => {
|
const buildOptionalWrapper = (node: ExpressionKind): ExpressionKind => {
|
||||||
return types.builders.binaryExpression(
|
return types.builders.binaryExpression(
|
||||||
'===',
|
'===',
|
||||||
|
@ -180,6 +189,7 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Another small wrapper, but for assigning to the cancel token this time.
|
||||||
const buildCancelAssignWrapper = (node: ExpressionKind): ExpressionKind => {
|
const buildCancelAssignWrapper = (node: ExpressionKind): ExpressionKind => {
|
||||||
return types.builders.assignmentExpression('=', cancelMemberExpression, node);
|
return types.builders.assignmentExpression('=', cancelMemberExpression, node);
|
||||||
};
|
};
|
||||||
|
@ -189,6 +199,9 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
let patchTop: ExpressionKind | null = null;
|
let patchTop: ExpressionKind | null = null;
|
||||||
let wrapNextTopInOptionalExtend = false;
|
let wrapNextTopInOptionalExtend = false;
|
||||||
|
|
||||||
|
// This patches the previous node to use our current one as it's left hand value.
|
||||||
|
// It takes `window.chainValue1.test1` and `window.chainValue1.test2` and turns it
|
||||||
|
// into `window.chainValue1.test2.test1`.
|
||||||
const updatePatch = (toPatch: ExpressionKind, node: ExpressionKind) => {
|
const updatePatch = (toPatch: ExpressionKind, node: ExpressionKind) => {
|
||||||
if (toPatch.type === 'MemberExpression' || toPatch.type === 'OptionalMemberExpression') {
|
if (toPatch.type === 'MemberExpression' || toPatch.type === 'OptionalMemberExpression') {
|
||||||
toPatch.object = node;
|
toPatch.object = node;
|
||||||
|
@ -200,7 +213,15 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This loop walks down an optional chain from the top. This will walk
|
||||||
|
// from right to left through an optional chain. We keep track of our current
|
||||||
|
// top of the chain (furthest right) and create a chain below it. This chain
|
||||||
|
// contains all of the (member and call) expressions that we need. These are
|
||||||
|
// patched versions that reference our current chain value. We then push this
|
||||||
|
// chain onto a stack when we hit an optional point in our chain.
|
||||||
while (true) {
|
while (true) {
|
||||||
|
// This should only ever be these types but you can optional chain on
|
||||||
|
// JSX nodes, which we don't support.
|
||||||
if (
|
if (
|
||||||
currentNode.type === 'MemberExpression' ||
|
currentNode.type === 'MemberExpression' ||
|
||||||
currentNode.type === 'OptionalMemberExpression' ||
|
currentNode.type === 'OptionalMemberExpression' ||
|
||||||
|
@ -208,6 +229,10 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
currentNode.type === 'OptionalCallExpression'
|
currentNode.type === 'OptionalCallExpression'
|
||||||
) {
|
) {
|
||||||
let patchNode: ExpressionKind;
|
let patchNode: ExpressionKind;
|
||||||
|
// Here we take the current node and extract the parts we actually care
|
||||||
|
// about.
|
||||||
|
// In the case of a member expression we take the property it's trying to
|
||||||
|
// access and make the object it's accessing be our chain value.
|
||||||
if (
|
if (
|
||||||
currentNode.type === 'MemberExpression' ||
|
currentNode.type === 'MemberExpression' ||
|
||||||
currentNode.type === 'OptionalMemberExpression'
|
currentNode.type === 'OptionalMemberExpression'
|
||||||
|
@ -216,6 +241,8 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
valueMemberExpression,
|
valueMemberExpression,
|
||||||
currentNode.property,
|
currentNode.property,
|
||||||
);
|
);
|
||||||
|
// In the case of a call expression we take the arguments and make the
|
||||||
|
// callee our chain value.
|
||||||
} else {
|
} else {
|
||||||
patchNode = types.builders.callExpression(
|
patchNode = types.builders.callExpression(
|
||||||
valueMemberExpression,
|
valueMemberExpression,
|
||||||
|
@ -223,16 +250,22 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have a previous node we patch it here.
|
||||||
if (currentPatch) {
|
if (currentPatch) {
|
||||||
updatePatch(currentPatch, patchNode);
|
updatePatch(currentPatch, patchNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have no top patch (first run, or just pushed onto the stack) we
|
||||||
|
// note it here.
|
||||||
if (!patchTop) {
|
if (!patchTop) {
|
||||||
patchTop = patchNode;
|
patchTop = patchNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPatch = patchNode;
|
currentPatch = patchNode;
|
||||||
|
|
||||||
|
// This is an optional in our chain. In here we'll push the node onto the
|
||||||
|
// stack. We also do a polyfill if the top of the stack is function call
|
||||||
|
// that might be a extended function.
|
||||||
if (currentNode.optional) {
|
if (currentNode.optional) {
|
||||||
// Implement polyfill described below
|
// Implement polyfill described below
|
||||||
if (wrapNextTopInOptionalExtend) {
|
if (wrapNextTopInOptionalExtend) {
|
||||||
|
@ -268,6 +301,7 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finally we get the next point AST to walk down.
|
||||||
if (
|
if (
|
||||||
currentNode.type === 'MemberExpression' ||
|
currentNode.type === 'MemberExpression' ||
|
||||||
currentNode.type === 'OptionalMemberExpression'
|
currentNode.type === 'OptionalMemberExpression'
|
||||||
|
@ -277,6 +311,8 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
currentNode = currentNode.callee;
|
currentNode = currentNode.callee;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// We update the final patch to point to the first part of the optional chain
|
||||||
|
// which is probably an identifier for an object.
|
||||||
if (currentPatch) {
|
if (currentPatch) {
|
||||||
updatePatch(currentPatch, currentNode);
|
updatePatch(currentPatch, currentNode);
|
||||||
if (!patchTop) {
|
if (!patchTop) {
|
||||||
|
@ -298,6 +334,7 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Push the first part of our chain to stack.
|
||||||
if (patchTop) {
|
if (patchTop) {
|
||||||
patchedStack.push(patchTop);
|
patchedStack.push(patchTop);
|
||||||
} else {
|
} else {
|
||||||
|
@ -307,28 +344,42 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Since we're working from right to left we need to flip the stack
|
||||||
|
// for the correct order of operations
|
||||||
patchedStack.reverse();
|
patchedStack.reverse();
|
||||||
|
|
||||||
|
// Walk the node stack and wrap all our expressions in cancel/assignment
|
||||||
|
// wrappers.
|
||||||
for (let i = 0; i < patchedStack.length; i++) {
|
for (let i = 0; i < patchedStack.length; i++) {
|
||||||
let node = patchedStack[i];
|
let node = patchedStack[i];
|
||||||
|
|
||||||
|
// We don't wrap the last expression in an assignment wrapper because
|
||||||
|
// it's going to be returned anyway. We just wrap it in a cancel check
|
||||||
|
// wrapper.
|
||||||
if (i !== patchedStack.length - 1) {
|
if (i !== patchedStack.length - 1) {
|
||||||
node = buildCancelAssignWrapper(buildOptionalWrapper(node));
|
node = buildCancelAssignWrapper(buildOptionalWrapper(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't wrap the first part in a cancel wrapper because the cancel
|
||||||
|
// token will always be undefined.
|
||||||
if (i !== 0) {
|
if (i !== 0) {
|
||||||
node = buildCancelCheckWrapper(node);
|
node = buildCancelCheckWrapper(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace the node in the stack with our wrapped one
|
||||||
patchedStack[i] = node;
|
patchedStack[i] = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Put all our expressions in a sequence expression (also called a
|
||||||
|
// group operator). These will all be executed in order and the value
|
||||||
|
// of the final expression will be returned.
|
||||||
const sequenceNode = types.builders.sequenceExpression(patchedStack);
|
const sequenceNode = types.builders.sequenceExpression(patchedStack);
|
||||||
|
|
||||||
path.replace(sequenceNode);
|
path.replace(sequenceNode);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
// Extended functions
|
||||||
visit(ast, {
|
visit(ast, {
|
||||||
visitCallExpression(path) {
|
visitCallExpression(path) {
|
||||||
this.traverse(path);
|
this.traverse(path);
|
||||||
|
@ -377,7 +428,6 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument
|
|
||||||
return print(ast);
|
return print(ast);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return;
|
return;
|
||||||
|
@ -515,3 +565,61 @@ export function extendOptional(
|
||||||
return foundFunction.function(input, args);
|
return foundFunction.function(input, args);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EXTENDED_SYNTAX_CACHE: Record<string, string> = {};
|
||||||
|
|
||||||
|
export function extendSyntax(bracketedExpression: string, forceExtend = false): string {
|
||||||
|
const chunks = splitExpression(bracketedExpression);
|
||||||
|
|
||||||
|
const codeChunks = chunks
|
||||||
|
.filter((c) => c.type === 'code')
|
||||||
|
.map((c) => c.text.replace(/("|').*?("|')/, '').trim());
|
||||||
|
|
||||||
|
if (
|
||||||
|
(!codeChunks.some(hasExpressionExtension) || hasNativeMethod(bracketedExpression)) &&
|
||||||
|
!forceExtend
|
||||||
|
) {
|
||||||
|
return bracketedExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've seen this expression before grab it from the cache
|
||||||
|
if (bracketedExpression in EXTENDED_SYNTAX_CACHE) {
|
||||||
|
return EXTENDED_SYNTAX_CACHE[bracketedExpression];
|
||||||
|
}
|
||||||
|
|
||||||
|
const extendedChunks = chunks.map((chunk): ExpressionChunk => {
|
||||||
|
if (chunk.type === 'code') {
|
||||||
|
let output = extendTransform(chunk.text);
|
||||||
|
|
||||||
|
// esprima fails to parse bare objects (e.g. `{ data: something }`), we can
|
||||||
|
// work around this by wrapping it in an parentheses
|
||||||
|
if (!output?.code && chunk.text.trim()[0] === '{') {
|
||||||
|
output = extendTransform(`(${chunk.text})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!output?.code) {
|
||||||
|
throw new ExpressionExtensionError('invalid syntax');
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = output.code;
|
||||||
|
|
||||||
|
// We need to cut off any trailing semicolons. These cause issues
|
||||||
|
// with certain types of expression and cause the whole expression
|
||||||
|
// to fail.
|
||||||
|
if (text.trim().endsWith(';')) {
|
||||||
|
text = text.trim().slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...chunk,
|
||||||
|
text,
|
||||||
|
} as ExpressionCode;
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
const expression = joinExpression(extendedChunks);
|
||||||
|
// Cache the expression so we don't have to do this transform again
|
||||||
|
EXTENDED_SYNTAX_CACHE[bracketedExpression] = expression;
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
|
@ -6,15 +6,13 @@ import { DateTime, Duration, Interval } from 'luxon';
|
||||||
import { Expression } from '@/Expression';
|
import { Expression } from '@/Expression';
|
||||||
import { Workflow } from '@/Workflow';
|
import { Workflow } from '@/Workflow';
|
||||||
import * as Helpers from './Helpers';
|
import * as Helpers from './Helpers';
|
||||||
import { baseFixtures } from './ExpressionFixtures/base';
|
|
||||||
import {
|
import {
|
||||||
IConnections,
|
baseFixtures,
|
||||||
IExecuteData,
|
ExpressionTestEvaluation,
|
||||||
INode,
|
ExpressionTestTransform,
|
||||||
INodeExecutionData,
|
} from './ExpressionFixtures/base';
|
||||||
IRunExecutionData,
|
import { INodeExecutionData } from '@/Interfaces';
|
||||||
ITaskData,
|
import { extendSyntax } from '@/Extensions/ExpressionExtension';
|
||||||
} from '@/Interfaces';
|
|
||||||
|
|
||||||
describe('Expression', () => {
|
describe('Expression', () => {
|
||||||
describe('getParameterValue()', () => {
|
describe('getParameterValue()', () => {
|
||||||
|
@ -186,7 +184,9 @@ describe('Expression', () => {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
test(t.expression, () => {
|
test(t.expression, () => {
|
||||||
for (const test of t.tests.filter((test) => test.type === 'evaluation')) {
|
for (const test of t.tests.filter(
|
||||||
|
(test) => test.type === 'evaluation',
|
||||||
|
) as ExpressionTestEvaluation[]) {
|
||||||
expect(evaluate(t.expression, test.input.map((d) => ({ json: d })) as any)).toStrictEqual(
|
expect(evaluate(t.expression, test.input.map((d) => ({ json: d })) as any)).toStrictEqual(
|
||||||
test.output,
|
test.output,
|
||||||
);
|
);
|
||||||
|
@ -194,4 +194,20 @@ describe('Expression', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Test all expression transform fixtures', () => {
|
||||||
|
for (const t of baseFixtures) {
|
||||||
|
if (!t.tests.some((test) => test.type === 'transform')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
test(t.expression, () => {
|
||||||
|
for (const test of t.tests.filter(
|
||||||
|
(test) => test.type === 'transform',
|
||||||
|
) as ExpressionTestTransform[]) {
|
||||||
|
const expr = t.expression;
|
||||||
|
expect(extendSyntax(expr, test.forceTransform)).toEqual(test.result ?? expr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,14 @@ export interface ExpressionTestEvaluation extends ExpressionTestBase {
|
||||||
output: IDataObject | GenericValue;
|
output: IDataObject | GenericValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExpressionTests = ExpressionTestEvaluation;
|
export interface ExpressionTestTransform extends ExpressionTestBase {
|
||||||
|
type: 'transform';
|
||||||
|
// If we don't specify a result we expect it to be the same as the input
|
||||||
|
result?: string;
|
||||||
|
forceTransform?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExpressionTests = ExpressionTestEvaluation | ExpressionTestTransform;
|
||||||
|
|
||||||
export interface ExpressionTestFixture {
|
export interface ExpressionTestFixture {
|
||||||
expression: string;
|
expression: string;
|
||||||
|
@ -31,6 +38,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ contact: null }],
|
input: [{ contact: null }],
|
||||||
output: undefined,
|
output: undefined,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -56,6 +65,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{}],
|
input: [{}],
|
||||||
output: undefined,
|
output: undefined,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -66,6 +77,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ test: { json: { message: { message_id: 'value' } } } }],
|
input: [{ test: { json: { message: { message_id: 'value' } } } }],
|
||||||
output: 'value',
|
output: 'value',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -76,6 +89,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ Set2: { json: { apiKey: 'testKey' } }, testKey: 'testValue' }],
|
input: [{ Set2: { json: { apiKey: 'testKey' } }, testKey: 'testValue' }],
|
||||||
output: 'testValue',
|
output: 'testValue',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -86,6 +101,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ get: { json: { recipes: [{ image: 'test' }] } } }],
|
input: [{ get: { json: { recipes: [{ image: 'test' }] } } }],
|
||||||
output: 'test',
|
output: 'test',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -97,6 +114,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ Clockify1: { parameter: { workspaceId: 'test1' }, json: { id: 'test2' } } }],
|
input: [{ Clockify1: { parameter: { workspaceId: 'test1' }, json: { id: 'test2' } } }],
|
||||||
output: 'https://example.com/api/v1/workspaces/test1/projects/test2',
|
output: 'https://example.com/api/v1/workspaces/test1/projects/test2',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -107,6 +126,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ 'dig check CF': { data: { stdout: 'testout' } } }],
|
input: [{ 'dig check CF': { data: { stdout: 'testout' } } }],
|
||||||
output: ' testout',
|
output: ' testout',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -117,6 +138,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ 'Set URL': { json: { base_domain: 'left' } }, link: 'right' }],
|
input: [{ 'Set URL': { json: { base_domain: 'left' } }, link: 'right' }],
|
||||||
output: 'leftright',
|
output: 'leftright',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -127,6 +150,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [],
|
input: [],
|
||||||
output: 0,
|
output: 0,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -137,6 +162,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [],
|
input: [],
|
||||||
output: '',
|
output: '',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -157,6 +184,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
],
|
],
|
||||||
output: 10,
|
output: 10,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -167,6 +196,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ GetTicket: { json: { tickets: [1, 2, 3, 4] } } }],
|
input: [{ GetTicket: { json: { tickets: [1, 2, 3, 4] } } }],
|
||||||
output: 4,
|
output: 4,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -177,6 +208,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ test: 1 }],
|
input: [{ test: 1 }],
|
||||||
output: '[object Object]',
|
output: '[object Object]',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -187,6 +220,10 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [],
|
input: [],
|
||||||
output: 100,
|
output: 100,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'transform',
|
||||||
|
result: '={{extend(Math, "floor", [extend(Math, "min", [1, 2]) * 100])}}',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -203,6 +240,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
],
|
],
|
||||||
output: 'test',
|
output: 'test',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -228,6 +267,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [],
|
input: [],
|
||||||
output: undefined,
|
output: undefined,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -239,6 +280,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ Webhook1: { json: { headers: { 'x-api-key': 'left' } } } }],
|
input: [{ Webhook1: { json: { headers: { 'x-api-key': 'left' } } } }],
|
||||||
output: 'left-test',
|
output: 'left-test',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -250,6 +293,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ Webhook1: { json: { headers: { 'x-api-key': 'left' } } }, test: 3 }],
|
input: [{ Webhook1: { json: { headers: { 'x-api-key': 'left' } } }, test: 3 }],
|
||||||
output: 'left-3',
|
output: 'left-3',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -260,6 +305,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ 'Create or update': { json: { vid: [1, 2, 3] } } }],
|
input: [{ 'Create or update': { json: { vid: [1, 2, 3] } } }],
|
||||||
output: [1, 2, 3],
|
output: [1, 2, 3],
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -270,6 +317,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ Crypto: { json: { data: 'testtest' } } }],
|
input: [{ Crypto: { json: { data: 'testtest' } } }],
|
||||||
output: 'https://example.com/test?id=testte',
|
output: 'https://example.com/test?id=testte',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -280,6 +329,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ body: { project: { name: 'test[1234]' } } }],
|
input: [{ body: { project: { name: 'test[1234]' } } }],
|
||||||
output: '1234',
|
output: '1234',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -296,6 +347,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
],
|
],
|
||||||
output: 4,
|
output: 4,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -312,6 +365,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ projectName: 'Project Test', projectsCount: 3 }],
|
input: [{ projectName: 'Project Test', projectsCount: 3 }],
|
||||||
output: 'Project Test',
|
output: 'Project Test',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -322,6 +377,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ created_at: '2023-02-09T13:22:54.187Z' }],
|
input: [{ created_at: '2023-02-09T13:22:54.187Z' }],
|
||||||
output: '2023-02-09T13:22:54.187Z',
|
output: '2023-02-09T13:22:54.187Z',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -332,6 +389,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ 'Find by ID1': { json: { fields: { clicks: 8 } } } }],
|
input: [{ 'Find by ID1': { json: { fields: { clicks: 8 } } } }],
|
||||||
output: 9,
|
output: 9,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -343,6 +402,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ Bid: '3,80', Baserow: { json: { Count: '10' } } }],
|
input: [{ Bid: '3,80', Baserow: { json: { Count: '10' } } }],
|
||||||
output: '38.00',
|
output: '38.00',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -363,6 +424,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -389,6 +452,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ 'Find by ID': { json: {} } }],
|
input: [{ 'Find by ID': { json: {} } }],
|
||||||
output: false,
|
output: false,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -404,6 +469,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ 'HTTP Request': { json: {} } }],
|
input: [{ 'HTTP Request': { json: {} } }],
|
||||||
output: false,
|
output: false,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -414,6 +481,19 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [],
|
input: [],
|
||||||
output: 1,
|
output: 1,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform', result: '={{extend(Math, "min", [1, 2])}}' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expression: '={{new String().toString();}}',
|
||||||
|
tests: [
|
||||||
|
{
|
||||||
|
type: 'evaluation',
|
||||||
|
input: [],
|
||||||
|
output: '',
|
||||||
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true, result: '={{new String().toString()}}' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -439,6 +519,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ different: { phone: 'test', name: 'test2' } }],
|
input: [{ different: { phone: 'test', name: 'test2' } }],
|
||||||
output: true,
|
output: true,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -449,6 +531,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [],
|
input: [],
|
||||||
output: 200,
|
output: 200,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -459,6 +543,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ assetValue: 50, value: 50 }],
|
input: [{ assetValue: 50, value: 50 }],
|
||||||
output: 25,
|
output: 25,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -474,6 +560,8 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||||
input: [{ search_term: 'asdf' }],
|
input: [{ search_term: 'asdf' }],
|
||||||
output: false,
|
output: false,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -487,6 +575,8 @@ value
|
||||||
multi
|
multi
|
||||||
line`,
|
line`,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -497,6 +587,12 @@ line`,
|
||||||
input: [{ body: { choices: 'testValue' } }],
|
input: [{ body: { choices: 'testValue' } }],
|
||||||
output: { data: 'testValue' },
|
output: { data: 'testValue' },
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{
|
||||||
|
type: 'transform',
|
||||||
|
forceTransform: true,
|
||||||
|
result: '={{( { "data": $json.body.choices } )}}',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -517,6 +613,8 @@ line`,
|
||||||
input: [{ data: {} }],
|
input: [{ data: {} }],
|
||||||
output: undefined,
|
output: undefined,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -532,6 +630,8 @@ line`,
|
||||||
input: [{ asdas: 1 }],
|
input: [{ asdas: 1 }],
|
||||||
output: undefined,
|
output: undefined,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -547,6 +647,8 @@ line`,
|
||||||
input: [{ data: {} }],
|
input: [{ data: {} }],
|
||||||
output: false,
|
output: false,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -557,6 +659,8 @@ line`,
|
||||||
input: [],
|
input: [],
|
||||||
output: 'TRUE',
|
output: 'TRUE',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -597,6 +701,13 @@ line`,
|
||||||
input: [{}],
|
input: [{}],
|
||||||
output: true,
|
output: true,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{
|
||||||
|
type: 'transform',
|
||||||
|
forceTransform: true,
|
||||||
|
result:
|
||||||
|
'={{ !(window.chainCancelToken1 = ((window.chainValue1 = $json) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainCancelToken1 = ((window.chainValue1 = window.chainValue1.data) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainCancelToken1 = ((window.chainValue1 = window.chainValue1.data) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainCancelToken1 = ((window.chainValue1 = window.chainValue1.issues) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainCancelToken1 = ((window.chainValue1 = window.chainValue1.pageInfo) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1.hasNextPage) }}',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -607,6 +718,8 @@ line`,
|
||||||
input: [],
|
input: [],
|
||||||
output: [{ name: 'something', batch_size: 1000, ignore_cols: ['x'] }],
|
output: [{ name: 'something', batch_size: 1000, ignore_cols: ['x'] }],
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -622,6 +735,8 @@ line`,
|
||||||
input: [{ person: { json: {} } }],
|
input: [{ person: { json: {} } }],
|
||||||
output: false,
|
output: false,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -637,6 +752,13 @@ line`,
|
||||||
input: [{}],
|
input: [{}],
|
||||||
output: '',
|
output: '',
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{
|
||||||
|
type: 'transform',
|
||||||
|
forceTransform: true,
|
||||||
|
result:
|
||||||
|
"={{ (window.chainCancelToken1 = ((window.chainValue1 = $json) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1.data) == undefined ? '' : $json.data }}",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -657,6 +779,8 @@ line`,
|
||||||
input: [{}],
|
input: [{}],
|
||||||
output: false,
|
output: false,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -667,6 +791,8 @@ line`,
|
||||||
input: [],
|
input: [],
|
||||||
output: 7,
|
output: 7,
|
||||||
},
|
},
|
||||||
|
{ type: 'transform' },
|
||||||
|
{ type: 'transform', forceTransform: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in a new issue