@adaptabletools/adaptable
Version:
Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
191 lines (190 loc) • 8.81 kB
JavaScript
import parseInt from 'lodash/parseInt';
import { ExpressionEvaluationError } from '../../parser/src/ExpressionEvaluationError';
import { handleColumnFunction, handleWhereFunction, } from './expressionFunctionUtils';
import { aggregatedScalarExpressionFunctions, } from './aggregatedScalarExpressionFunctions';
import { getTypedKeys } from '../Extensions/TypeExtensions';
const SUPPORTED_AGGREGATION_FNS = [
'SUM',
'MIN',
'MAX',
'AVG',
'COUNT',
];
const aggregationScalarOperandMap = {
SUM: ['string', 'number'],
MIN: ['string', 'number'],
MAX: ['string', 'number'],
AVG: ['string', 'number'],
COUNT: ['string', 'number'],
};
// 0. optional non-capturing block of empty spaces
// 1. block containing a numeric value (any digits) (required)
// 2. block containing a single 'K', 'M' or 'B' letter (optional) (case insensitive)
const CRITERIA_REGEX = /^(?:\s*)(\d+)(K|M|B)?$/i;
export const aggregatedBooleanExpressionFunctions = {
WHERE: {
handler(args, context) {
return handleWhereFunction(args, context);
},
isHiddenFromMenu: true,
description: 'Splits 2 composed queries, allowing for a main query(lhs) to have a where clause query(rhs) defined',
signatures: ['<main_query> WHERE <boolean_query>'],
examples: ['<main_query> WHERE <boolean_query>', '<main_query> WHERE QUERY("abc")'],
hasEagerEvaluation: true,
category: 'conditional',
},
SUM: aggregatedScalarExpressionFunctions['SUM'],
MIN: aggregatedScalarExpressionFunctions['MIN'],
MAX: aggregatedScalarExpressionFunctions['MAX'],
AVG: aggregatedScalarExpressionFunctions['AVG'],
WEIGHT: aggregatedScalarExpressionFunctions['WEIGHT'],
COUNT: aggregatedScalarExpressionFunctions['COUNT'],
GROUP_BY: aggregatedScalarExpressionFunctions['GROUP_BY'],
EQ: {
handler(args, context) {
return buildBooleanAggregationParameter(args, context, '=', (aggregatedValue, conditionValue) => aggregatedValue === conditionValue);
},
isHiddenFromMenu: true,
description: 'Evaluates if the aggregation result equals a numerical value defined either as a number or a string abbreviation for thousands(K), millions(M) or billions(B)',
signatures: [
'SUM() = number',
`SUM() = '%number%(K|M|B)'`,
'EQ(a: SUM(), b: number)',
`EQ(a: SUM(), b: '%number%(K|M|B)')`,
],
examples: [`SUM([col1]) = '5M'`, 'EQ( SUM([col1]), 250000)'],
category: 'comparison',
},
NEQ: {
handler(args, context) {
return buildBooleanAggregationParameter(args, context, '!=', (aggregatedValue, conditionValue) => aggregatedValue !== conditionValue);
},
isHiddenFromMenu: true,
description: 'Evaluates if the aggregation result is NOT equal with a numerical value defined either as a number or a string abbreviation for thousands(K), millions(M) or billions(B)',
signatures: [
'SUM() != number',
`SUM() != '%number%(K|M|B)'`,
'NEQ(a: SUM(), b: number)',
`NEQ(a: SUM(), b: '%number%(K|M|B)')`,
],
examples: [`SUM([col1]) != '5M'`, 'NEQ( SUM([col1]), 250000)'],
category: 'comparison',
},
LT: {
handler(args, context) {
return buildBooleanAggregationParameter(args, context, '<', (aggregatedValue, conditionValue) => aggregatedValue < conditionValue);
},
isHiddenFromMenu: true,
description: 'Evaluates if the aggregation result is less than a numerical value defined either as a number or a string abbreviation for thousands(K), millions(M) or billions(B)',
signatures: [
'SUM() < number',
`SUM() < '%number%(K|M|B)'`,
'LT(a: SUM(), b: number)',
`LT(a: SUM(), b: '%number%(K|M|B)')`,
],
examples: [`SUM([col1]) < '5M'`, 'LT( SUM([col1]), 250000)'],
category: 'comparison',
},
LTE: {
handler(args, context) {
return buildBooleanAggregationParameter(args, context, '<=', (aggregatedValue, conditionValue) => aggregatedValue <= conditionValue);
},
isHiddenFromMenu: true,
description: 'Evaluates if the aggregation result is less than or equals a numerical value defined either as a number or a string abbreviation for thousands(K), millions(M) or billions(B)',
signatures: [
'SUM() <= number',
`SUM() <= '%number%(K|M|B)'`,
'LTE(a: SUM(), b: number)',
`LTE(a: SUM(), b: '%number%(K|M|B)')`,
],
examples: [`SUM([col1]) <= '5M'`, 'LTE( SUM([col1]), 250000)'],
category: 'comparison',
},
GT: {
handler(args, context) {
return buildBooleanAggregationParameter(args, context, '>', (aggregatedValue, conditionValue) => aggregatedValue > conditionValue);
},
isHiddenFromMenu: true,
description: 'Evaluates if the aggregation result is greater than a numerical value defined either as a number or a string abbreviation for thousands(K), millions(M) or billions(B)',
signatures: [
'SUM() > number',
`SUM() > '%number%(K|M|B)'`,
'GT(a: SUM(), b: number)',
`GT(a: SUM(), b: '%number%(K|M|B)')`,
],
examples: [`SUM([col1]) > '5M'`, 'GT( SUM([col1]), 250000)'],
category: 'comparison',
},
GTE: {
handler(args, context) {
return buildBooleanAggregationParameter(args, context, '>=', (aggregatedValue, conditionValue) => aggregatedValue >= conditionValue);
},
isHiddenFromMenu: true,
description: 'Evaluates if the aggregation result is greater than or equals a numerical value defined either as a number or a string abbreviation for thousands(K), millions(M) or billions(B)',
signatures: [
'SUM() >= number',
`SUM() >= '%number%(K|M|B)'`,
'GTE(a: SUM(), b: number)',
`GTE(a: SUM(), b: '%number%(K|M|B)')`,
],
examples: [`SUM([col1]) >= '5M'`, 'GTE( SUM([col1]), 250000)'],
category: 'comparison',
},
COL: {
handler(args, context) {
return handleColumnFunction(args, context);
},
description: 'References a column by its unique identifier',
signatures: ['[colName]', 'COL(name: string)'],
examples: ['[col1]', "COL('col1')"],
category: 'special',
},
};
const buildBooleanAggregationParameter = (args, context, comparisonOperator, conditionFn) => {
// LHS
const aggregationOperand = extractScalarAggregationOperand(comparisonOperator, args[0]);
// RHS
const scalarOperand = extractScalarOperand(comparisonOperator, args[1], aggregationScalarOperandMap[aggregationOperand.name]);
return {
type: 'aggregationBoolean',
name: aggregationOperand.name,
scalarAggregation: aggregationOperand,
conditionValue: scalarOperand,
conditionFn,
};
};
const extractScalarAggregationOperand = (consumingFunctionName, argument) => {
const result = argument;
if (result == undefined ||
result.type !== 'aggregationScalar' ||
!SUPPORTED_AGGREGATION_FNS.includes(result.name)) {
throw new ExpressionEvaluationError(consumingFunctionName, `comparison operator expects as a left-hand operand an aggregation function of type '${SUPPORTED_AGGREGATION_FNS.join(' | ')}'`);
}
return result;
};
const extractScalarOperand = (consumingFunctionName, value, allowedTypes) => {
if (value == undefined || !allowedTypes.find((allowedType) => typeof value === allowedType)) {
throw new ExpressionEvaluationError(consumingFunctionName, `comparison operator expects as a right-hand operand a scalar value of type '${allowedTypes.join(' | ')}'`);
}
if (typeof value === 'number') {
return value;
}
// assertion is safe, we checked in the IF above
const stringValue = value;
const matchingResult = stringValue.match(CRITERIA_REGEX);
const numericString = matchingResult?.[1];
const largeNumberUnit = matchingResult?.[2] ?? 'number';
if (!numericString) {
throw new ExpressionEvaluationError('Numeric abbreviation', `"${stringValue}" is invalid, expecting a number with a suffix for thousands(K), millions(M) or billions(B)"`);
}
// unit -> number
const numberUnitRatios = {
number: 1,
k: 1000,
m: 1000000,
b: 1000000000,
};
// numeric value
return parseInt(numericString) * numberUnitRatios[largeNumberUnit.toLowerCase()];
};
export const aggregatedBooleanExpressionFunctionNames = getTypedKeys(aggregatedBooleanExpressionFunctions);