UNPKG

@adaptabletools/adaptable-cjs

Version:

Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

696 lines (695 loc) 29.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.scalarExpressionFunctionNames = exports.scalarExpressionFunctions = void 0; const tslib_1 = require("tslib"); const ExpressionEvaluationError_1 = require("../../parser/src/ExpressionEvaluationError"); const date_fns_1 = require("date-fns"); const date_fns_2 = require("date-fns"); const date_fns_3 = require("date-fns"); const date_fns_4 = require("date-fns"); const date_fns_5 = require("date-fns"); const date_fns_6 = require("date-fns"); const date_fns_7 = require("date-fns"); const date_fns_8 = require("date-fns"); const date_fns_9 = require("date-fns"); const date_fns_10 = require("date-fns"); const date_fns_11 = require("date-fns"); const date_fns_12 = require("date-fns"); const date_fns_13 = require("date-fns"); const expressionFunctionUtils_1 = require("./expressionFunctionUtils"); const dateUtils_1 = require("./dateUtils"); const StringExtensions_1 = tslib_1.__importDefault(require("../Extensions/StringExtensions")); const TypeExtensions_1 = require("../Extensions/TypeExtensions"); const src_1 = require("../../parser/src"); const evaluator_1 = require("../../parser/src/evaluator"); // useful for unary operators which expect a list of arguments // we extract the provided array elements into a list const flattenArguments = (values) => { if (!Array.isArray(values)) { return values; } return values.flat(Infinity); }; const sanitizeArguments = (values, allowNaN) => { return values.filter((value) => value != undefined && value != null && value !== '' && (allowNaN || !isNaN(value))); }; const sanitizeNumericResult = (value) => { if (isNaN(value)) { return ''; } return value; }; exports.scalarExpressionFunctions = { VAR: { handler(args, context) { const [functionName, ...optionalArgs] = args; if (StringExtensions_1.default.IsNullOrEmpty(functionName)) { throw new ExpressionEvaluationError_1.ExpressionEvaluationError('VAR', 'should have a name'); } return context.evaluateCustomQueryVariable(functionName, optionalArgs); }, description: 'Returns the variable evaluation', signatures: ['VAR(varName)', 'VAR(varName, arg1, arg2)'], examples: ['VAR(CURRENT_USER)', 'VAR(IS_VALID_VALUE, IS_BLANK([col1]), [col2] < [col3])'], category: 'special', returnType: 'any', }, COL: { handler(args, context) { const columnId = args[0]; const column = context.adaptableApi?.columnApi.getColumnWithColumnId(columnId); if (!column) { throw new ExpressionEvaluationError_1.ExpressionEvaluationError('COL', `Column name "${columnId}" is not found`); } if (!column.queryable) { throw new ExpressionEvaluationError_1.ExpressionEvaluationError('COL', `Column name "${columnId}" is not queryable`); } return context.adaptableApi?.gridApi.getNormalisedValueFromRowNode(context.node, columnId); }, description: 'Returns the value of a Column', signatures: ['[colName]', 'COL(name: string)'], examples: ['[col1]', 'COL("col1")'], category: 'special', returnType: 'any', }, FIELD: { handler(args, context) { const fieldName = args[0]; if (StringExtensions_1.default.IsNullOrEmpty(fieldName)) { throw new ExpressionEvaluationError_1.ExpressionEvaluationError('FIELD', 'requires a field name'); } return context.adaptableApi?.internalApi.getValueUsingField(context.node?.data, fieldName); }, description: 'Returns the value of a row field (not necessarily mapped to a column). If the field is nested, use dot notation (e.g. "nested.fieldName")', signatures: ['FIELD(fieldName:string)'], examples: ['FIELD("fieldName")', 'FIELD("nested.fieldName")'], category: 'special', returnType: 'any', }, QUERY: { handler(args, context) { const namedQueryName = args[0]; if (StringExtensions_1.default.IsNullOrEmpty(namedQueryName)) { return false; } const namedQuery = context.adaptableApi?.namedQueryApi.getNamedQueryByName(namedQueryName); if (!namedQuery) { throw new ExpressionEvaluationError_1.ExpressionEvaluationError('QUERY', `Named Query with name ${namedQueryName} not found!`); } // add query to call stack if (!context.namedQueryCallStack) { context.namedQueryCallStack = []; } context.namedQueryCallStack.push(namedQueryName); //check if this Named Query is not already evaluated, in which case this would lead to an infinite evaluation cycle const firstIndex = context.namedQueryCallStack.indexOf(namedQueryName); const lastIndex = context.namedQueryCallStack.lastIndexOf(namedQueryName); if (firstIndex !== lastIndex) { const cycle = context.namedQueryCallStack.slice(firstIndex, lastIndex + 1); throw new ExpressionEvaluationError_1.ExpressionEvaluationError(`${namedQueryName}`, ` contains a circular reference: ${cycle.join(' -> ')}`); } const queryEvaluationResult = (0, src_1.evaluate)(namedQuery.BooleanExpression, context); // remove query name from callstack context.namedQueryCallStack?.pop(); return queryEvaluationResult; }, category: 'special', description: 'Returns the evaluation result of the Named Query with the given name', signatures: ['QUERY("anyNamedQuery")'], examples: ['QUERY("anyNamedQuery")'], returnType: 'boolean', }, TO_ARRAY: { handler(args) { if (!args?.length) { return []; } return [...args]; }, description: 'Creates an array containing all given parameters (which can be scalar values or expressions)', signatures: ['TO_ARRAY(value1, expression1, value2)'], examples: [`TO_ARRAY([col1], AVG([col2],[col3]), QUERY("maxValue"))`], category: 'special', returnType: 'any', }, COALESCE: { handler(args) { return flattenArguments(args).find((arg) => !(arg === null || arg === undefined)); }, description: 'Returns the first argument which is not null', signatures: ['COALESCE(value, value, ...value)'], examples: ['COALESCE([col1], [col2], [col3], 0)'], category: 'special', returnType: 'string', }, NULL: { handler: () => { return null; }, description: 'Returns the NULL literal which represents the intentional absence of any object value', category: 'special', returnType: 'null', signatures: ['NULL'], examples: ['[status] = "accepted" ? 100 : NULL'], }, IS_BLANK: { handler(args) { return args[0] === undefined || args[0] === null || args[0] === ''; }, category: 'special', description: 'Returns true if input value is undefined, null, or an empty string', signatures: ['IS_BLANK(input: any)'], examples: ['IS_BLANK([col1])'], returnType: 'boolean', }, IS_NOT_BLANK: { handler(args) { return args[0] !== undefined && args[0] !== null && String(args[0]).trim() !== ''; }, category: 'special', description: 'Returns true if input value is not empty', signatures: ['IS_NOT_BLANK(input: any)'], examples: ['IS_NOT_BLANK([col1])'], returnType: 'boolean', }, ABS: { handler(args) { return sanitizeNumericResult(Math.abs(args[0])); }, isHiddenFromMenu: true, description: 'Returns the absolute value of the given number', signatures: ['ABS(a: number)'], examples: ['ABS([columnName])'], category: 'maths', returnType: 'number', }, CEILING: { handler(args) { return sanitizeNumericResult(Math.ceil(args[0])); }, isHiddenFromMenu: true, description: 'Returns smallest integer greater than or equal to the given number', signatures: ['CEILING(a: number)'], examples: ['CEILING([columnName])'], category: 'maths', returnType: 'number', }, FLOOR: { handler(args) { return sanitizeNumericResult(Math.floor(args[0])); }, isHiddenFromMenu: true, description: 'Returns largest integer less than or equal to the given number', signatures: ['FLOOR(a: number)'], examples: ['FLOOR([columnName])'], category: 'maths', returnType: 'number', }, ROUND: { handler(args) { return sanitizeNumericResult(Math.round(args[0])); }, isHiddenFromMenu: true, description: 'Returns value of the given number rounded to the nearest integer', signatures: ['ROUND(a: number)'], examples: ['ROUND([columnName])'], category: 'maths', returnType: 'number', }, MIN: { handler(args) { return Math.min(...sanitizeArguments(flattenArguments(args))); }, description: 'Returns the smallest of given numbers', signatures: ['MIN(number, number, ...number)'], examples: ['MIN([col1], 5)'], category: 'maths', returnType: 'number', }, MAX: { handler(args) { return Math.max(...sanitizeArguments(flattenArguments(args))); }, description: 'Returns the highest of given numbers', signatures: ['MAX(number, number, ...number)'], examples: ['MAX([col1], 5)'], category: 'maths', returnType: 'number', }, AVG: { handler(args) { const sanitizedArguments = sanitizeArguments(flattenArguments(args)); if (args.length && !sanitizedArguments.length) { // expression is syntactically valid, but operates with incompatible values return; } return sanitizedArguments.reduce((a, b) => a + b) / sanitizedArguments.length; }, description: 'Returns the average of inputted numbers', signatures: ['AVG(number, number, ...number)'], examples: ['AVG([col1], 5)'], category: 'maths', returnType: 'number', }, ADD: { handler(args) { const sanitizedArguments = sanitizeArguments(args, true); if (args.length && !sanitizedArguments.length) { // expression is syntactically valid, but operates with incompatible values return; } return sanitizedArguments.reduce((a, b) => a + b); }, isHiddenFromMenu: true, description: 'Returns the sum of 2 numbers', signatures: ['number + number', 'ADD(a: number, b: number)'], examples: ['[col1] + 5', 'ADD([col1], 5)'], category: 'maths', returnType: 'number', }, SUB: { handler(args) { const sanitizedArguments = sanitizeArguments(args); if (args.length && !sanitizedArguments.length) { // expression is syntactically valid, but operates with incompatible values return; } return sanitizedArguments.reduce((a, b) => a - b); }, isHiddenFromMenu: true, description: 'Returns the difference of 2 numbers', signatures: ['number - number', 'SUB(a: number, b: number)'], examples: ['[col1] - 5', 'SUB([col1], 5)'], category: 'maths', returnType: 'number', }, MUL: { handler(args) { return sanitizeNumericResult(args.reduce((a, b) => a * b)); }, isHiddenFromMenu: true, description: 'Returns the product of 2 numbers', signatures: ['number * number', 'mul(a: number, b: number)'], examples: ['[col1] * 5', 'mul([col1], 5)'], category: 'maths', returnType: 'number', }, DIV: { handler(args) { return sanitizeNumericResult(args.reduce((a, b) => a / b)); }, isHiddenFromMenu: true, description: 'Returns the division of 2 numbers', signatures: ['number / number', 'DIV(a: number, b: number)'], examples: ['[col1] / 5', 'DIV([col1], 5)'], category: 'maths', returnType: 'number', }, MOD: { handler(args) { return sanitizeNumericResult(args[0] % args[1]); }, isHiddenFromMenu: true, description: 'Returns the modulo of 2 numbers', signatures: ['number % number', 'MOD(a: number, b: number)'], examples: ['[col1] % 5', 'MOD([col1], 5)'], category: 'maths', returnType: 'number', }, IF: { handler(args) { return args[0] ? args[1] : args[2]; }, isHiddenFromMenu: true, description: 'Evaluates a condition expression and returns the result of one of the two expressions, depending on whether the condition expression evaluates to true or false', signatures: ['condition_expr ? true_statement : false_statement'], examples: ['[col1] > [col2] ? "BIG" : "SMALL"'], category: 'conditional', returnType: 'any', }, CASE: { handler([expressionValueNode, whenThenListNodes, defaultValueNode], context) { const caseExpressionValue = expressionValueNode != undefined ? // CASE matches the first WHEN statement with the provided expression value (0, expressionFunctionUtils_1.evaluateExpressionNode)(expressionValueNode, context) : // otherwise it returns the first WHEN statement evaluated to TRUE true; const matchingWhen = whenThenListNodes.find((whenThenStatement) => { const whenValue = (0, expressionFunctionUtils_1.evaluateExpressionNode)(whenThenStatement.WHEN, context); return whenValue === caseExpressionValue; }); if (!matchingWhen) { if (defaultValueNode) { return (0, expressionFunctionUtils_1.evaluateExpressionNode)(defaultValueNode, context); } return; } const matchingThen = (0, expressionFunctionUtils_1.evaluateExpressionNode)(matchingWhen.THEN, context); return matchingThen; }, isHiddenFromMenu: true, hasEagerEvaluation: true, description: `The syntax of CASE supports 2 variants: 1. CASE takes an expression and compares its value with each WHEN clause until one of them is equal, whereupon the corresponding THEN clause is executed. 2. Each expression in the WHEN clause is evaluated until one of them is true, whereupon the corresponding THEN clause is executed. If no WHEN clause is satisfied, the ELSE clause is executed if one exists, otherwise NULL is returned`, signatures: [ `CASE <expression> [WHEN <expression> THEN <expression>] [ELSE <expression>] END`, `CASE [WHEN <expression> THEN <expression>] [ELSE <expression>] END`, ], examples: [ `CASE [day] WHEN 'Saturday' THEN 'weekend' WHEN 'Sunday' THEN 'weekend' ELSE 'workday' END`, `CASE WHEN [price] < 10 THEN 'low price' WHEN [price] < 50 THEN 'medium price' ELSE 'high price' END`, ], category: 'conditional', returnType: 'any', }, POW: { handler(args) { return sanitizeNumericResult(Math.pow(args[0], args[1])); }, isHiddenFromMenu: true, description: 'Returns the pow of 2 numbers', signatures: ['number ^ number', 'POW(a: number, b: number)'], examples: ['[col1] ^ 5', 'POW([col1], 5)'], category: 'maths', returnType: 'number', }, DATE: { handler(args) { return (0, date_fns_1.parseISO)(args[0]); }, description: 'Returns a new date by parsing the given string in ISO 8601 format', signatures: [ 'DATE(input: string)', 'DATE("YYYYMMDD")', 'DATE("YYYY-MM-DD")', 'DATE("YYYY-MM-DD HH:SS")', ], examples: ['DATE("20210101")', 'DATE("2021-01-01")', 'DATE("2021-01-01 10:10")'], category: 'dates', returnType: 'date', }, NOW: { handler() { return new Date(); }, description: 'Returns the current date', signatures: ['NOW()'], examples: ['[col1] > NOW()'], category: 'dates', returnType: 'date', }, CURRENT_DAY: { handler() { return (0, date_fns_2.startOfDay)(new Date()); }, description: 'Returns the current day', signatures: ['CURRENT_DAY()'], examples: ['[col1] > CURRENT_DAY()'], category: 'dates', returnType: 'date', }, DAY: { handler(args) { return (0, date_fns_2.startOfDay)(args[0]); }, description: 'Returns the day from a date', signatures: ['DAY(input: date)'], examples: ['DAY([col1]) = DAY(NOW())'], category: 'dates', returnType: 'date', }, WEEK: { handler(args) { return (0, date_fns_3.startOfWeek)(args[0]); }, description: 'Returns the week from a date', signatures: ['WEEK(input: date)'], examples: ['WEEK([col1]) = WEEK(NOW())'], category: 'dates', returnType: 'number', }, MONTH: { handler(args) { return (0, date_fns_4.startOfMonth)(args[0]); }, description: 'Returns the month from a date', signatures: ['MONTH(input: date)'], examples: ['MONTH([col1]) = MONTH(NOW())'], category: 'dates', returnType: 'number', }, YEAR: { handler(args) { return (0, date_fns_5.startOfYear)(args[0]); }, description: 'Returns the year from a date', signatures: ['YEAR(input: date)'], examples: ['YEAR([col1]) = YEAR(NOW())'], category: 'dates', returnType: 'date', }, ADD_DAYS: { handler(args) { return (0, date_fns_6.addDays)(args[0], args[1]); }, description: 'Returns a date based on input data and days to add', signatures: ['ADD_DAYS(input: date, days: number)'], examples: ['ADD_DAYS(CURRENT_DAY(), 5)', 'ADD_DAYS([col1], 5)'], category: 'dates', returnType: 'date', }, ADD_WEEKS: { handler(args) { return (0, date_fns_7.addWeeks)(args[0], args[1]); }, description: 'Returns a date based on input data and weeks to add', signatures: ['ADD_WEEKS(input: date, weeks: number)'], examples: ['ADD_WEEKS(CURRENT_DAY(), 5)', 'ADD_WEEKS([col1], 5)'], category: 'dates', returnType: 'date', }, ADD_MONTHS: { handler(args) { return (0, date_fns_8.addMonths)(args[0], args[1]); }, description: 'Returns a date based on input data and months to add', signatures: ['ADD_MONTHS(input: date, months: number)'], examples: ['ADD_MONTHS(CURRENT_DAY(), 5)', 'ADD_MONTHS([col1], 5)'], category: 'dates', returnType: 'date', }, ADD_YEARS: { handler(args) { return (0, date_fns_9.addYears)(args[0], args[1]); }, description: 'Returns a date based on input data and years to add', signatures: ['ADD_YEARS(input: date, years: number)'], examples: ['ADD_YEARS(CURRENT_DAY(), 5)', 'ADD_YEARS([col1], 5)'], category: 'dates', returnType: 'date', }, DIFF_DAYS: { handler(args) { const [first, second] = (0, dateUtils_1.normalizeDateParams)(args); const result = (0, date_fns_10.differenceInDays)(first, second); return sanitizeNumericResult(result); }, description: 'Returns the difference in days between 2 dates', signatures: ['DIFF_DAYS(a: date, b: date)'], examples: ['DIFF_DAYS([col1], CURRENT_DAY())'], category: 'dates', returnType: 'number', }, DIFF_WEEKS: { handler(args) { const [first, second] = (0, dateUtils_1.normalizeDateParams)(args); const result = (0, date_fns_11.differenceInWeeks)(first, second); return sanitizeNumericResult(result); }, description: 'Returns the difference in weeks between 2 dates', signatures: ['DIFF_WEEKS(a: date, b: date)'], examples: ['DIFF_WEEKS([col1], CURRENT_DAY())'], category: 'dates', returnType: 'number', }, DIFF_MONTHS: { handler(args) { const [first, second] = (0, dateUtils_1.normalizeDateParams)(args); const result = (0, date_fns_12.differenceInMonths)(first, second); return sanitizeNumericResult(result); }, description: 'Returns the difference in months between 2 dates', signatures: ['DIFF_MONTHS(a: date, b: date)'], examples: ['DIFF_MONTHS([col1], CURRENT_DAY())'], category: 'dates', returnType: 'number', }, DIFF_YEARS: { handler(args) { const [first, second] = (0, dateUtils_1.normalizeDateParams)(args); const result = (0, date_fns_13.differenceInYears)(first, second); return sanitizeNumericResult(result); }, description: 'Returns the difference in years between 2 dates', signatures: ['DIFF_YEARS(a: date, b: date)'], examples: ['DIFF_YEARS([col1], CURRENT_DAY())'], category: 'dates', returnType: 'number', }, LEN: { handler(args) { return args[0] == null || args[0] == undefined ? 0 : String(args[0]).length; }, description: 'Returns the length of a string', signatures: ['LEN(a: string)'], examples: ['LEN([col1])'], category: 'strings', returnType: 'number', }, SUB_STRING: { handler(args) { return String(args[0]).substring(args[1], args[2]); }, description: 'Extracts characters from string, between 2 indices, returning new sub string', signatures: ['SUB_STRING(a: string, b: number, c:number)'], examples: ['SUB_STRING([col1], 1, 5)'], category: 'strings', returnType: 'string', }, UPPER: { handler(args) { return String(args[0]).toUpperCase(); }, description: 'Converts a string to Upper Case', signatures: ['UPPER(a: string)'], examples: ['UPPER([col1])'], category: 'strings', returnType: 'string', }, LOWER: { handler(args) { return String(args[0]).toLowerCase(); }, description: 'Converts a string to Lower Case', signatures: ['LOWER(a: string)'], examples: ['LOWER([col1])'], category: 'strings', returnType: 'string', }, REPLACE: { handler(args, context) { if ((0, expressionFunctionUtils_1.isTextSearchCaseInsensitive)(context)) { return String(args[0]).replace(new RegExp(args[1], 'ig'), args[2]); } else { return String(args[0]).replace(args[1], args[2]); } }, description: 'Searches string for specified value returning new string with replaced values', signatures: ['REPLACE(a: string, b: string, c:string)'], examples: ['REPLACE([col1], "GBP", "EUR")'], category: 'strings', returnType: 'string', }, CONCAT: { handler(args) { return args.join(' '); }, description: 'Concatenates multiple strings', signatures: ['CONCAT(value1, value2, value3)'], examples: ['CONCAT([col1], [col2], [col3]'], category: 'strings', returnType: 'string', }, PERCENT_CHANGE: { handler(args, context) { if (!context.dataChangedEvent) { throw new ExpressionEvaluationError_1.ExpressionEvaluationError('PERCENT_CHANGE', 'is only valid in changed cell contexts'); } const currentValue = (0, expressionFunctionUtils_1.getNumericValue)(args[0], true); if (Number.isNaN(currentValue)) { throw new ExpressionEvaluationError_1.ExpressionEvaluationError('PERCENT_CHANGE', 'First argument should be numeric'); } const previousValue = context.dataChangedEvent.oldValue; const increaseDecrease = args[1]; let result; if (!increaseDecrease) { result = sanitizeNumericResult(Math.abs(((currentValue - previousValue) / previousValue) * 100)); } else if (increaseDecrease === 'INCREASE') { result = currentValue - previousValue > 0 ? sanitizeNumericResult(((currentValue - previousValue) / previousValue) * 100) : undefined; } else if (increaseDecrease === 'DECREASE') { result = currentValue - previousValue < 0 ? sanitizeNumericResult(((previousValue - currentValue) / previousValue) * 100) : undefined; } else { throw new ExpressionEvaluationError_1.ExpressionEvaluationError('PERCENT_CHANGE', 'Optional second argument must be "INCREASE" or "DECREASE"'); } return result; }, description: "Returns the percentage difference between a cell's current value and its previous value.", signatures: [ 'PERCENT_CHANGE( [colName], <INCREASE|DECREASE> )', 'PERCENT_CHANGE( COL(name: string), <INCREASE|DECREASE>)', ], examples: [ 'PERCENT_CHANGE([col1])', 'PERCENT_CHANGE([col1], "INCREASE")', 'PERCENT_CHANGE([col1], "DECREASE")', ], category: 'changes', returnType: 'number', }, ABSOLUTE_CHANGE: { handler(args, context) { if (!context.dataChangedEvent) { throw new ExpressionEvaluationError_1.ExpressionEvaluationError('ABSOLUTE_CHANGE', 'is only valid in changed cell contexts'); } const currentValue = (0, expressionFunctionUtils_1.getNumericValue)(args[0], true); if (Number.isNaN(currentValue)) { throw new ExpressionEvaluationError_1.ExpressionEvaluationError('ABSOLUTE_CHANGE', 'First argument should be numeric'); } const previousValue = context.dataChangedEvent.oldValue; return currentValue - previousValue; }, description: "Returns the absolute difference between a cell's current value and its previous value.", signatures: ['ABSOLUTE_CHANGE( [colName] )', 'ABSOLUTE_CHANGE( COL(name: string) )'], examples: ['ABSOLUTE_CHANGE([col1])'], category: 'changes', returnType: 'number', }, ANY_CHANGE: { handler(args, context) { if (!context.dataChangedEvent) { throw new ExpressionEvaluationError_1.ExpressionEvaluationError('ANY_CHANGE', 'is only valid in changed cell contexts'); } const columnArg = args[0]; if (!columnArg) { // if no column is provided, we check if any value has changed return context.dataChangedEvent.oldValue !== context.dataChangedEvent.newValue; } // only COL argument is supported if (columnArg.type !== 'COL') { throw new ExpressionEvaluationError_1.ExpressionEvaluationError('ANY_CHANGE', 'accepts only a column reference as an argument'); } const currentColumnValue = (0, evaluator_1.evaluateNode)(columnArg, context); const previousValue = context.dataChangedEvent.oldValue; return currentColumnValue !== previousValue; }, description: "Returns true if a cell's current value is different from its previous value, otherwise false. If no column is provided, it checks if any value has changed.", signatures: ['ANY_CHANGE( [colName] )', 'ANY_CHANGE()'], examples: ['ANY_CHANGE([col1])', 'ANY_CHANGE()'], category: 'changes', returnType: 'boolean', hasEagerEvaluation: true, }, }; exports.scalarExpressionFunctionNames = (0, TypeExtensions_1.getTypedKeys)(exports.scalarExpressionFunctions);