Skip to main content

Rule expression builder

The rule expression builder is a UI component which allows the user to configure boolean expressions upon a set of data. The generated expression can then be directly executed by the evaluator and criteria back-end services.

Use cases:

  • Allow the user to define a set of filters which can be used on a dataset.
  • Configure predicate expressions to execute actions when specific conditions are realized.
  • Allow run-time configuration of rules that dictate application function, which typically would require developer code changes.
tip

This component is based on the expression builder library and is pre-configured to create boolean expressions that the back-end can work with.

If you want to set your own operations and other configurations, you can create your own component using that base library.

Example

You can add, change, or delete a rule to create different expressions based upon input fields. You can use groups to join rules using specific boolean combinators (AND and OR) to create complex expressions. The json payload that is created from your expression is displayed on the right-hand side. When you change the expression model on the UI, you can see the payload change to show the result. Click the button to show the model if it's currently hidden.

Notice that if a rule is highlighted in red when it's partially defined (e.g. you've chosen the field but not an operator), then the model payload on the right-hand side won't change. This is because the back-end model doesn't support partial rules. It is possible to look at the underlying model, which contains a tree structure even for partial rules.

Hide Model
{
  "TYPE": "PREDICATE_EXPRESSION",
  "OPERATION": "OR",
  "EXPRESSIONS": [
    {
      "TYPE": "BINARY_EXPRESSION",
      "LEFT": {
        "TYPE": "FIELD",
        "NAME": "age"
      },
      "OPERATION": "GREATER_THAN",
      "RIGHT": {
        "TYPE": "NUMBER",
        "VALUE": "18"
      }
    },
    {
      "TYPE": "PREDICATE_EXPRESSION",
      "OPERATION": "AND",
      "EXPRESSIONS": [
        {
          "TYPE": "BINARY_EXPRESSION",
          "LEFT": {
            "TYPE": "FIELD",
            "NAME": "country"
          },
          "OPERATION": "EQUALS",
          "RIGHT": {
            "TYPE": "STRING",
            "VALUE": "uk"
          }
        },
        {
          "TYPE": "BINARY_EXPRESSION",
          "LEFT": {
            "TYPE": "FIELD",
            "NAME": "isActive"
          },
          "OPERATION": "EQUALS",
          "RIGHT": {
            "TYPE": "BOOLEAN",
            "VALUE": true
          }
        }
      ]
    }
  ]
}

Declaration

<rapid-rule-expression-builder></rapid-rule-expression-builder>

Usage

import { ExpressionBuilderTypes, RuleExpression } from '@genesislcap/rapid-design-system';
// See further down for code snippet containing fields
import { fields } from './fields';
@customElement({
name: 'my-element',
template: html<MyElement>`
<rapid-rule-expression-builder
:ruleConfig=${(x) => x.ruleConfig}
@change=${(x) =>
x.handleChangedRuleExpression(
x.event as CustomEvent<RuleExpression.Expression['PredicateExpression']>,
)}
></rapid-rule-expression-builder>
`,
})
export class MyElement extends GenesisElement {
ruleConfig: ExpressionBuilderTypes.RuleConfig = {
fields, // from import
// you can also set the model and other configuration options here
}
handleChangedRuleExpression(e: CustomEvent<RuleExpression.Expression['PredicateExpression']>){
const newModel = e.detail;
console.log({newModel});
}
}

Expand to see example fields you can use with the rule expression builder for testing and learning

Date filtering

When working with date or date-time fields, the rule expression builder provides specialized operators to filter records based on dynamic time periods relative to the current server time: Is current and Is previous.

These operators allow you to select a time unit (HOUR, DAY, WEEK, MONTH, YEAR) to define the range.

warning

Before calculating the time range for any of these operations, the current time will be converted to the UTC timezone, and that time will be used to calculate the range. Please factor this in when running or scheduling certain operations.

For example, if you execute a filter or schedule a report to run at 23:00 EST on 2023-10-25, and want to filter all transactions for the current day, this will first be converted to 04:00 UTC on 2023-10-26, giving a time range of:

2023-10-26 00:00:00 UTC to 2023-10-27 00:00:00 UTC

This is in the future, and will likely return an empty data set.

The examples below assume the current server time is Wednesday, 2023-10-25 14:35:00 UTC.

Time unitOperatorCommon termDescriptionRange calculationExample (Start <= date < End)
HOURIs currentCurrent hourMatches times falling within the current hour.
  • Start: Beginning of the current hour.
  • End: Beginning of the next hour.

2023-10-25 14:00:00
to
2023-10-25 15:00:00

HOURIs previousPrevious hourMatches times falling within the previous hour.
  • Start: Beginning of the hour immediately preceding the current one.
  • End: Beginning of the current hour.

2023-10-25 13:00:00
to
2023-10-25 14:00:00

DAYIs currentTodayMatches dates falling within the current day (Today).
  • Start: 00:00:00 on the current day.
  • End: 00:00:00 on the next day.

2023-10-25 00:00:00
to
2023-10-26 00:00:00

DAYIs previousYesterdayMatches dates falling within the previous day (Yesterday).
  • Start: 00:00:00 on the day before today.
  • End: 00:00:00 on the current day.

2023-10-24 00:00:00
to
2023-10-25 00:00:00

WEEKIs currentThis weekMatches dates falling within the current week. Weeks are assumed to start on Monday.
  • Start: 00:00:00 on the Monday of the current week.
  • End: 00:00:00 on the Monday of the following week.

2023-10-23 00:00:00
to
2023-10-30 00:00:00

WEEKIs previousLast weekMatches dates falling within the previous week.
  • Start: 00:00:00 on the Monday of the previous week.
  • End: 00:00:00 on the Monday of the current week.

2023-10-16 00:00:00
to
2023-10-23 00:00:00

MONTHIs currentThis monthMatches dates falling within the current month.
  • Start: 00:00:00 on the 1st day of the current month.
  • End: 00:00:00 on the 1st day of the next month.

2023-10-01 00:00:00
to
2023-11-01 00:00:00

MONTHIs previousLast monthMatches dates falling within the previous month.
  • Start: 00:00:00 on the 1st day of the previous month.
  • End: 00:00:00 on the 1st day of the current month.

2023-09-01 00:00:00
to
2023-10-01 00:00:00

YEARIs currentThis yearMatches dates falling within the current year.
  • Start: 00:00:00 on January 1st of the current year.
  • End: 00:00:00 on January 1st of the next year.

2023-01-01 00:00:00
to
2024-01-01 00:00:00

YEARIs previousLast yearMatches dates falling within the previous year.
  • Start: 00:00:00 on January 1st of the previous year.
  • End: 00:00:00 on January 1st of the current year.

2022-01-01 00:00:00
to
2023-01-01 00:00:00

Example

Here is an example of the rule expression builder configured with a rule to filter for "Yesterday".

Hide Model
{
  "TYPE": "PREDICATE_EXPRESSION",
  "OPERATION": "AND",
  "EXPRESSIONS": [
    {
      "TYPE": "METHOD_EXPRESSION",
      "METHOD": "DATE_TIME_IS_IN_RANGE",
      "PARAMETERS": [
        {
          "TYPE": "FIELD",
          "NAME": "lastUpdated"
        },
        {
          "TYPE": "STRING",
          "VALUE": "DAY"
        },
        {
          "TYPE": "STRING",
          "VALUE": "PREVIOUS"
        }
      ]
    }
  ]
}

Tips

The expressions are evaluated on each entity sequentially; this means for long running processes the current time is changing.

For example, consider you're scheduling a report to run every hour which collates the trades for that hour, and you're using this component to configure the report filters. If you schedule the report to run just before the hour and then filter on Is current HOUR then if the report takes a long time then the later filters might apply for the next hour, missing out trades. To solve problems like this it's recommended that you would instead schedule the report for the next hour and then you can use Is previous Hour to filter on the same entities.

DOM API

Property and attribute binding examples for Genesis Component syntax. Closing tag omitted.

Attributes

This component doesn't have any attributes.

Properties

PropertyTypeUseExample
ruleConfigExpressionBuilderTypes.RuleConfigThe primary configuration point for the component which allows you to specify input fields, optionally set the model, among other configuration items.
<rapid-rule-expression-builder :ruleConfig="${(x) => x.ruleConfig}">
stylesExpressionBuilderTypes.StylesAllows you to change which custom elements and their styles are used inside of the component. This isn't recommended to be used, if you want to use a custom style for an element, such as the checkbox, you should override its style when registering the component.
<rapid-rule-expression-builder :styles="${(x) => x.styles}">
modelRuleExpression.Expression['PredicateExpression']This is a readonly property and therefore shouldn't be set directly - if you want to set the model then do it via the optional model property on the ruleConfig block. This property can be used to receive the precise underlying model the UI state currently represents. This can be useful when the user has configured a partial rule (e.g. only selected a field in a rule) which isn't a valid server rule and therefore will not be shown on the change event payload.
const model = document.querySelector('rapid-rule-expression-builder').model;

Slots

This component doesn't have any slots.

Parts

This component doesn't have any css parts. You should instead override the styling by changing the constituent component's styling during registration, or using the styles properties for advanced users.

Fired events

EventTypeUseExample
changeRuleExpression.Expression['PredicateExpression']Emits whenever the user changes the state of the UI component. The event detail contains the predicate expression payload the server expects. As the server expression must always contain non-partial rules, the payload may be slightly out of sync with the UI if the user has a partial rule. You can use the model property on the component to get the precise underlying state of the component if you wish.
<rapid-rule-expression-builder
@change=${(x) =>
x.handleChangedRuleExpression(
x.event as CustomEvent<RuleExpression.Expression['PredicateExpression']>,
)}
>

Listened events

This component doesn't listen to any events.

Predicate expressions

The payload that the component produces is a predicate expression, which is a tree structure expression. Each rule on the UI maps to either a method or a binary expression.

  • A binary expression example is when FIRST_NAME EQUALS "John".
  • A method expression example is when FIRST_NAME CONTAINS "Matt".
  • A predicate expression example is when we join the first two examples FIRST_NAME EQUALS "John" (AND) FIRST_NAME CONTAINS "Matt".

Depending on the configuration of the expression builder, you can either allow no nested expressions, or as many nested expressions as possible!

These expressions can then be used directly with the evaluator and criteria back-end services. You can also write your own server functions which work with these payloads if you wish.

Using server fields

As shown in the example code at the top, you can define the fields in which the user can use in the component directly in the front end, and of course you can generate them dynamically, or construct them.

A likely use case is where you want the fields to match fields from specific tables in your database. To help streamline this process, the platform provides a number of utilities and functions.

import { Connect } from '@genesislcap/foundation-comms';
import { ExpressionBuilderTypes } from '@genesislcap/rapid-design-system';
import { mapJsonSchemaFieldToExprBuilderField } from '@genesislcap/foundation-utils';

// Request the fields via the JSON schema for a specific endpoint
// You need to get a reference to a "connect" instance from `foundation-comms` via
// dependency injection in your chosen platform. Ensure you also are in an async function
// to be able to await, or use `Promise.then`
const resourceSchema = await this.connect.getJSONSchema('TRADE')
// Genesis server should not ever return the following formats, but they're valid
// valid JSON schema shapes so we need to check the cases
if (
typeof resourceSchema.OUTBOUND?.properties?.REPLY === 'boolean' ||
Array.isArray(resourceSchema.OUTBOUND?.properties?.REPLY?.items) ||
typeof resourceSchema.OUTBOUND?.properties?.REPLY?.items === 'boolean'
) {
console.log('Malformed error shape');
return;
}
// Use the utility function to map the json schema properties which are defined
// on the response. Filter out null values (e.g. NANO_TIMESTAMP server fields are not
// valid expression builder fields)
const fields = Object.entries(
resourceSchema.OUTBOUND?.properties?.REPLY?.items?.properties ?? {},
).map(mapJsonSchemaFieldToExprBuilderField).filter(Boolean);
// Finally use those fields in the config for the rule expression builder
const ruleConfig: ExpressionBuilderTypes.RuleConfig = {
fields,
};