Skip to content

Commit

Permalink
New expression functions: compare, string functions and more (#2961)
Browse files Browse the repository at this point in the history
Co-authored-by: Ole Martin Handeland <[email protected]>
  • Loading branch information
olemartinorg and Ole Martin Handeland authored Feb 12, 2025
1 parent d94f4fd commit 414fbc8
Show file tree
Hide file tree
Showing 121 changed files with 1,295 additions and 264 deletions.
108 changes: 105 additions & 3 deletions schemas/json/layout/expression.schema.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@
{ "$ref": "#/definitions/func-upperCase" },
{ "$ref": "#/definitions/func-_experimentalSelectAndMap" },
{ "$ref": "#/definitions/func-argv"},
{ "$ref": "#/definitions/func-value"}
{ "$ref": "#/definitions/func-value"},
{ "$ref": "#/definitions/func-stringReplace" },
{ "$ref": "#/definitions/func-stringSlice" },
{ "$ref": "#/definitions/func-upperCaseFirst" },
{ "$ref": "#/definitions/func-lowerCaseFirst" }
]
},
"boolean": {
Expand Down Expand Up @@ -112,7 +116,9 @@
{ "$ref": "#/definitions/func-endsWith" },
{ "$ref": "#/definitions/func-startsWith" },
{ "$ref": "#/definitions/func-commaContains" },
{ "$ref": "#/definitions/func-hasRole" }
{ "$ref": "#/definitions/func-hasRole" },
{ "$ref": "#/definitions/func-compare" },
{ "$ref": "#/definitions/func-compare-inverse" }
]
},
"number": {
Expand All @@ -128,7 +134,9 @@
"title": "Any expression returning a number (strict)",
"anyOf": [
{ "type": "number", "title": "Numeric constant" },
{ "$ref": "#/definitions/func-stringLength" }
{ "$ref": "#/definitions/func-stringLength" },
{ "$ref": "#/definitions/func-stringIndexOf" },
{ "$ref": "#/definitions/func-countDataElements" }
]
},
"func-if": {
Expand Down Expand Up @@ -180,6 +188,16 @@
],
"additionalItems": false
},
"func-countDataElements": {
"title": "Count data elements function",
"description": "This function will count the number of data elements of the specified data type in the current instance",
"type": "array",
"items": [
{ "const": "countDataElements" },
{ "$ref": "#/definitions/string" }
],
"additionalItems": false
},
"func-externalApi": {
"title": "External API lookup function",
"description": "This function will look up a value from an external API defined in the app metadata. It uses JSON dot notation for referencing the API response structure.",
Expand Down Expand Up @@ -357,6 +375,31 @@
],
"additionalItems": false
},
"func-compare": {
"title": "Compare function",
"description": "This function compares two values (or expressions) and returns a boolean. The first and last arguments are values, and the second argument is a comparison operator.",
"type": "array",
"items": [
{ "const": "compare" },
{ "$ref": "#/definitions/any" },
{ "$ref": "#/definitions/compare-operator" },
{ "$ref": "#/definitions/any" }
],
"additionalItems": false
},
"func-compare-inverse": {
"title": "Inverse compare function",
"description": "This function compares two values (or expressions) and returns a boolean. The first and last arguments are values, and the third argument is a comparison operator.",
"type": "array",
"items": [
{ "const": "compare" },
{ "$ref": "#/definitions/any" },
{ "const": "not" },
{ "$ref": "#/definitions/compare-operator" },
{ "$ref": "#/definitions/any" }
],
"additionalItems": false
},
"func-linkToComponent": {
"title": "Link to component function",
"description": "This function returns a link to a specific component in a form",
Expand Down Expand Up @@ -453,6 +496,18 @@
],
"additionalItems": false
},
"func-stringReplace": {
"title": "String replace function",
"description": "This function replaces all occurrences of a substring in a string with another string",
"type": "array",
"items": [
{ "const": "stringReplace" },
{ "$ref": "#/definitions/string" },
{ "$ref": "#/definitions/string" },
{ "$ref": "#/definitions/string" }
],
"additionalItems": false
},
"func-stringLength": {
"title": "String length function",
"description": "This function returns the length of a string",
Expand All @@ -463,6 +518,29 @@
],
"additionalItems": false
},
"func-stringSlice": {
"title": "String slice function",
"description": "This function returns a substring of a string (from start index to end index, can be negative)",
"type": "array",
"items": [
{ "const": "stringSlice" },
{ "$ref": "#/definitions/string" },
{ "$ref": "#/definitions/number" },
{ "$ref": "#/definitions/number" }
],
"additionalItems": false
},
"func-stringIndexOf": {
"title": "String index of function",
"description": "This function returns the index of the first occurrence of a substring in a string. If none is found, null is returned.",
"type": "array",
"items": [
{ "const": "stringIndexOf" },
{ "$ref": "#/definitions/string" },
{ "$ref": "#/definitions/string" }
],
"additionalItems": false
},
"func-commaContains": {
"title": "Comma contains function",
"description": "This function checks if the first comma-separated string contains the second string",
Expand Down Expand Up @@ -494,6 +572,26 @@
],
"additionalItems": false
},
"func-lowerCaseFirst": {
"title": "Lower case first function",
"description": "This function converts the first character of a string to lower case",
"type": "array",
"items": [
{ "const": "lowerCaseFirst" },
{ "$ref": "#/definitions/string" }
],
"additionalItems": false
},
"func-upperCaseFirst": {
"title": "Upper case first function",
"description": "This function converts the first character of a string to upper case",
"type": "array",
"items": [
{ "const": "upperCaseFirst" },
{ "$ref": "#/definitions/string" }
],
"additionalItems": false
},
"func-_experimentalSelectAndMap": {
"title": "Experimental Select and map function",
"description": "This function takes a data model path which points to an array in the data model, and selects a specific property from the object in the containing array. It then concatenates the selected values into a single string separated by a space. The values can be prepended and appended with a string constant. This function is experimental and will be removed in future versions. Do not use unless you know what you're doing.",
Expand Down Expand Up @@ -527,6 +625,10 @@
{ "$ref": "#/definitions/string" }
],
"additionalItems": false
},
"compare-operator": {
"title": "Comparison operator",
"enum": ["equals", "greaterThan", "greaterThanEq", "lessThan", "lessThanEq", "isAfter", "isBefore", "isAfterEq", "isBeforeEq", "isSameDay"]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import comboboxClasses from 'src/styles/combobox.module.css';
import { useNodes } from 'src/utils/layout/NodesContext';
import { useExpressionDataSources } from 'src/utils/layout/useExpressionDataSources';
import { useNodeTraversal, useNodeTraversalSelector } from 'src/utils/layout/useNodeTraversal';
import type { ExprConfig, Expression, ExprFunction } from 'src/features/expressions/types';
import type { ExprConfig, Expression, ExprFunctionName } from 'src/features/expressions/types';
import type { LayoutNode } from 'src/utils/layout/LayoutNode';
import type { LayoutPage } from 'src/utils/layout/LayoutPage';

Expand Down Expand Up @@ -131,7 +131,7 @@ export const ExpressionPlayground = () => {

const calls: string[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onAfterFunctionCall = (path: string[], func: ExprFunction, args: any[], result: any) => {
const onAfterFunctionCall = (path: string[], func: ExprFunctionName, args: any[], result: any) => {
const indent = ' '.repeat(path.length);
calls.push(`${indent}${JSON.stringify([func, ...args])} => ${JSON.stringify(result)}`);
};
Expand Down
8 changes: 6 additions & 2 deletions src/features/expressions/errors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { prettyErrors } from 'src/features/expressions/prettyErrors';
import { ValidationErrorMessage } from 'src/features/expressions/validation';
import type { ExprConfig, Expression } from 'src/features/expressions/types';

export class ExprRuntimeError extends Error {
Expand All @@ -17,9 +18,12 @@ export class UnknownTargetType extends ExprRuntimeError {
}
}

export class UnknownSourceType extends ExprRuntimeError {
export class UnknownArgType extends ExprRuntimeError {
public constructor(expression: Expression, path: string[], type: string, supported: string) {
super(expression, path, `Received unsupported type '${type}, only ${supported} are supported'`);
let paramIdx = 0;
const params = [supported, type];
const newMessage = ValidationErrorMessage.ArgWrongType.replaceAll('%s', () => params[paramIdx++]);
super(expression, path, newMessage);
}
}

Expand Down
Loading

0 comments on commit 414fbc8

Please sign in to comment.