@o3r/rules-engine
Version:
This module provides a rule engine that can be executed on your Otter application to customize your application (translations, placeholders and configs) based on a json file generated by your CMS.
2,573 lines • 175 kB
JavaScript
import * as i0 from '@angular/core';
import { Pipe, input, signal, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, Input, NgModule, InjectionToken, Injectable, Optional, Inject, inject, DestroyRef } from '@angular/core';
import * as i1 from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import * as i1$1 from '@angular/common';
import { CommonModule, JsonPipe } from '@angular/common';
import { Subject, of, from, ReplaySubject, Observable, debounceTime, combineLatest, BehaviorSubject, merge, firstValueFrom, fromEvent } from 'rxjs';
import { switchMap, delay, startWith, mergeMap, map, catchError, withLatestFrom, concatMap, tap, share, shareReplay, pairwise, distinctUntilChanged, takeUntil, filter, scan } from 'rxjs/operators';
import * as i1$3 from '@ngrx/store';
import { createAction, props, on, createReducer, StoreModule, createFeatureSelector, createSelector, select } from '@ngrx/store';
import * as i2 from '@o3r/logger';
import { LoggerModule } from '@o3r/logger';
import { asyncProps, fromApiEffectSwitchMap, asyncStoreItemAdapter, computeItemIdentifier, sendOtterMessage, filterMessageContent } from '@o3r/core';
import * as i1$2 from '@ngrx/effects';
import { createEffect, ofType, EffectsModule } from '@ngrx/effects';
import { createEntityAdapter } from '@ngrx/entity';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { JSONPath } from 'jsonpath-plus';
class O3rFallbackToPipe {
transform(value, fallback = 'undefined') {
return value === undefined ? fallback : value;
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: O3rFallbackToPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
/** @nocollapse */ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.13", ngImport: i0, type: O3rFallbackToPipe, isStandalone: true, name: "o3rFallbackTo" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: O3rFallbackToPipe, decorators: [{
type: Pipe,
args: [{ name: 'o3rFallbackTo' }]
}] });
class O3rJsonOrStringPipe {
/**
* @inheritDoc
*/
transform(value) {
if (typeof value === 'string') {
return value;
}
return JSON.stringify(value, null, 2);
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: O3rJsonOrStringPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
/** @nocollapse */ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.13", ngImport: i0, type: O3rJsonOrStringPipe, isStandalone: true, name: "o3rJsonOrString" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: O3rJsonOrStringPipe, decorators: [{
type: Pipe,
args: [{ name: 'o3rJsonOrString' }]
}] });
/**
* Compute the status of the execution depending on its execution event type, the output and whether the execution
* is still active
* @param rulesetExecution
* @param isActive
*/
const getStatus = (rulesetExecution, isActive) => {
if (rulesetExecution.type === 'RulesetExecutionError') {
return 'Error';
}
else if (rulesetExecution.outputActions?.length === 0) {
return 'NoEffect';
}
else if (isActive) {
return 'Active';
}
return 'Deactivated';
};
/**
* Transform the output of the debug reports into the model for the ruleset history debug panel
* @param events
* @param rulesetMap
*/
const rulesetReportToHistory = (events, rulesetMap) => {
const availableRulesets = (events.filter((e) => e.type === 'AvailableRulesets').reverse()[0])?.availableRulesets || [];
const lastActiveRulesets = (events.filter((e) => e.type === 'ActiveRulesets').reverse()[0])?.rulesets || [];
return availableRulesets
.filter((ruleset) => !!ruleset)
.reduce((acc, ruleset) => {
const rulesetExecutions = events
.filter((e) => ((e.type === 'RulesetExecutionError' || e.type === 'RulesetExecution') && e.rulesetId === ruleset.id));
if (rulesetExecutions) {
acc.push(...rulesetExecutions);
}
return acc;
}, [])
.sort((execA, execB) => execB.timestamp - execA.timestamp)
.map((rulesetExecution) => {
const rulesetInformation = rulesetMap[rulesetExecution.rulesetId];
const isActive = lastActiveRulesets.find((r) => r.id === rulesetExecution.rulesetId);
return {
...rulesetExecution,
status: getStatus(rulesetExecution, !!isActive),
isActive: !!isActive,
rulesetInformation,
rulesEvaluations: (rulesetExecution.rulesEvaluations || []).sort((evalA, evalB) => (rulesetInformation?.rules.findIndex((r) => r.id === evalA.rule.id) || -1)
- (rulesetInformation?.rules.findIndex((r) => r.id === evalB.rule.id) || -1))
};
});
};
class FactsSnapshotComponent {
constructor() {
/**
* Full list of available facts with their current value
*/
this.facts = input.required();
/**
* Search terms
*/
this.search = signal('');
/**
* Filtered list of facts using search terms
*/
this.filteredFacts = computed(() => {
const search = this.search();
const facts = this.facts();
if (search) {
const matchString = new RegExp(search.replace(/[\s#$()*+,.?[\\\]^{|}-]/g, '\\$&'), 'i');
return facts.filter(({ factName, value }) => matchString.test(factName)
|| (typeof value === 'object'
? matchString.test(JSON.stringify(value))
: matchString.test(String(value))));
}
else {
return facts;
}
});
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: FactsSnapshotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
/** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.13", type: FactsSnapshotComponent, isStandalone: true, selector: "o3r-facts-snapshot", inputs: { facts: { classPropertyName: "facts", publicName: "facts", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<section>\n @if (facts().length < 1) {\n <div class=\"m-2\">\n No facts are registered.\n </div>\n } @else {\n <div class=\"input-group\">\n <label class=\"input-group-text\" for=\"search-fact\">\n <span class=\"mx-1 fa-search\" aria-label=\"Search\"></span>\n </label>\n <input class=\"form-control\" [(ngModel)]=\"search\" type=\"text\" id=\"search-fact\" placeholder=\"Search for fact name or value\" />\n </div>\n <div class=\"mt-3\">\n List of <b>{{facts().length}}</b> registered facts\n @if (facts().length > filteredFacts().length) { (<b>{{filteredFacts().length}}</b> matching the search) }\n </div>\n <ul>\n @for (fact of filteredFacts(); track fact.factName) {\n <li>\n <span class=\"fact-name\">{{fact.factName}}: </span>\n <span class=\"fact-value\">{{fact.value | o3rJsonOrString}}</span>\n </li>\n }\n </ul>\n }\n</section>\n", styles: ["o3r-facts-snapshot .fact-name{color:#26c}o3r-facts-snapshot .fact-value{color:#c29}o3r-facts-snapshot .fact-name,o3r-facts-snapshot .fact-value{font-family:monospace}\n"], dependencies: [{ kind: "pipe", type: O3rJsonOrStringPipe, name: "o3rJsonOrString" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: FactsSnapshotComponent, decorators: [{
type: Component,
args: [{ selector: 'o3r-facts-snapshot', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
O3rJsonOrStringPipe,
FormsModule,
ReactiveFormsModule
], encapsulation: ViewEncapsulation.None, template: "<section>\n @if (facts().length < 1) {\n <div class=\"m-2\">\n No facts are registered.\n </div>\n } @else {\n <div class=\"input-group\">\n <label class=\"input-group-text\" for=\"search-fact\">\n <span class=\"mx-1 fa-search\" aria-label=\"Search\"></span>\n </label>\n <input class=\"form-control\" [(ngModel)]=\"search\" type=\"text\" id=\"search-fact\" placeholder=\"Search for fact name or value\" />\n </div>\n <div class=\"mt-3\">\n List of <b>{{facts().length}}</b> registered facts\n @if (facts().length > filteredFacts().length) { (<b>{{filteredFacts().length}}</b> matching the search) }\n </div>\n <ul>\n @for (fact of filteredFacts(); track fact.factName) {\n <li>\n <span class=\"fact-name\">{{fact.factName}}: </span>\n <span class=\"fact-value\">{{fact.value | o3rJsonOrString}}</span>\n </li>\n }\n </ul>\n }\n</section>\n", styles: ["o3r-facts-snapshot .fact-name{color:#26c}o3r-facts-snapshot .fact-value{color:#c29}o3r-facts-snapshot .fact-name,o3r-facts-snapshot .fact-value{font-family:monospace}\n"] }]
}] });
/**
* Duration of the notification for clipboard feature (in milliseconds)
*/
const NOTIFICATION_DURATION = 1750;
/**
* Minimal length required to enable clipboard feature
*/
const CLIPBOARD_FEATURE_LENGTH_THRESHOLD = 80;
class RuleKeyValuePresComponent {
constructor() {
/**
* Type of display:
* - 'state': `key: value`, `key: oldValue -> value` or `oldValue -> value`
* - 'assignment': `key = value`
*/
this.type = 'state';
this.shouldLimitCharactersForValue = true;
this.isClipBoardFeatureAvailableForValue = false;
this.isValuePrimitiveType = false;
this.shouldLimitCharactersForOldValue = true;
this.isClipBoardFeatureAvailableForOldValue = false;
this.isOldValuePrimitiveType = false;
this.triggerNotification = new Subject();
this.showNotification$ = this.triggerNotification.asObservable().pipe(switchMap(() => of(false).pipe(delay(NOTIFICATION_DURATION), startWith(true))));
}
isClipBoardFeatureAvailable(value) {
return !!(navigator.clipboard && value && value.length > CLIPBOARD_FEATURE_LENGTH_THRESHOLD);
}
ngOnChanges({ value, oldValue }) {
if (value) {
this.isValuePrimitiveType = value.currentValue === null || typeof value.currentValue !== 'object';
this.isClipBoardFeatureAvailableForValue = this.isClipBoardFeatureAvailable(this.isValuePrimitiveType ? String(value.currentValue) : JSON.stringify(value.currentValue));
}
if (oldValue) {
this.isOldValuePrimitiveType = oldValue.currentValue === null || typeof oldValue.currentValue !== 'object';
this.isClipBoardFeatureAvailableForOldValue = this.isClipBoardFeatureAvailable(this.isOldValuePrimitiveType ? String(oldValue.currentValue) : JSON.stringify(oldValue.currentValue));
}
}
async copyToClipBoard(content) {
await navigator.clipboard.writeText(content);
this.triggerNotification.next();
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RuleKeyValuePresComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
/** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: RuleKeyValuePresComponent, isStandalone: true, selector: "o3r-rule-key-value-pres", inputs: { key: "key", value: "value", oldValue: "oldValue", type: "type" }, usesOnChanges: true, ngImport: i0, template: "<span *ngIf=\"key\" class=\"input-key\">{{key}}<ng-container *ngIf=\"type === 'state'\">: </ng-container></span>\n<ng-container *ngIf=\"type === 'assignment'\"> = </ng-container>\n<ng-container *ngIf=\"oldValue\">\n <pre class=\"input-value\"\n [class.limit-characters]=\"shouldLimitCharactersForOldValue\"\n (click)=\"shouldLimitCharactersForOldValue = !shouldLimitCharactersForOldValue\"\n (keyup.enter)=\"shouldLimitCharactersForOldValue = !shouldLimitCharactersForOldValue\"\n tabindex=\"0\"\n >\n {{isOldValuePrimitiveType ? oldValue : (oldValue | json)}}\n </pre>\n <button (click)=\"copyToClipBoard(oldValue)\" *ngIf=\"isClipBoardFeatureAvailableForOldValue\" title=\"Copy to clipboard\">\uD83D\uDCCB</button>\n \u2192\n</ng-container>\n<pre class=\"input-value\"\n [class.limit-characters]=\"shouldLimitCharactersForValue\"\n (click)=\"shouldLimitCharactersForValue = !shouldLimitCharactersForValue\"\n (keyup.enter)=\"shouldLimitCharactersForValue = !shouldLimitCharactersForValue\"\n tabindex=\"0\"\n>\n {{isValuePrimitiveType ? value : (value | json)}}\n</pre>\n<button (click)=\"copyToClipBoard(value)\" *ngIf=\"isClipBoardFeatureAvailableForValue\" title=\"Copy to clipboard\">\uD83D\uDCCB</button>\n<div role=\"alert\" class=\"notification\" *ngIf=\"showNotification$ | async\">Copied to clipboard</div>\n", styles: ["o3r-rule-key-value-pres{position:relative}o3r-rule-key-value-pres .ruleset-panel-title,o3r-rule-key-value-pres .ruleset-panel-category-title{display:flex;justify-content:space-between;align-items:center}o3r-rule-key-value-pres .ruleset-panel-title{font-size:1rem;padding:.5rem 0 .1rem}o3r-rule-key-value-pres .ruleset-expansion-action,o3r-rule-key-value-pres .icon-caret-down,o3r-rule-key-value-pres .icon-caret-up{cursor:pointer}o3r-rule-key-value-pres .ruleset-panel-subtitle{font-size:.75rem}o3r-rule-key-value-pres .ruleset-panel-title-aside{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;min-width:fit-content}o3r-rule-key-value-pres .ruleset-panel-category-title{font-size:.95rem;background:#eee;padding:.5rem;margin-bottom:.5rem;margin-top:1rem}o3r-rule-key-value-pres .rule-description .ruleset-panel-category-title{font-size:.893rem;cursor:default}o3r-rule-key-value-pres .rule-description .ruleset-panel-title{font-size:.94rem}o3r-rule-key-value-pres .rule-description .ruleset-panel-category-body{padding-bottom:.5rem;padding-left:1.5rem}o3r-rule-key-value-pres .rule-description .ruleset-panel-category-body:empty{margin:0;padding:0 0 0 1.5rem}o3r-rule-key-value-pres .limit-characters{display:inline-block;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;max-width:38rem;vertical-align:bottom}o3r-rule-key-value-pres .input-value{margin-top:0;margin-bottom:0}o3r-rule-key-value-pres button{background:none;border:0;appearance:none}o3r-rule-key-value-pres .notification{position:absolute;padding:1rem 1.5rem;border-radius:5px;background:#444;color:#eee;right:0;z-index:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RuleKeyValuePresComponent, decorators: [{
type: Component,
args: [{ selector: 'o3r-rule-key-value-pres', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [CommonModule, JsonPipe], template: "<span *ngIf=\"key\" class=\"input-key\">{{key}}<ng-container *ngIf=\"type === 'state'\">: </ng-container></span>\n<ng-container *ngIf=\"type === 'assignment'\"> = </ng-container>\n<ng-container *ngIf=\"oldValue\">\n <pre class=\"input-value\"\n [class.limit-characters]=\"shouldLimitCharactersForOldValue\"\n (click)=\"shouldLimitCharactersForOldValue = !shouldLimitCharactersForOldValue\"\n (keyup.enter)=\"shouldLimitCharactersForOldValue = !shouldLimitCharactersForOldValue\"\n tabindex=\"0\"\n >\n {{isOldValuePrimitiveType ? oldValue : (oldValue | json)}}\n </pre>\n <button (click)=\"copyToClipBoard(oldValue)\" *ngIf=\"isClipBoardFeatureAvailableForOldValue\" title=\"Copy to clipboard\">\uD83D\uDCCB</button>\n \u2192\n</ng-container>\n<pre class=\"input-value\"\n [class.limit-characters]=\"shouldLimitCharactersForValue\"\n (click)=\"shouldLimitCharactersForValue = !shouldLimitCharactersForValue\"\n (keyup.enter)=\"shouldLimitCharactersForValue = !shouldLimitCharactersForValue\"\n tabindex=\"0\"\n>\n {{isValuePrimitiveType ? value : (value | json)}}\n</pre>\n<button (click)=\"copyToClipBoard(value)\" *ngIf=\"isClipBoardFeatureAvailableForValue\" title=\"Copy to clipboard\">\uD83D\uDCCB</button>\n<div role=\"alert\" class=\"notification\" *ngIf=\"showNotification$ | async\">Copied to clipboard</div>\n", styles: ["o3r-rule-key-value-pres{position:relative}o3r-rule-key-value-pres .ruleset-panel-title,o3r-rule-key-value-pres .ruleset-panel-category-title{display:flex;justify-content:space-between;align-items:center}o3r-rule-key-value-pres .ruleset-panel-title{font-size:1rem;padding:.5rem 0 .1rem}o3r-rule-key-value-pres .ruleset-expansion-action,o3r-rule-key-value-pres .icon-caret-down,o3r-rule-key-value-pres .icon-caret-up{cursor:pointer}o3r-rule-key-value-pres .ruleset-panel-subtitle{font-size:.75rem}o3r-rule-key-value-pres .ruleset-panel-title-aside{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;min-width:fit-content}o3r-rule-key-value-pres .ruleset-panel-category-title{font-size:.95rem;background:#eee;padding:.5rem;margin-bottom:.5rem;margin-top:1rem}o3r-rule-key-value-pres .rule-description .ruleset-panel-category-title{font-size:.893rem;cursor:default}o3r-rule-key-value-pres .rule-description .ruleset-panel-title{font-size:.94rem}o3r-rule-key-value-pres .rule-description .ruleset-panel-category-body{padding-bottom:.5rem;padding-left:1.5rem}o3r-rule-key-value-pres .rule-description .ruleset-panel-category-body:empty{margin:0;padding:0 0 0 1.5rem}o3r-rule-key-value-pres .limit-characters{display:inline-block;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;max-width:38rem;vertical-align:bottom}o3r-rule-key-value-pres .input-value{margin-top:0;margin-bottom:0}o3r-rule-key-value-pres button{background:none;border:0;appearance:none}o3r-rule-key-value-pres .notification{position:absolute;padding:1rem 1.5rem;border-radius:5px;background:#444;color:#eee;right:0;z-index:1}\n"] }]
}], propDecorators: { key: [{
type: Input
}], value: [{
type: Input
}], oldValue: [{
type: Input
}], type: [{
type: Input
}] } });
class RuleActionsPresComponent {
constructor() {
/**
* List of all the output actions of a rules or ruleset execution
*/
this.actions = [];
/**
* The list of temporary facts used and/or modified within the rule or the ruleset.
* They are scoped to the ruleset and their value is the one after the rule or ruleset execution.
*/
this.temporaryFacts = {};
/**
* List of temporary facts that will be modified by the ruleset or the rule.
*/
this.runtimeOutputs = [];
}
/**
* Check if a given block is of type ActionBlock
* @param block
*/
isActionBlock(block) {
return !!block.actionType;
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RuleActionsPresComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
/** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.13", type: RuleActionsPresComponent, isStandalone: true, selector: "o3r-rule-actions-pres", inputs: { actions: "actions", temporaryFacts: "temporaryFacts", runtimeOutputs: "runtimeOutputs" }, ngImport: i0, template: "<div class=\"ruleset-panel-category-title\">Output Actions</div>\n@if (actions?.length === 0 && runtimeOutputs.length === 0) {\n <div class=\"ruleset-panel-category-body empty\">\n No action\n </div>\n} @else {\n <ul class=\"ruleset-panel-category-body\">\n @for (action of actions; track $index) {\n <li>\n @if (isActionBlock(action)) {\n @switch (action.actionType) {\n @case ('SET_FACT') {\n <div>\n <div>Set Fact</div>\n <div>\n <o3r-rule-key-value-pres\n [key]=\"action.fact | o3rFallbackTo: 'Missing \\'fact\\''\"\n [value]=\"action.value | o3rFallbackTo\"\n [type]=\"'assignment'\"></o3r-rule-key-value-pres>\n </div>\n </div>\n }\n @case ('UPDATE_CONFIG') {\n <div>\n <div>Update Config {{action.component}} {{action.library}}</div>\n <div>\n <o3r-rule-key-value-pres\n [key]=\"action.property | o3rFallbackTo: 'Missing \\'property\\''\"\n [value]=\"action.value | o3rFallbackTo\"\n [type]=\"'assignment'\"></o3r-rule-key-value-pres>\n </div>\n </div>\n }\n @case ('UPDATE_ASSET') {\n <div>\n <div>Update Asset:</div>\n <div>\n <o3r-rule-key-value-pres\n [oldValue]=\"action.asset | o3rFallbackTo: 'Missing \\'asset\\''\"\n [value]=\"action.value | o3rFallbackTo\"\n [type]=\"'state'\"></o3r-rule-key-value-pres>\n </div>\n </div>\n }\n @case ('UPDATE_LOCALISATION') {\n <div>\n <div>Update localization:</div>\n <div>\n <o3r-rule-key-value-pres\n [oldValue]=\"action.key | o3rFallbackTo: 'Missing \\'key\\''\"\n [value]=\"action.value | o3rFallbackTo\"\n [type]=\"'state'\"></o3r-rule-key-value-pres>\n </div>\n </div>\n }\n @case ('UPDATE_PLACEHOLDER') {\n <div [class.error]=\"!action.placeholderId\">\n <div>Update placeholder in {{action.component}} {{action.library}}</div>\n <div>\n <o3r-rule-key-value-pres\n [oldValue]=\"action.placeholderId | o3rFallbackTo: 'Missing \\'placeholderId\\''\"\n [value]=\"action.value\"\n [type]=\"'state'\"></o3r-rule-key-value-pres>\n </div>\n </div>\n }\n @default {\n <div class=\"error\">\n <div>Unrecognized action</div>\n <div>{{action | json}}</div>\n </div>\n }\n }\n }\n </li>\n }\n @for (runtimeOutput of runtimeOutputs; track $index) {\n <li>\n <div>Set temporary fact</div>\n <div>\n <o3r-rule-key-value-pres\n [key]=\"runtimeOutput | o3rFallbackTo: 'Missing \\'fact\\''\"\n [value]=\"temporaryFacts[runtimeOutput] | o3rFallbackTo\"\n [type]=\"'assignment'\"></o3r-rule-key-value-pres>\n </div>\n </li>\n }\n </ul>\n}\n", styles: ["o3r-rule-actions-pres .ruleset-panel-title,o3r-rule-actions-pres .ruleset-panel-category-title{display:flex;justify-content:space-between;align-items:center}o3r-rule-actions-pres .ruleset-panel-title{font-size:1rem;padding:.5rem 0 .1rem}o3r-rule-actions-pres .ruleset-expansion-action,o3r-rule-actions-pres .icon-caret-down,o3r-rule-actions-pres .icon-caret-up{cursor:pointer}o3r-rule-actions-pres .ruleset-panel-subtitle{font-size:.75rem}o3r-rule-actions-pres .ruleset-panel-title-aside{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;min-width:fit-content}o3r-rule-actions-pres .ruleset-panel-category-title{font-size:.95rem;background:#eee;padding:.5rem;margin-bottom:.5rem;margin-top:1rem}o3r-rule-actions-pres .rule-description .ruleset-panel-category-title{font-size:.893rem;cursor:default}o3r-rule-actions-pres .rule-description .ruleset-panel-title{font-size:.94rem}o3r-rule-actions-pres .rule-description .ruleset-panel-category-body{padding-bottom:.5rem;padding-left:1.5rem}o3r-rule-actions-pres .rule-description .ruleset-panel-category-body:empty{margin:0;padding:0 0 0 1.5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "component", type: RuleKeyValuePresComponent, selector: "o3r-rule-key-value-pres", inputs: ["key", "value", "oldValue", "type"] }, { kind: "pipe", type: O3rFallbackToPipe, name: "o3rFallbackTo" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RuleActionsPresComponent, decorators: [{
type: Component,
args: [{ selector: 'o3r-rule-actions-pres', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [CommonModule, JsonPipe, RuleKeyValuePresComponent, O3rFallbackToPipe], template: "<div class=\"ruleset-panel-category-title\">Output Actions</div>\n@if (actions?.length === 0 && runtimeOutputs.length === 0) {\n <div class=\"ruleset-panel-category-body empty\">\n No action\n </div>\n} @else {\n <ul class=\"ruleset-panel-category-body\">\n @for (action of actions; track $index) {\n <li>\n @if (isActionBlock(action)) {\n @switch (action.actionType) {\n @case ('SET_FACT') {\n <div>\n <div>Set Fact</div>\n <div>\n <o3r-rule-key-value-pres\n [key]=\"action.fact | o3rFallbackTo: 'Missing \\'fact\\''\"\n [value]=\"action.value | o3rFallbackTo\"\n [type]=\"'assignment'\"></o3r-rule-key-value-pres>\n </div>\n </div>\n }\n @case ('UPDATE_CONFIG') {\n <div>\n <div>Update Config {{action.component}} {{action.library}}</div>\n <div>\n <o3r-rule-key-value-pres\n [key]=\"action.property | o3rFallbackTo: 'Missing \\'property\\''\"\n [value]=\"action.value | o3rFallbackTo\"\n [type]=\"'assignment'\"></o3r-rule-key-value-pres>\n </div>\n </div>\n }\n @case ('UPDATE_ASSET') {\n <div>\n <div>Update Asset:</div>\n <div>\n <o3r-rule-key-value-pres\n [oldValue]=\"action.asset | o3rFallbackTo: 'Missing \\'asset\\''\"\n [value]=\"action.value | o3rFallbackTo\"\n [type]=\"'state'\"></o3r-rule-key-value-pres>\n </div>\n </div>\n }\n @case ('UPDATE_LOCALISATION') {\n <div>\n <div>Update localization:</div>\n <div>\n <o3r-rule-key-value-pres\n [oldValue]=\"action.key | o3rFallbackTo: 'Missing \\'key\\''\"\n [value]=\"action.value | o3rFallbackTo\"\n [type]=\"'state'\"></o3r-rule-key-value-pres>\n </div>\n </div>\n }\n @case ('UPDATE_PLACEHOLDER') {\n <div [class.error]=\"!action.placeholderId\">\n <div>Update placeholder in {{action.component}} {{action.library}}</div>\n <div>\n <o3r-rule-key-value-pres\n [oldValue]=\"action.placeholderId | o3rFallbackTo: 'Missing \\'placeholderId\\''\"\n [value]=\"action.value\"\n [type]=\"'state'\"></o3r-rule-key-value-pres>\n </div>\n </div>\n }\n @default {\n <div class=\"error\">\n <div>Unrecognized action</div>\n <div>{{action | json}}</div>\n </div>\n }\n }\n }\n </li>\n }\n @for (runtimeOutput of runtimeOutputs; track $index) {\n <li>\n <div>Set temporary fact</div>\n <div>\n <o3r-rule-key-value-pres\n [key]=\"runtimeOutput | o3rFallbackTo: 'Missing \\'fact\\''\"\n [value]=\"temporaryFacts[runtimeOutput] | o3rFallbackTo\"\n [type]=\"'assignment'\"></o3r-rule-key-value-pres>\n </div>\n </li>\n }\n </ul>\n}\n", styles: ["o3r-rule-actions-pres .ruleset-panel-title,o3r-rule-actions-pres .ruleset-panel-category-title{display:flex;justify-content:space-between;align-items:center}o3r-rule-actions-pres .ruleset-panel-title{font-size:1rem;padding:.5rem 0 .1rem}o3r-rule-actions-pres .ruleset-expansion-action,o3r-rule-actions-pres .icon-caret-down,o3r-rule-actions-pres .icon-caret-up{cursor:pointer}o3r-rule-actions-pres .ruleset-panel-subtitle{font-size:.75rem}o3r-rule-actions-pres .ruleset-panel-title-aside{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;min-width:fit-content}o3r-rule-actions-pres .ruleset-panel-category-title{font-size:.95rem;background:#eee;padding:.5rem;margin-bottom:.5rem;margin-top:1rem}o3r-rule-actions-pres .rule-description .ruleset-panel-category-title{font-size:.893rem;cursor:default}o3r-rule-actions-pres .rule-description .ruleset-panel-title{font-size:.94rem}o3r-rule-actions-pres .rule-description .ruleset-panel-category-body{padding-bottom:.5rem;padding-left:1.5rem}o3r-rule-actions-pres .rule-description .ruleset-panel-category-body:empty{margin:0;padding:0 0 0 1.5rem}\n"] }]
}], propDecorators: { actions: [{
type: Input
}], temporaryFacts: [{
type: Input
}], runtimeOutputs: [{
type: Input
}] } });
class RuleConditionPresComponent {
constructor() {
/**
* Left hand operator as it will be displayed in the template.
* In the case of a fact with a json path, will resolve the whole fact path, else will only display the value
*/
this.lhs = 'undefined';
}
/**
* Rule condition that will be flattened by the component setter
*/
set condition(condition) {
this._condition = condition;
this.lhs = condition?.lhs ? this.getOperandName(condition.lhs) : 'undefined';
this.rhs = condition?.rhs ? this.getOperandName(condition.rhs) : undefined;
}
get condition() {
return this._condition;
}
getOperandName(operand) {
const value = `${operand.value ?? 'MISSING_VALUE'}`;
return operand.path ? operand.path.replace(/^\$/, value) : value;
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RuleConditionPresComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
/** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: RuleConditionPresComponent, isStandalone: true, selector: "o3r-rule-condition-pres", inputs: { condition: "condition" }, ngImport: i0, template: "<ng-container *ngIf=\"!condition; else displayConditions\">\n <span class=\"input-value\">true</span>\n</ng-container>\n<ng-template #displayConditions>\n <ng-container *ngIf=\"!$any(condition).all && !$any(condition).any && !$any(condition).not\">\n <span class=\"input-key\">{{ lhs }}</span> {{ $any(condition).operator }} <span class=\"input-value\" *ngIf=\"rhs !== undefined\">{{ rhs }}</span>\n </ng-container>\n <ng-container *ngIf=\"$any(condition).all || $any(condition).any || $any(condition).not\">\n <span *ngIf=\"$any(condition).all\">ALL</span>\n <span *ngIf=\"$any(condition).any\">ANY</span>\n <span *ngIf=\"$any(condition).not\">NOT</span>\n <span>(\n <ng-container *ngFor=\"let cond of $any(condition).all || $any(condition).any || [$any(condition).not]; let last = last;\">\n <o3r-rule-condition-pres [condition]=\"cond\"></o3r-rule-condition-pres>\n <span *ngIf=\"!last\">, </span>\n </ng-container>\n )</span>\n </ng-container>\n</ng-template>\n", styles: ["o3r-rule-condition-pres{word-break:break-word}\n"], dependencies: [{ kind: "component", type: RuleConditionPresComponent, selector: "o3r-rule-condition-pres", inputs: ["condition"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RuleConditionPresComponent, decorators: [{
type: Component,
args: [{ selector: 'o3r-rule-condition-pres', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [CommonModule], template: "<ng-container *ngIf=\"!condition; else displayConditions\">\n <span class=\"input-value\">true</span>\n</ng-container>\n<ng-template #displayConditions>\n <ng-container *ngIf=\"!$any(condition).all && !$any(condition).any && !$any(condition).not\">\n <span class=\"input-key\">{{ lhs }}</span> {{ $any(condition).operator }} <span class=\"input-value\" *ngIf=\"rhs !== undefined\">{{ rhs }}</span>\n </ng-container>\n <ng-container *ngIf=\"$any(condition).all || $any(condition).any || $any(condition).not\">\n <span *ngIf=\"$any(condition).all\">ALL</span>\n <span *ngIf=\"$any(condition).any\">ANY</span>\n <span *ngIf=\"$any(condition).not\">NOT</span>\n <span>(\n <ng-container *ngFor=\"let cond of $any(condition).all || $any(condition).any || [$any(condition).not]; let last = last;\">\n <o3r-rule-condition-pres [condition]=\"cond\"></o3r-rule-condition-pres>\n <span *ngIf=\"!last\">, </span>\n </ng-container>\n )</span>\n </ng-container>\n</ng-template>\n", styles: ["o3r-rule-condition-pres{word-break:break-word}\n"] }]
}], propDecorators: { condition: [{
type: Input
}] } });
class RuleTreePresComponent {
constructor() {
/**
* Type of the block being resolved.
* A type "IF_ELSE" will display two branches and the success and failure outputs associated
* Else, only the successElements will be shown
*/
this.blockType = '';
/**
* If case output
*/
this.successElements = [];
/**
* Else case output
*/
this.failureElements = [];
/**
* Should the "Else case scenario" actions be displayed
*/
this.failureActionsExpanded = false;
/**
* Should the "If case scenario" actions be displayed
*/
this.successActionsExpanded = false;
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RuleTreePresComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
/** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: RuleTreePresComponent, isStandalone: true, selector: "o3r-rule-tree-pres", inputs: { name: "name", blockType: "blockType", condition: "condition", successElements: "successElements", failureElements: "failureElements" }, ngImport: i0, template: "<span *ngIf=\"name\">{{name | titlecase}}:</span>\n<div class=\"rule-wrapper tree\">\n <ng-container *ngIf=\"blockType === 'IF_ELSE'; else noCondition\">\n <div class=\"tree-root\" *ngIf=\"!name\" [attr.aria-hidden]=\"true\">\n <div></div>\n <div></div>\n </div>\n <div class=\"rule-conditions\">\n <div class=\"rule-conditions-title\">If\n <o3r-rule-condition-pres [condition]=\"condition\"></o3r-rule-condition-pres>\n </div>\n <div class=\"tree-root\" [attr.aria-hidden]=\"true\">\n <div></div>\n <div></div>\n </div>\n </div>\n <div class=\"rule-actions-wrapper tree-node\">\n <div class=\"rule-actions tree-branch\">\n <div class=\"tree-leaf\" [attr.aria-hidden]=\"true\">\n <div></div>\n <div></div>\n </div>\n <div class=\"rule-action-title success-actions\"\n tabindex=\"0\"\n (click)=\"successActionsExpanded = !successActionsExpanded\"\n (keyup.enter)=\"successActionsExpanded = !successActionsExpanded\">\n <i class=\"icon refx-icon-validate\"></i>\n <span>Then</span>\n <i class=\"icon\"\n [class.icon-caret-down]=\"!successActionsExpanded\"\n [class.icon-caret-up]=\"successActionsExpanded\">\n </i>\n </div>\n <o3r-rule-actions-pres class=\"rule-tree-actions\" *ngIf=\"successActionsExpanded\"\n [actions]=\"successElements\">\n </o3r-rule-actions-pres>\n <ng-container [ngTemplateOutlet]=\"subTree\" [ngTemplateOutletContext]=\"{blocks: successElements}\"></ng-container>\n </div>\n <div class=\"rule-actions tree-branch\">\n <div class=\"tree-leaf\" [attr.aria-hidden]=\"true\">\n <div></div>\n <div></div>\n </div>\n <div class=\"rule-action-title error-actions\"\n tabindex=\"0\"\n (click)=\"failureActionsExpanded = !failureActionsExpanded\"\n (keyup.enter)=\"failureActionsExpanded = !failureActionsExpanded\">\n <i class=\"icon refx-icon-cross\"></i>\n <span>Else</span>\n <i class=\"icon\"\n [class.icon-caret-down]=\"!failureActionsExpanded\"\n [class.icon-caret-up]=\"failureActionsExpanded\">\n </i>\n </div>\n <o3r-rule-actions-pres class=\"rule-tree-actions\"\n *ngIf=\"failureActionsExpanded\"\n [actions]=\"failureElements\">\n </o3r-rule-actions-pres>\n <ng-container [ngTemplateOutlet]=\"subTree\" [ngTemplateOutletContext]=\"{blocks: failureElements}\"></ng-container>\n </div>\n </div>\n </ng-container>\n</div>\n<ng-template #noCondition>\n <div class=\"rule-conditions\">\n <div class=\"rule-conditions-title\">If <span class=\"input-value\">true</span></div>\n <div class=\"tree-root\" [attr.aria-hidden]=\"true\">\n <div></div>\n <div></div>\n </div>\n </div>\n <div class=\"rule-actions-wrapper\">\n <div class=\"rule-actions\">\n <div class=\"rule-action-title success-actions\" tabindex=\"0\"\n (keyup.enter)=\"successActionsExpanded = !successActionsExpanded\"\n (click)=\"successActionsExpanded = !successActionsExpanded\">\n <i class=\"icon refx-icon-validate\">\n </i>\n <span>Then</span>\n <i class=\"icon\"\n [class.icon-caret-down]=\"!successActionsExpanded\"\n [class.icon-caret-up]=\"successActionsExpanded\">\n </i>\n </div>\n <o3r-rule-actions-pres class=\"rule-tree-actions\"\n *ngIf=\"successActionsExpanded\"\n [actions]=\"successElements\">\n </o3r-rule-actions-pres>\n <ng-container [ngTemplateOutlet]=\"subTree\" [ngTemplateOutletContext]=\"{blocks: successElements}\"></ng-container>\n </div>\n </div>\n</ng-template>\n\n<ng-template #subTree let-blocks=\"blocks\">\n <div class=\"rule-sub-trees\">\n <ng-container *ngFor=\"let block of blocks\">\n <div *ngIf=\"block.blockType === 'IF_ELSE'\" class=\"tree-branch\">\n <o3r-rule-tree-pres\n [blockType]=\"'IF_ELSE'\"\n [condition]=\"block.condition\"\n [failureElements]=\"block.failureElements\"\n [successElements]=\"block.successElements\"></o3r-rule-tree-pres>\n </div>\n </ng-container>\n </div>\n</ng-template>\n", styles: ["o3r-rule-tree-pres{display:block;padding-bottom:1rem}o3r-rule-tree-pres .rule-sub-trees{display:flex}o3r-rule-tree-pres .rule-conditions-title,o3r-rule-tree-pres .rule-action-title{text-align:center}o3r-rule-tree-pres .rule-conditions-title{border:1px solid #999999;background:#fff;color:#000;padding:.2rem}o3r-rule-tree-pres .rule-action-title{background:#fff;border-radius:0;border:1px solid #999999;cursor:pointer;display:flex;justify-content:center;align-items:center;gap:.5rem}o3r-rule-tree-pres .rule-tree-actions{display:block;padding:.5rem}o3r-rule-tree-pres .rule-actions:first-child>.rule-action-title{border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}o3r-rule-tree-pres .rule-actions:last-child>.rule-action-title{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}o3r-rule-tree-pres .rule-actions-wrapper{display:flex}o3r-rule-tree-pres .rule-actions-wrapper .rule-actions{flex:1 1 100%}o3r-rule-tree-pres .success-actions{border-color:#16aa32}o3r-rule-tree-pres .success-actions.rule-action-title{background:#16aa32;color:#fff}o3r-rule-tree-pres .error-actions{border-color:#c02020}o3r-rule-tree-pres .error-actions.rule-action-title{background:#c02020;color:#fff}o3r-rule-tree-pres .tree .tree-leaf,o3r-rule-tree-pres .tree .tree-root{display:flex;width:100%}o3r-rule-tree-pres .tree .tree-leaf>div,o3r-rule-tree-pres .tree .tree-root>div{height:1rem;width:50%}o3r-rule-tree-pres .tree .tree-root div:first-child{border-right:1px dashed}o3r-rule-tree-pres .tree .tree-root{margin-top:.2rem}o3r-rule-tree-pres .tree .tree-node>.tree-branch:last-child>.tree-leaf>div:first-child{border-right:1px dashed;border-top:1px dashed;border-top-right-radius:.2rem}o3r-rule-tree-pres .tree .tree-node>.tree-branch:first-child>.tree-leaf>div:last-child{border-left:1px dashed;border-top:1px dashed;border-top-left-radius:.2rem}o3r-rule-tree-pres .icon{cursor:pointer}\n"], dependencies: [{ kind: "component", type: RuleTreePresComponent, selector: "o3r-rule-tree-pres", inputs: ["name", "blockType", "condition", "successElements", "failureElements"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }, { kind: "component", type: RuleActionsPresComponent, selector: "o3r-rule-actions-pres", inputs: ["actions", "temporaryFacts", "runtimeOutputs"] }, { kind: "component", type: RuleConditionPresComponent, selector: "o3r-rule-condition-pres", inputs: ["condition"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RuleTreePresComponent, decorators: [{
type: Component,
args: [{ selector: 'o3r-rule-tree-pres', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [CommonModule, RuleActionsPresComponent, RuleConditionPresComponent], template: "<span *ngIf=\"name\">{{name | titlecase}}:</span>\n<div class=\"rule-wrapper tree\">\n <ng-container *ngIf=\"blockType === 'IF_ELSE'; else noCondition\">\n <div class=\"tree-root\" *ngIf=\"!name\" [attr.aria-hidden]=\"true\">\n <div></div>\n <div></div>\n </div>\n <div class=\"rule-conditions\">\n <div class=\"rule-conditions-title\">If\n <o3r-rule-condition-pres [condition]=\"condition\"></o3r-rule-condition-pres>\n </div>\n <div class=\"tree-root\" [attr.aria-hidden]=\"true\">\n <div></div>\n <div></div>\n </div>\n </div>\n <div class=\"rule-actions-wrapper tree-node\">\n <div class=\"rule-actions tree-branch\">\n <div class=\"tree-leaf\" [attr.aria-hidden]=\"true\">\n <div></div>\n <div></div>\n </div>\n <div class=\"rule-action-title success-actions\"\n tabindex=\"0\"\n (click)=\"successActionsExpanded = !successActionsExpanded\"\n (keyup.enter)=\"successActionsExpanded = !successActionsExpanded\">\n <i class=\"icon refx-icon-validate\"></i>\n <span>Then</span>\n <i class=\"icon\"\n [class.icon-caret-down]=\"!successActionsExpanded\"\n [class.icon-caret-up]=\"successActionsExpanded\">\n </i>\n </div>\n <o3r-rule-actions-pres class=\"rule-tree-actions\" *ngIf=\"successActionsExpanded\"\n [actions]=\"successElements\">\n </o3r-rule-actions-pres>\n <ng-container [ngTemplateOutlet]=\"subTree\" [ngTemplateOutletContext]=\"{blocks: successElements}\"></ng-container>\n </div>\n <div class=\"rule-actions tree-branch\">\n <div class=\"tree-leaf\" [attr.aria-hidden]=\"true\">\n <div></div>\n <div></div>\n </div>\n <div class=\"rule-action-title error-actions\"\n tabindex=\"0\"\n (click)=\"failureActionsExpanded = !failureActionsExpanded\"\n (keyup.enter)=\"failureActionsExpanded = !failureActionsExpanded\">\n <i class=\"icon refx-icon-cross\"></i>\n <span>Else</span>\n <i class=\"icon\"\n [class.icon-caret-down]=\"!failureActionsExpanded\"\n [class.icon-caret-up]=\"failureActionsExpanded\">\n </i>\n </div>\n <o3r-rule-actions-pres class=\"rule-tree-actions\"\n *ngIf=\"failureActionsExpanded\"\n [actions]=\"failureElements\">\n </o3r-rule-actions-pres>\n <ng-container [ngTemplateOutlet]=\"subTree\" [ngTemplateOutletContext]=\"{blocks: failureElements}\"></ng-container>\n </div>\n </div>\n </ng-container>\n</div>\n<ng-template #noCondition>\n <div class=\"rule-conditions\">\n <div class=\"rule-conditions-title\">If <span class=\"input-value\">true</span></div>\n <div class=\"tree-root\" [attr.aria-hidden]=\"true\">\n <div></div>\n <div></div>\n </div>\n </div>\n <div class=\"rule-actions-wrapper\">\n <div class=\"rule-actions\">\n <div class=\"rule-action-title success-actions\" tabindex=\"0\"\n (keyup.enter)=\"successActionsExpanded = !successActionsExpanded\"\n (click)=\"successActionsExpanded = !successActionsExpanded\">\n <i class=\"icon refx-icon-validate\">\n </i>\n <span>Then</span>\n <i class=\"icon\"\n [class.icon-caret-down]=\"!successActionsExpanded\"\n [class.icon-caret-up]=\"successActionsExpanded\">\n </i>\n </div>\n <o3r-rule-actions-pres class=\"rule-tree-actions\"\n *ngIf=\"successActionsExpanded\"\n [actions]=\"successElements\">\n </o3r-rule-actions-pres>\n <ng-container [ngTemplateOutlet]=\"subTree\" [ngTemplateOutletContext]=\"{blocks: successElements}\"></ng-container>\n </div>\n </div>\n</ng-template>\n\n<ng-template #subTree let-blocks=\"blocks\">\n <div class=\"rule-sub-trees\">\n <ng-container *ngFor=\"let block of blocks\">\n <div *ngIf=\"block.blockType === 'IF_ELSE'\" class=\"tree-branch\">\n <o3r-rule-tree-pres\n [blockType]=\"'IF_ELSE'\"\n [condition]=\"block.condition\"\n [failureElements]=\"block.failureElements\"\n [successElements]=\"block.successElements\"></o3r-rule-tree-pres>\n </div>\n </ng-container>\n </div>\n</ng-template>\n", styles: ["o3r-rule-tree-pres{display:block;padding-bottom:1rem}o3r-rule-tree-pres .rule-sub-trees{display:flex}o3r-rule-tree-pres .rule-conditions-title,o3r-rule-tree-pres .rule-action-title{text-align:center}o3r-rule-tree-pres .rule-conditions-title{border:1px solid #999999;background:#fff;color:#000;padding:.2rem}o3r-rule-tree-pres .rule-action-title{background:#fff;border-radius:0;border:1px solid #999999;cursor:pointer;display:flex;justify-content:center;align-items:center;gap:.5rem}o3r-rule-tree-pres .rule-tree-actions{display:block;padding:.5rem}o3r-rule-tree-pres .rule-actions:first-child>.rule-action-title{border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}o3r-rule-tree-pres .rule-actions:last-child>.rule-action-title{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}o3r-rule-tree-pres .rule-actions-wrapper{display:flex}o3r-rule-tree-pres .rule-actions-wrapper .rule-actions{flex:1 1 100%}o3r-rule-tree-pres .success-actions{border-color:#16aa32}o3r-rule-tree-pres .success-actions.rule-action-title{background:#16aa32;color:#fff}o3r-rule-tree-pres .error-actions{border-color:#c02020}o3r-rule-tree-pres .error-actions.rule-action-title{background:#c02020;color:#fff}o3r-rule-tree-pres .tree .tree-leaf,o3r-rule-tree-pres .tree .tree-root{display:flex;width:100%}o3r-rule-tree-pres .tree .tree-leaf>div,o3r-rule-tree-pres .tree .tree-root>div{height:1rem;width:50%}o3r-rule-tree-pres .tree .tree-root div:first-child{border-right:1px dashed}o3r-rule-tree-pres .tree .tree-root{margin-top:.2rem}o3r-rule-tree-pres .tree .tree-node>.tree-branch:last-child>.tree-leaf>div:first-child{border-right:1px dashed;border-top:1px dashed;border-top-right-radius:.2rem}o3r-rule-tree-pres .tree .tree-node>.tree-branch:first-child>.tree-leaf>div:last-child{border-left:1px dashed;border-top:1px dashed;border-top-left-radius:.2rem}o3r-rule-tree-pres .icon{cursor:pointer}\n"] }]
}], propDecorators: { name: [{
type: Input
}], blockType: [{
type: Input
}], condition: [{
type: Input
}], successElements: [{
type: Input
}], failureElements: [{
type: Input
}] } });
class RulesetHistoryPresComponent {
constructor(cd) {
this.cd = cd;
/**
* Reflects the state of each ruleset expanded elements.
* Each ruleset entry contains a list of subpanel that can be collapsed or expanded.
* Ruleset whole panel status is store the 'ruleset' entry.
* @example
* Expanded ruleset with rule overview collapsed:
* {'rulesetId': {'ruleset' : true, 'ruleOverview': false}}
* @note Collapsing a ruleset will not reset the subpanel expansion status
*/
this.expansionStatus = {};
this.rulesetExecutions = [];
this.executionDurationFormat = '1.3-3';
}
/**
* Toggle a ruleset subpanel
* @param ruleId
* @param subpanel element to collapse. 'ruleset' will toggle the whole panel but won't reset the subpanels states.
*/
toggleExpansion(ruleId, subpanel) {
if (this.expansionStatus[ruleId]) {
this.expansionStatus[ruleId][subpanel] = !this.expansionStatus[ruleId][subpanel];
}
else {
this.expansionStatus[ruleId] = { [subpanel]: true };
}
this.cd.detectChanges();
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesetHistoryPresComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
/** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: RulesetHistoryPresComponent, isStandalone: true, selector: "o3r-ruleset-history-pres", inputs: { rulesetExecutions: "rulesetExecutions", executionDurationFormat: "executionDurationFormat" }, ngImport: i0, template: "<section>\n <ng-template #noRulesEngine>\n <div class=\"alert alert-danger m-2\" role=\"alert\">\n The Rules Engine is not configured on this page.\n </div>\n </ng-template>\n <ul *ngIf=\"rulesetExecutions; else noRulesEngine\" class=\"rulesets\">\n <li *ngFor=\"let execution of rulesetExecutions\" class=\"ruleset\">\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- need to refactor the div to accordion from DF #1518 -->\n <div class=\"ruleset-panel-title ruleset-expansion-action\"\n [class.error]=\"execution.type === 'RulesetExecutionError'\"\n (click)=\"toggleExpansion(execution.executionId, 'ruleset')\">\n <div><span [title]=\"'This ruleset has been evaluated ' + execution.executionCounter + ' time(s)'\">{{execution.executionCounter}}</span> - {{execution.rulesetName | titlecase }}\n <div class=\"ruleset-panel-subtitle\" *ngIf=\"execution.rulesetInformation?.linkedComponents?.or\">\n <ng-container *ngFor=\"let lc of execution.rulesetInformation?.linkedComponents?.or; last as isLast\">\n <div>{{lc.name}} {{lc.library}} <span *ngIf=\"!isLast\"> OR </span></div>\n </ng-container>\n </div>\n <div class=\"ruleset-panel-subtitle\" *ngIf=\"execution.rulesetInformation?.validityRange as validityRange\">\n Date range: {{validityRange.from}} - {{validityRange.to}}\n </div>\n </div>\n <div class=\"ruleset-panel-title-aside\">\n <span class=\"error capsule\" *ngIf=\"execution.status === 'Error'\">Error</span>\n <span class=\"success capsule\" *ngIf=\"execution.status === 'Active'\">Applied</span>\n <span class=\"inactive capsule\" *ngIf=\"execution.status === 'Deactivated'\">Deactivated</span>\n <span class=\"warn capsule\" *ngIf=\"execution.status === 'NoEffect'\">No effect</span>\n <span class=\"time capsule\">\n <span>{{execution.timestamp | date: 'HH:mm:ss SSS'}}</span>\n <span>({{execution.duration | number: executionDurationFormat}}ms)</span>\n </span>\n <button\n class=\"icon\"\n [class.icon-caret-down]=\"!expansionStatus[execution.executionId]?.ruleset\"\n [class.icon-caret-up]=\"expansionStatus[execution.executionId]?.ruleset\">\n </button>\n </div>\n </div>\n <div class=\"ruleset-panel-description\" *ngIf=\"expansionStatus[execution.executionId]?.ruleset\">\n <ng-container [ngTemplateOutlet]=\"rules\"\n [ngTemplateOutletContext]=\"{\n rules: execution.rulesetInformation.rules,\n expansionID: execution.executionId\n }\"></ng-container>\n <ng-container [ngTemplateOutlet]=\"inputs\"\n [ngTemplateOutletContext]=\"{\n inputs: execution.inputFacts\n }\"></ng-container>\n <ng-container *ngIf=\"execution.type === 'RulesetExecutionError'; else success\">\n <div class=\"ruleset-panel-category-title\">Rules:</div>\n <ul class=\"ruleset-panel-category-body rule-description\">\n <li *ngFor=\"let ruleEvaluation of execution.rulesEvaluations; let index=index;\">\n <ng-container>\n <div class=\"ruleset-panel-title\" [class.error]=\"ruleEvaluation.error\">\n <span>{{ruleEvaluation.rule.name | titlecase}} </span>\n <span class=\"capsule error\" *ngIf=\"ruleEvaluation.error\">Error</span>\n </div>\n <div>\n <ng-container *ngIf=\"ruleEvaluation.error\">\n <span class=\"ruleset-panel-category-title\">Error:</span>\n <pre class=\"ruleset-panel-category-body error\">{{ruleEvaluation.error | o3rJsonOrString}}</pre>\n </ng-container>\n <ng-container [ngTemplateOutlet]=\"inputs\"\n [ngTemplateOutletContext]=\"{\n inputs: execution.inputFacts,\n runtimeInputs: execution.rulesetInformation?.rules[index]?.inputRuntimeFacts\n }\"></ng-container>\n <o3r-rule-actions-pres *ngIf=\"!ruleEvaluation.error\"\n [temporaryFacts]=\"ruleEvaluation.temporaryFacts\"\n [runtimeOutputs]=\"execution.rulesetInformation?.rules[index]?.outputRuntimeFacts\"\n ></o3r-rule-actions-pres>\n </div>\n </ng-container>\n </li>\n </ul>\n </ng-container>\n <ng-template #success>\n <o3r-rule-actions-pres [actions]=\"execution.outputActions\"></o3r-rule-actions-pres>\n <div class=\"ruleset-panel-category-title\">Executed Rules</div>\n <ul class=\"rule-description ruleset-panel-category-body\">\n <li *ngFor=\"let ruleEvaluation of execution.rulesEvaluations; let index=index;\">\n <div class=\"ruleset-panel-title\">\n <span>{{ruleEvaluation.rule.name | titlecase}}</span>\n <span class=\"capsule inactive\" *ngIf=\"ruleEvaluation.cached\">Cached</span>\n <span class=\"capsule\">({{ruleEvaluation.duration | number: executionDurationFormat}}ms)</span>\n </div>\n <div>\n <ng-container [ngTemplateOutlet]=\"triggers\"\n [ngTemplateOutletContext]=\"{triggers: (ruleEvaluation.triggers[ruleEvaluation.rule.id])}\"></ng-container>\n <o3r-rule-actions-pres\n [actions]=\"ruleEvaluation.outputActions\"\n [temporaryFacts]=\"ruleEvaluation.temporaryFacts\"\n [runtimeOutputs]=\"execution.rulesetInformation?.rules[index]?.outputRuntimeFacts\">\n </o3r-rule-actions-pres>\n </div>\n </li>\n </ul>\n </ng-template>\n </div>\n </li>\n </ul>\n</section>\n\n<ng-template let-triggers=\"triggers\" #triggers>\n <div class=\"ruleset-panel-category-title\">Basefacts Triggers</div>\n <ul class=\"ruleset-panel-category-body triggers\">\n <ng-container *ngFor=\"let trigger of (triggers | keyvalue)\">\n <li *ngIf=\"trigger.value?.factName\">\n <o3r-rule-key-value-pres\n [key]=\"trigger.value.factName\"\n [oldValue]=\"trigger.value.oldValue | o3rFallbackTo\"\n [value]=\"trigger.value.newValue | o3rFallbackTo\"\n [type]=\"'state'\"></o3r-rule-key-value-pres>\n </li>\n </ng-container>\n </ul>\n</ng-template>\n\n<ng-template let-rules=\"rules\" let-expansionID=\"expansionID\" #rules>\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- need to refactor the div to accordion from DF #1518 -->\n <div class=\"ruleset-panel-category-title ruleset-expansion-action\"\n (click)=\"toggleExpansion(expansionID, 'rulesOverview')\">\n <span>Rules Overview</span>\n <button class=\"icon\"\n [class.icon-caret-down]=\"!expansionStatus[expansionID]?.rulesOverview\"\n [class.icon-caret-up]=\"expansionStatus[expansionID]?.rulesOverview\">\n </button>\n </div>\n <ng-container *ngIf=\"expansionStatus[expansionID]?.rulesOverview\">\n <div *ngIf=\"rules?.length === 0\" class=\"ruleset-panel-category-body empty\">No rule</div>\n <ul class=\"ruleset-panel-category-body\" *ngIf=\"rules?.length > 0\">\n <li *ngFor=\"let rule of rules\">\n <o3r-rule-tree-pres [name]=\"rule.name\"\n [condition]=\"rule?.rootElement?.condition\"\n [blockType]=\"rule?.rootElement?.blockType\"\n [successElements]=\"rule?.rootElement?.successElements\"\n [failureElements]=\"rule?.rootElement?.failureElements\">\n </o3r-rule-tree-pres>\n </li>\n </ul>\n </ng-container>\n</ng-template>\n\n<ng-template let-inputs=\"inputs\" let-runtimeInputs=\"runtimeInputs\" #inputs>\n <div class=\"ruleset-panel-category-title\">Inputs snapshot</div>\n <div *ngIf=\"inputs?.length === 0\" class=\"ruleset-panel-category-body empty\">No inputs</div>\n <ul class=\"ruleset-panel-category-body\" *ngIf=\"inputs?.length > 0\">\n <li *ngFor=\"let input of inputs\">\n <o3r-rule-key-value-pres\n [key]=\"input.factName\"\n [value]=\"input.value | o3rFallbackTo\"\n [type]=\"'state'\"></o3r-rule-key-value-pres>\n </li>\n <li *ngFor=\"let input of runtimeInputs\">{{input}} (scope limited to ruleset)</li>\n </ul>\n</ng-template>\n", styles: ["o3r-ruleset-history-pres .ruleset-panel-title,o3r-ruleset-history-pres .ruleset-panel-category-title{display:flex;justify-content:space-between;align-items:center}o3r-ruleset-history-pres .ruleset-panel-title{font-size:1rem;padding:.5rem 0 .1rem}o3r-ruleset-history-pres .ruleset-expansion-action,o3r-ruleset-history-pres .icon-caret-down,o3r-ruleset-history-pres .icon-caret-up{cursor:pointer}o3r-ruleset-history-pres .ruleset-panel-subtitle{font-size:.75rem}o3r-ruleset-history-pres .ruleset-panel-title-aside{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;min-width:fit-content}o3r-ruleset-history-pres .ruleset-panel-category-title{font-size:.95rem;background:#eee;padding:.5rem;margin-bottom:.5rem;margin-top:1rem}o3r-ruleset-history-pres .rule-description .ruleset-panel-category-title{font-size:.893rem;cursor:default}o3r-ruleset-history-pres .rule-description .ruleset-panel-title{font-size:.94rem}o3r-ruleset-history-pres .rule-description .ruleset-panel-category-body{padding-bottom:.5rem;padding-left:1.5rem}o3r-ruleset-history-pres .rule-description .ruleset-panel-category-body:empty{margin:0;padding:0 0 0 1.5rem}o3r-ruleset-history-pres .rulesets{margin:0;padding:0;list-style:none}o3r-ruleset-history-pres .rulesets ul{margin:0;padding-left:2rem}o3r-ruleset-history-pres .rulesets li.ruleset:nth-child(odd){background:#fbfbfb}o3r-ruleset-history-pres .ruleset-panel-category-body li{list-style:disc}o3r-ruleset-history-pres li:empty{display:none}o3r-ruleset-history-pres .ruleset{border-bottom:1px solid #dddddd}o3r-ruleset-history-pres .ruleset .ruleset-panel-description{padding:0 1rem 0 2rem;margin-bottom:2em}o3r-ruleset-history-pres .ruleset:first-child{border-top:1px solid #dddddd}o3r-ruleset-history-pres .ruleset-panel-title.ruleset-expansion-action{padding:.5rem 1rem}o3r-ruleset-history-pres .empty{font-style:italic;padding-left:1.5rem}o3r-ruleset-history-pres .ruleset-panel-category-body:empty.triggers:after{content:\"No trigger for this rule\";display:block;font-style:italic;padding-bottom:.5rem}o3r-ruleset-history-pres .ruleset-panel-title-aside{padding-left:1rem}o3r-ruleset-history-pres .rule-description .capsule{font-size:.8em;padding:.1rem .5rem;margin:0 .5rem}o3r-ruleset-history-pres .icon{background:none;border:none;font-size:1em}o3r-ruleset-history-pres .capsule{padding:.3rem;margin:.5rem;font-size:.875em;min-width:6rem;text-align:center}o3r-ruleset-history-pres .capsule.time{display:flex;flex-direction:column}o3r-ruleset-history-pres .success{background-color:#16aa32;color:#fff}o3r-ruleset-history-pres .inactive{background-color:#aaa;color:#fff}o3r-ruleset-history-pres .error{color:#c02020}o3r-ruleset-history-pres .error .capsule.error{background-color:#c02020;color:#fff}o3r-ruleset-history-pres .warn{background-color:#b92;color:#fff}o3r-ruleset-history-pres .input-key{color:#26c}o3r-ruleset-history-pres .input-value{color:#c29}o3r-ruleset-history-pres .input-key,o3r-ruleset-history-pres .input-value{font-family:monospace}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1$1.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }, { kind: "pipe", type: i1$1.KeyValuePipe, name: "keyvalue" }, { kind: "pipe", type: O3rFallbackToPipe, name: "o3rFallbackTo" }, { kind: "pipe", type: O3rJsonOrStringPipe, name: "o3rJsonOrString" }, { kind: "component", type: RuleActionsPresComponent, selector: "o3r-rule-actions-pres", inputs: ["actions", "temporaryFacts", "runtimeOutputs"] }, { kind: "component", type: RuleKeyValuePresComponent, selector: "o3r-rule-key-value-pres", inputs: ["key", "value", "oldValue", "type"] }, { kind: "component", type: RuleTreePresComponent, selector: "o3r-rule-tree-pres", inputs: ["name", "blockType", "condition", "successElements", "failureElements"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesetHistoryPresComponent, decorators: [{
type: Component,
args: [{ selector: 'o3r-ruleset-history-pres', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [
CommonModule,
O3rFallbackToPipe,
O3rJsonOrStringPipe,
RuleActionsPresComponent,
RuleKeyValuePresComponent,
RuleTreePresComponent
], template: "<section>\n <ng-template #noRulesEngine>\n <div class=\"alert alert-danger m-2\" role=\"alert\">\n The Rules Engine is not configured on this page.\n </div>\n </ng-template>\n <ul *ngIf=\"rulesetExecutions; else noRulesEngine\" class=\"rulesets\">\n <li *ngFor=\"let execution of rulesetExecutions\" class=\"ruleset\">\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- need to refactor the div to accordion from DF #1518 -->\n <div class=\"ruleset-panel-title ruleset-expansion-action\"\n [class.error]=\"execution.type === 'RulesetExecutionError'\"\n (click)=\"toggleExpansion(execution.executionId, 'ruleset')\">\n <div><span [title]=\"'This ruleset has been evaluated ' + execution.executionCounter + ' time(s)'\">{{execution.executionCounter}}</span> - {{execution.rulesetName | titlecase }}\n <div class=\"ruleset-panel-subtitle\" *ngIf=\"execution.rulesetInformation?.linkedComponents?.or\">\n <ng-container *ngFor=\"let lc of execution.rulesetInformation?.linkedComponents?.or; last as isLast\">\n <div>{{lc.name}} {{lc.library}} <span *ngIf=\"!isLast\"> OR </span></div>\n </ng-container>\n </div>\n <div class=\"ruleset-panel-subtitle\" *ngIf=\"execution.rulesetInformation?.validityRange as validityRange\">\n Date range: {{validityRange.from}} - {{validityRange.to}}\n </div>\n </div>\n <div class=\"ruleset-panel-title-aside\">\n <span class=\"error capsule\" *ngIf=\"execution.status === 'Error'\">Error</span>\n <span class=\"success capsule\" *ngIf=\"execution.status === 'Active'\">Applied</span>\n <span class=\"inactive capsule\" *ngIf=\"execution.status === 'Deactivated'\">Deactivated</span>\n <span class=\"warn capsule\" *ngIf=\"execution.status === 'NoEffect'\">No effect</span>\n <span class=\"time capsule\">\n <span>{{execution.timestamp | date: 'HH:mm:ss SSS'}}</span>\n <span>({{execution.duration | number: executionDurationFormat}}ms)</span>\n </span>\n <button\n class=\"icon\"\n [class.icon-caret-down]=\"!expansionStatus[execution.executionId]?.ruleset\"\n [class.icon-caret-up]=\"expansionStatus[execution.executionId]?.ruleset\">\n </button>\n </div>\n </div>\n <div class=\"ruleset-panel-description\" *ngIf=\"expansionStatus[execution.executionId]?.ruleset\">\n <ng-container [ngTemplateOutlet]=\"rules\"\n [ngTemplateOutletContext]=\"{\n rules: execution.rulesetInformation.rules,\n expansionID: execution.executionId\n }\"></ng-container>\n <ng-container [ngTemplateOutlet]=\"inputs\"\n [ngTemplateOutletContext]=\"{\n inputs: execution.inputFacts\n }\"></ng-container>\n <ng-container *ngIf=\"execution.type === 'RulesetExecutionError'; else success\">\n <div class=\"ruleset-panel-category-title\">Rules:</div>\n <ul class=\"ruleset-panel-category-body rule-description\">\n <li *ngFor=\"let ruleEvaluation of execution.rulesEvaluations; let index=index;\">\n <ng-container>\n <div class=\"ruleset-panel-title\" [class.error]=\"ruleEvaluation.error\">\n <span>{{ruleEvaluation.rule.name | titlecase}} </span>\n <span class=\"capsule error\" *ngIf=\"ruleEvaluation.error\">Error</span>\n </div>\n <div>\n <ng-container *ngIf=\"ruleEvaluation.error\">\n <span class=\"ruleset-panel-category-title\">Error:</span>\n <pre class=\"ruleset-panel-category-body error\">{{ruleEvaluation.error | o3rJsonOrString}}</pre>\n </ng-container>\n <ng-container [ngTemplateOutlet]=\"inputs\"\n [ngTemplateOutletContext]=\"{\n inputs: execution.inputFacts,\n runtimeInputs: execution.rulesetInformation?.rules[index]?.inputRuntimeFacts\n }\"></ng-container>\n <o3r-rule-actions-pres *ngIf=\"!ruleEvaluation.error\"\n [temporaryFacts]=\"ruleEvaluation.temporaryFacts\"\n [runtimeOutputs]=\"execution.rulesetInformation?.rules[index]?.outputRuntimeFacts\"\n ></o3r-rule-actions-pres>\n </div>\n </ng-container>\n </li>\n </ul>\n </ng-container>\n <ng-template #success>\n <o3r-rule-actions-pres [actions]=\"execution.outputActions\"></o3r-rule-actions-pres>\n <div class=\"ruleset-panel-category-title\">Executed Rules</div>\n <ul class=\"rule-description ruleset-panel-category-body\">\n <li *ngFor=\"let ruleEvaluation of execution.rulesEvaluations; let index=index;\">\n <div class=\"ruleset-panel-title\">\n <span>{{ruleEvaluation.rule.name | titlecase}}</span>\n <span class=\"capsule inactive\" *ngIf=\"ruleEvaluation.cached\">Cached</span>\n <span class=\"capsule\">({{ruleEvaluation.duration | number: executionDurationFormat}}ms)</span>\n </div>\n <div>\n <ng-container [ngTemplateOutlet]=\"triggers\"\n [ngTemplateOutletContext]=\"{triggers: (ruleEvaluation.triggers[ruleEvaluation.rule.id])}\"></ng-container>\n <o3r-rule-actions-pres\n [actions]=\"ruleEvaluation.outputActions\"\n [temporaryFacts]=\"ruleEvaluation.temporaryFacts\"\n [runtimeOutputs]=\"execution.rulesetInformation?.rules[index]?.outputRuntimeFacts\">\n </o3r-rule-actions-pres>\n </div>\n </li>\n </ul>\n </ng-template>\n </div>\n </li>\n </ul>\n</section>\n\n<ng-template let-triggers=\"triggers\" #triggers>\n <div class=\"ruleset-panel-category-title\">Basefacts Triggers</div>\n <ul class=\"ruleset-panel-category-body triggers\">\n <ng-container *ngFor=\"let trigger of (triggers | keyvalue)\">\n <li *ngIf=\"trigger.value?.factName\">\n <o3r-rule-key-value-pres\n [key]=\"trigger.value.factName\"\n [oldValue]=\"trigger.value.oldValue | o3rFallbackTo\"\n [value]=\"trigger.value.newValue | o3rFallbackTo\"\n [type]=\"'state'\"></o3r-rule-key-value-pres>\n </li>\n </ng-container>\n </ul>\n</ng-template>\n\n<ng-template let-rules=\"rules\" let-expansionID=\"expansionID\" #rules>\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- need to refactor the div to accordion from DF #1518 -->\n <div class=\"ruleset-panel-category-title ruleset-expansion-action\"\n (click)=\"toggleExpansion(expansionID, 'rulesOverview')\">\n <span>Rules Overview</span>\n <button class=\"icon\"\n [class.icon-caret-down]=\"!expansionStatus[expansionID]?.rulesOverview\"\n [class.icon-caret-up]=\"expansionStatus[expansionID]?.rulesOverview\">\n </button>\n </div>\n <ng-container *ngIf=\"expansionStatus[expansionID]?.rulesOverview\">\n <div *ngIf=\"rules?.length === 0\" class=\"ruleset-panel-category-body empty\">No rule</div>\n <ul class=\"ruleset-panel-category-body\" *ngIf=\"rules?.length > 0\">\n <li *ngFor=\"let rule of rules\">\n <o3r-rule-tree-pres [name]=\"rule.name\"\n [condition]=\"rule?.rootElement?.condition\"\n [blockType]=\"rule?.rootElement?.blockType\"\n [successElements]=\"rule?.rootElement?.successElements\"\n [failureElements]=\"rule?.rootElement?.failureElements\">\n </o3r-rule-tree-pres>\n </li>\n </ul>\n </ng-container>\n</ng-template>\n\n<ng-template let-inputs=\"inputs\" let-runtimeInputs=\"runtimeInputs\" #inputs>\n <div class=\"ruleset-panel-category-title\">Inputs snapshot</div>\n <div *ngIf=\"inputs?.length === 0\" class=\"ruleset-panel-category-body empty\">No inputs</div>\n <ul class=\"ruleset-panel-category-body\" *ngIf=\"inputs?.length > 0\">\n <li *ngFor=\"let input of inputs\">\n <o3r-rule-key-value-pres\n [key]=\"input.factName\"\n [value]=\"input.value | o3rFallbackTo\"\n [type]=\"'state'\"></o3r-rule-key-value-pres>\n </li>\n <li *ngFor=\"let input of runtimeInputs\">{{input}} (scope limited to ruleset)</li>\n </ul>\n</ng-template>\n", styles: ["o3r-ruleset-history-pres .ruleset-panel-title,o3r-ruleset-history-pres .ruleset-panel-category-title{display:flex;justify-content:space-between;align-items:center}o3r-ruleset-history-pres .ruleset-panel-title{font-size:1rem;padding:.5rem 0 .1rem}o3r-ruleset-history-pres .ruleset-expansion-action,o3r-ruleset-history-pres .icon-caret-down,o3r-ruleset-history-pres .icon-caret-up{cursor:pointer}o3r-ruleset-history-pres .ruleset-panel-subtitle{font-size:.75rem}o3r-ruleset-history-pres .ruleset-panel-title-aside{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;min-width:fit-content}o3r-ruleset-history-pres .ruleset-panel-category-title{font-size:.95rem;background:#eee;padding:.5rem;margin-bottom:.5rem;margin-top:1rem}o3r-ruleset-history-pres .rule-description .ruleset-panel-category-title{font-size:.893rem;cursor:default}o3r-ruleset-history-pres .rule-description .ruleset-panel-title{font-size:.94rem}o3r-ruleset-history-pres .rule-description .ruleset-panel-category-body{padding-bottom:.5rem;padding-left:1.5rem}o3r-ruleset-history-pres .rule-description .ruleset-panel-category-body:empty{margin:0;padding:0 0 0 1.5rem}o3r-ruleset-history-pres .rulesets{margin:0;padding:0;list-style:none}o3r-ruleset-history-pres .rulesets ul{margin:0;padding-left:2rem}o3r-ruleset-history-pres .rulesets li.ruleset:nth-child(odd){background:#fbfbfb}o3r-ruleset-history-pres .ruleset-panel-category-body li{list-style:disc}o3r-ruleset-history-pres li:empty{display:none}o3r-ruleset-history-pres .ruleset{border-bottom:1px solid #dddddd}o3r-ruleset-history-pres .ruleset .ruleset-panel-description{padding:0 1rem 0 2rem;margin-bottom:2em}o3r-ruleset-history-pres .ruleset:first-child{border-top:1px solid #dddddd}o3r-ruleset-history-pres .ruleset-panel-title.ruleset-expansion-action{padding:.5rem 1rem}o3r-ruleset-history-pres .empty{font-style:italic;padding-left:1.5rem}o3r-ruleset-history-pres .ruleset-panel-category-body:empty.triggers:after{content:\"No trigger for this rule\";display:block;font-style:italic;padding-bottom:.5rem}o3r-ruleset-history-pres .ruleset-panel-title-aside{padding-left:1rem}o3r-ruleset-history-pres .rule-description .capsule{font-size:.8em;padding:.1rem .5rem;margin:0 .5rem}o3r-ruleset-history-pres .icon{background:none;border:none;font-size:1em}o3r-ruleset-history-pres .capsule{padding:.3rem;margin:.5rem;font-size:.875em;min-width:6rem;text-align:center}o3r-ruleset-history-pres .capsule.time{display:flex;flex-direction:column}o3r-ruleset-history-pres .success{background-color:#16aa32;color:#fff}o3r-ruleset-history-pres .inactive{background-color:#aaa;color:#fff}o3r-ruleset-history-pres .error{color:#c02020}o3r-ruleset-history-pres .error .capsule.error{background-color:#c02020;color:#fff}o3r-ruleset-history-pres .warn{background-color:#b92;color:#fff}o3r-ruleset-history-pres .input-key{color:#26c}o3r-ruleset-history-pres .input-value{color:#c29}o3r-ruleset-history-pres .input-key,o3r-ruleset-history-pres .input-value{font-family:monospace}\n"] }]
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { rulesetExecutions: [{
type: Input
}], executionDurationFormat: [{
type: Input
}] } });
/**
* @deprecated The Components and Pipes are now standalone, this module will be removed in v14
*/
class RulesetHistoryPresModule {
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesetHistoryPresModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.13", ngImport: i0, type: RulesetHistoryPresModule, imports: [JsonPipe,
RulesetHistoryPresComponent,
RuleConditionPresComponent], exports: [RulesetHistoryPresComponent] }); }
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesetHistoryPresModule, imports: [RulesetHistoryPresComponent,
RuleConditionPresComponent] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesetHistoryPresModule, decorators: [{
type: NgModule,
args: [{
imports: [
JsonPipe,
RulesetHistoryPresComponent,
RuleConditionPresComponent
],
exports: [RulesetHistoryPresComponent]
}]
}] });
const isRulesEngineMessage = (message) => {
return message && (message.dataType === 'rulesEngineEvents'
|| message.dataType === 'requestMessages'
|| message.dataType === 'connect');
};
/** Determine if the action should be executed */
const RULES_ENGINE_OPTIONS = new InjectionToken('Rules Engine Options');
/** Default Rules engine options */
const DEFAULT_RULES_ENGINE_OPTIONS = {
dryRun: false,
debug: false
};
/** StateDetailsActions */
const ACTION_SET = '[Rulesets] set';
const ACTION_UPDATE = '[Rulesets] update';
const ACTION_RESET = '[Rulesets] reset';
const ACTION_CANCEL_REQUEST = '[Rulesets] cancel request';
/** Entity Actions */
const ACTION_CLEAR_ENTITIES = '[Rulesets] clear entities';
const ACTION_UPSERT_ENTITIES = '[Rulesets] upsert entities';
const ACTION_SET_ENTITIES = '[Rulesets] set entities';
const ACTION_FAIL_ENTITIES = '[Rulesets] fail entities';
/** Async Actions */
const ACTION_SET_ENTITIES_FROM_API = '[Rulesets] set entities from api';
const ACTION_UPSERT_ENTITIES_FROM_API = '[Rulesets] upsert entities from api';
/** Action to clear the StateDetails of the store and replace it */
const setRulesets = createAction(ACTION_SET, props());
/** Action to change a part or the whole object in the store. */
const updateRulesets = createAction(ACTION_UPDATE, props());
/** Action to reset the whole state, by returning it to initial state. */
const resetRulesets = createAction(ACTION_RESET);
/** Action to cancel a Request ID registered in the store. Can happen from effect based on a switchMap for instance */
const cancelRulesetsRequest = createAction(ACTION_CANCEL_REQUEST, props());
/** Action to clear all rulesets and fill the store with the payload */
const setRulesetsEntities = createAction(ACTION_SET_ENTITIES, props());
/** Action to update rulesets with known IDs, insert the new ones */
const upsertRulesetsEntities = createAction(ACTION_UPSERT_ENTITIES, props());
/** Action to empty the list of entities, keeping the global state */
const clearRulesetsEntities = createAction(ACTION_CLEAR_ENTITIES);
/** Action to update failureStatus for every RulesetsModel */
const failRulesetsEntities = createAction(ACTION_FAIL_ENTITIES, props());
/**
* Action to put the global status of the store in a pending state. Call SET action with the list of RulesetsModels received, when this action resolves.
* If the call fails, dispatch FAIL_ENTITIES action
*/
const setRulesetsEntitiesFromApi = createAction(ACTION_SET_ENTITIES_FROM_API, asyncProps());
/**
* Action to put global status of the store in a pending state. Call UPSERT action with the list of RulesetsModels received, when this action resolves.
* If the call fails, dispatch FAIL_ENTITIES action
*/
const upsertRulesetsEntitiesFromApi = createAction(ACTION_UPSERT_ENTITIES_FROM_API, asyncProps());
/**
* Service to handle async Rulesets actions
*/
class RulesetsEffect {
constructor(actions$) {
this.actions$ = actions$;
/**
* Set the entities with the reply content, dispatch failRulesetsEntities if it catches a failure
*/
this.setEntitiesFromApi$ = createEffect(() => this.actions$.pipe(ofType(setRulesetsEntitiesFromApi), fromApiEffectSwitchMap((reply, action) => setRulesetsEntities({ entities: reply, requestId: action.requestId }), (error, action) => of(failRulesetsEntities({ error, requestId: action.requestId })), cancelRulesetsRequest)));
/**
* Upsert the entities with the reply content, dispatch failRulesetsEntities if it catches a failure
*/
this.upsertEntitiesFromApi$ = createEffect(() => this.actions$.pipe(ofType(upsertRulesetsEntitiesFromApi), mergeMap((payload) => from(payload.call).pipe(map((reply) => upsertRulesetsEntities({ entities: reply, requestId: payload.requestId })), catchError((err) => of(failRulesetsEntities({ error: err, requestId: payload.requestId })))))));
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesetsEffect, deps: [{ token: i1$2.Actions }], target: i0.ɵɵFactoryTarget.Injectable }); }
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesetsEffect }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesetsEffect, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1$2.Actions }] });
/**
* Rulesets Store adapter
*/
const rulesetsAdapter = createEntityAdapter({
selectId: (model) => model.id
});
/**
* Rulesets Store initial value
*/
const rulesetsInitialState = rulesetsAdapter.getInitialState({
requestIds: []
});
/**
* List of basic actions for Rulesets Store
*/
const rulesetsReducerFeatures = [
on(resetRulesets, () => rulesetsInitialState),
on(setRulesets, (state, payload) => ({ ids: state.ids, entities: state.entities, ...payload.stateDetails })),
on(cancelRulesetsRequest, (state, action) => asyncStoreItemAdapter.resolveRequest(state, action.requestId)),
on(updateRulesets, (state, payload) => ({ ...state, ...payload.stateDetails })),
on(setRulesetsEntities, (state, payload) => rulesetsAdapter.addMany(payload.entities, rulesetsAdapter.removeAll(asyncStoreItemAdapter.resolveRequest(state, payload.requestId)))),
on(upsertRulesetsEntities, (state, payload) => rulesetsAdapter.upsertMany(payload.entities, asyncStoreItemAdapter.resolveRequest(state, payload.requestId))),
on(clearRulesetsEntities, (state) => rulesetsAdapter.removeAll(state)),
on(failRulesetsEntities, (state, payload) => asyncStoreItemAdapter.failRequest(state, payload.requestId)),
on(setRulesetsEntitiesFromApi, upsertRulesetsEntitiesFromApi, (state, payload) => asyncStoreItemAdapter.addRequest(state, payload.requestId))
];
/**
* Rulesets Store reducer
*/
const rulesetsReducer = createReducer(rulesetsInitialState, ...rulesetsReducerFeatures);
/**
* Name of the Rulesets Store
*/
const RULESETS_STORE_NAME = 'rulesets';
/** Token of the Rulesets reducer */
const RULESETS_REDUCER_TOKEN = new InjectionToken('Feature Rulesets Reducer');
/** Provide default reducer for Rulesets store */
function getDefaultRulesetsReducer() {
return rulesetsReducer;
}
class RulesetsStoreModule {
static forRoot(reducerFactory) {
return {
ngModule: RulesetsStoreModule,
providers: [
{ provide: RULESETS_REDUCER_TOKEN, useFactory: reducerFactory }
]
};
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesetsStoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.13", ngImport: i0, type: RulesetsStoreModule, imports: [i1$3.StoreFeatureModule, i1$2.EffectsFeatureModule] }); }
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesetsStoreModule, providers: [
{ provide: RULESETS_REDUCER_TOKEN, useFactory: getDefaultRulesetsReducer }
], imports: [StoreModule.forFeature(RULESETS_STORE_NAME, RULESETS_REDUCER_TOKEN), EffectsModule.forFeature([RulesetsEffect])] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesetsStoreModule, decorators: [{
type: NgModule,
args: [{
imports: [
StoreModule.forFeature(RULESETS_STORE_NAME, RULESETS_REDUCER_TOKEN), EffectsModule.forFeature([RulesetsEffect])
],
providers: [
{ provide: RULESETS_REDUCER_TOKEN, useFactory: getDefaultRulesetsReducer }
]
}]
}] });
const { selectIds, selectEntities, selectAll, selectTotal } = rulesetsAdapter.getSelectors();
/** Select Rulesets State */
const selectRulesetsState = createFeatureSelector(RULESETS_STORE_NAME);
/** Select the array of Rulesets ids */
const selectRulesetsIds = createSelector(selectRulesetsState, selectIds);
/** Select the array of Rulesets */
const selectAllRulesets = createSelector(selectRulesetsState, selectAll);
/** Select the dictionary of Rulesets entities */
const selectRulesetsEntities = createSelector(selectRulesetsState, selectEntities);
/** Select the total Rulesets count */
const selectRulesetsTotal = createSelector(selectRulesetsState, selectTotal);
/** Select the store pending status */
const selectRulesetsStorePendingStatus = createSelector(selectRulesetsState, (state) => state.isPending || false);
/**
* Check if the given value is a valid date
* @param d
*/
const isValidDate$1 = (d) => !Number.isNaN(d) && d instanceof Date;
/**
* Returns the rulesets which are in the validity range, if provided
*/
const selectRuleSetsInRange = createSelector(selectAllRulesets, (ruleSets) => ruleSets.filter((ruleSet) => {
const validity = ruleSet.validityRange;
if (!validity || (!validity.from && !validity.to)) {
return true;
}
const from = validity.from && new Date(validity.from);
const to = validity.to && new Date(validity.to);
if ((to && !isValidDate$1(to)) || (from && !isValidDate$1(from))) {
return false;
}
const time = Date.now();
if (to && from) {
return from.getTime() <= time && to.getTime() >= time;
}
if (from) {
return from.getTime() <= time;
}
return to && to.getTime() >= time;
}));
/**
* Returns the rulesets ids which are not onDemand and in the validity range
*/
const selectActiveRuleSets = createSelector(selectRuleSetsInRange, (ruleSets) => ruleSets
.filter((ruleSet) => (!ruleSet.linkedComponents?.or?.length))
.map((ruleSet) => ruleSet.id));
/**
* Assign component to RulesetIds Map
* @param compName
* @param library
* @param ruleSetId
* @param acc
*/
function linkComponentToRuleset(compName, library, ruleSetId, acc = {}) {
const configName = computeItemIdentifier(compName, library);
acc[ruleSetId] ||= [];
acc[ruleSetId].push(configName);
}
/**
* Select the map of ruleSets to activate based on linked components
*/
const selectComponentsLinkedToRuleset = createSelector(selectRuleSetsInRange, (ruleSets) => ruleSets
.reduce((acc, ruleSet) => {
if (!ruleSet.linkedComponents?.or || ruleSet.linkedComponents.or.length === 0) {
return acc;
}
if (ruleSet.linkedComponents?.or?.length) {
ruleSet.linkedComponents.or.forEach((linkComp) => {
linkComponentToRuleset(linkComp.name, linkComp.library, ruleSet.id, acc.or);
});
return acc;
}
return acc;
}, { or: {} }));
const rulesetsStorageSerializer = (state) => {
return asyncStoreItemAdapter.clearAsyncStoreItem(state);
};
const rulesetsStorageDeserializer = (rawObject) => {
if (!rawObject || !rawObject.ids) {
return rulesetsInitialState;
}
const storeObject = rulesetsAdapter.getInitialState(rawObject);
for (const id of rawObject.ids) {
storeObject.entities[id] = rawObject.entities[id];
}
return storeObject;
};
const rulesetsStorageSync = {
serialize: rulesetsStorageSerializer,
deserialize: rulesetsStorageDeserializer
};
/**
* Function to retrieve from 2 sequential executions only the triggers which activated the last ruleset execution
* @param currRes Current ruleset execution object
* @param prevRes Previous ruleset execution object
* @returns The triggers list which activates the last ruleset execution
*/
function retrieveRulesetTriggers(currRes, prevRes) {
let rulesetTriggers = {};
const allCurrRulesetTriggersList = currRes.map((r) => r.evaluation).filter((e) => !!e).map((e) => e.triggers);
const allCurrRulesetTriggers = {};
allCurrRulesetTriggersList.forEach((rTrig) => {
Object.keys(rTrig).forEach((ruleId) => {
allCurrRulesetTriggers[ruleId] = rTrig[ruleId];
});
});
if (prevRes) {
const allPrevRulesetTriggersList = prevRes.map((r) => r.evaluation).filter((e) => !!e).map((e) => e.triggers);
const allPrevRulesetTriggers = {};
allPrevRulesetTriggersList.forEach((rTrig) => {
Object.keys(rTrig).forEach((ruleId) => {
allPrevRulesetTriggers[ruleId] = rTrig[ruleId];
});
});
Object.entries(allCurrRulesetTriggers).forEach(([ruleId, ruleTriggers]) => {
Object.keys(ruleTriggers).forEach((factName) => {
if (!allPrevRulesetTriggers[ruleId]
|| !allPrevRulesetTriggers[ruleId][factName]
|| ruleTriggers[factName].newValue !== allPrevRulesetTriggers[ruleId][factName].newValue) {
(rulesetTriggers[ruleId] ||= {})[factName] = ruleTriggers[factName];
}
});
});
}
else {
rulesetTriggers = allCurrRulesetTriggers;
}
return rulesetTriggers;
}
/**
* Flag as cached the rules evaluations which are from previous ruleset executions
* @param rulesEvaluations all rules evaluations list
* @param triggers Ruleset triggers object
* @returns Rules evaluation list with flagged rules evaluation from previous ruleset executions
*/
function flagCachedRules(rulesEvaluations, triggers) {
const rulesWhichTriggeredExecution = Object.keys(triggers);
return rulesEvaluations.map((e) => {
if (e && !rulesWhichTriggeredExecution.includes(e.rule.id)) {
return { ...e, cached: true };
}
return { ...e };
});
}
/**
* Create the debug rule evaluation object
* @param rule
* @param rulesetName
* @param outputActions
* @param outputError
* @param runtimeFactValues
* @param factValues
* @param oldFactValues
* @param inputFacts
*/
function handleRuleEvaluationDebug(rule, rulesetName, outputActions, outputError, runtimeFactValues, factValues, oldFactValues, inputFacts = []) {
const executionId = `${rulesetName} - ${rule.name}`;
const reasons = {};
for (let index = 0; index < factValues.length; index++) {
if (!oldFactValues) {
(reasons[rule.id] ||= {})[inputFacts[index]] = { factName: inputFacts[index], newValue: factValues[index] };
}
else if (oldFactValues[index]?.toString() !== factValues[index]?.toString()) {
(reasons[rule.id] ||= {})[inputFacts[index]] = { factName: inputFacts[index], oldValue: oldFactValues[index], newValue: factValues[index] };
}
}
const ruleEvaluation = {
timestamp: Date.now(),
outputActions,
id: executionId,
triggers: reasons,
temporaryFacts: rule.outputRuntimeFacts.concat(rule.inputRuntimeFacts).reduce((acc, runtimeFactName) => {
if (typeof runtimeFactValues[runtimeFactName] !== 'undefined') {
acc[runtimeFactName] = runtimeFactValues[runtimeFactName];
}
return acc;
}, {}),
rule: { id: rule.id, name: rule.name },
...(outputError ? { error: outputError } : {})
};
return ruleEvaluation;
}
/**
* Rules engine debugger object to emit debug events
*/
class EngineDebugger {
/** Retrieved the rules engine plugged to the debugger */
get rulesEngine() {
return this.registeredRuleEngine;
}
/**
* Instantiate a rules engine debugger
* @param options Options to configure the debugger
*/
constructor(options) {
this.registeredRulesets = [];
this.requestFactsSnapshot = new Subject();
this.debugEventsSubject$ = new ReplaySubject(options?.eventsStackLimit);
this.initializePerformanceObserver();
this.debugEvents$ = new Observable((subscriber) => {
const factsSnapshotSubscription = this.requestFactsSnapshot.pipe(debounceTime(1000), switchMap(async () => {
const timestamp = Date.now();
const facts = await this.getFactsSnapshot(this.registeredRuleEngine.getRegisteredFactsNames());
this.debugEventsSubject$.next(() => ({
timestamp,
type: 'AvailableFactsSnapshot',
facts
}));
})).subscribe();
const debugEventsSubscription = this.debugEventsSubject$.pipe(withLatestFrom(this.performanceMeasures$), concatMap(async ([eventFunc, performanceMeasures]) => {
const debugEvent = await eventFunc();
if (debugEvent.type === 'RulesetExecution' || debugEvent.type === 'RulesetExecutionError') {
let rulesetDuration = 0;
debugEvent.rulesEvaluations.forEach((rule) => {
const mark = `rules-engine:${this.registeredRuleEngine?.rulesEngineInstanceName || ''}:${debugEvent.rulesetName}:${rule.rule.name}`;
const measures = performanceMeasures.filter((m) => m.name === mark);
const duration = measures.at(-1)?.duration || 0;
rule.duration = duration;
rulesetDuration += duration;
});
debugEvent.duration = rulesetDuration;
}
return debugEvent;
}), tap((debugEvent) => {
if (debugEvent.type === 'RulesetExecution') {
this.rulesEngine?.logger?.debug?.(`${debugEvent.rulesetName} has been triggered and resulted in ${JSON.stringify(debugEvent.outputActions)}`);
}
})).subscribe(subscriber);
return () => {
factsSnapshotSubscription.unsubscribe();
debugEventsSubscription.unsubscribe();
};
}).pipe(share());
}
initializePerformanceObserver() {
this.performanceMeasures$ = new Observable((subscriber) => {
const performanceObserver = new PerformanceObserver((list) => {
subscriber.next(list.getEntries());
});
performanceObserver.observe({ entryTypes: ['measure'] });
return () => performanceObserver.disconnect();
}).pipe(startWith([]), shareReplay(1));
}
async createBaseExecutionOutputObject(ruleset, executionCounter, rulesetInputFacts, runtimeFactValues, rulesetTriggers, rulesExecutions) {
const inputFacts = await this.getFactsSnapshot(rulesetInputFacts);
const baseRulesetOutputExecution = {
executionCounter,
executionId: `${ruleset.id}-${executionCounter}`,
rulesetId: ruleset.id,
rulesetName: ruleset.name,
inputFacts,
triggers: rulesetTriggers,
rulesEvaluations: flagCachedRules(rulesExecutions?.sort((a, b) => a.timestamp - b.timestamp) || [], rulesetTriggers),
temporaryFacts: runtimeFactValues
};
return baseRulesetOutputExecution;
}
async rulesetExecution(timestamp, ruleset, executionCounter, rulesetInputFacts, allOutputActions, runtimeFactValues, rulesetTriggers, rulesExecutions) {
const baseRulesetOutputExecution = await this.createBaseExecutionOutputObject(ruleset, executionCounter, rulesetInputFacts, runtimeFactValues, rulesetTriggers, rulesExecutions);
const rulesetOutputExecution = {
timestamp,
type: 'RulesetExecution',
outputActions: allOutputActions,
...baseRulesetOutputExecution
};
return rulesetOutputExecution;
}
async rulesetExecutionError(timestamp, ruleset, rulesetInputFacts, executionCounter, runtimeFactValues, rulesetTriggers, rulesExecutions) {
const baseRulesetOutputExecution = await this.createBaseExecutionOutputObject(ruleset, executionCounter, rulesetInputFacts, runtimeFactValues, rulesetTriggers, rulesExecutions);
const rulesExecWithErrors = rulesExecutions.filter((ex) => !!ex && !!ex.error);
const rulesetOutputExecutionSkip = {
timestamp,
type: 'RulesetExecutionError',
rulesCausingTheError: rulesExecWithErrors.map((e) => e.rule) || [],
errors: rulesExecWithErrors.map((e) => e.error),
...baseRulesetOutputExecution
};
return rulesetOutputExecutionSkip;
}
/**
* Plug the debugger to a Rule Engine
* @param rulesEngine
*/
registerRuleEngine(rulesEngine) {
this.registeredRuleEngine = rulesEngine;
}
/**
* Handle ruleset execution debug info
* @param currRes
* @param prevRes
* @param allExecutionsValid
* @param rulesetInputFacts
* @param runtimeFactValues
* @param executionCounter
* @param ruleset
*/
handleDebugRulesetExecutionInfo(currRes, prevRes, allExecutionsValid, rulesetInputFacts, runtimeFactValues, executionCounter, ruleset) {
const rulesetTriggers = retrieveRulesetTriggers(currRes, prevRes);
const rulesetOutputExecution = currRes.map((r) => r.evaluation);
if (!allExecutionsValid) {
this.addRulesetExecutionErrorEvent(ruleset, rulesetInputFacts, executionCounter, runtimeFactValues, rulesetTriggers, rulesetOutputExecution);
}
return {
executionCounter,
rulesetOutputExecution,
allExecutionsValid,
rulesetTriggers
};
}
/**
* Emits an 'AvailableRulesets' debug event when rulesets are registered to the rules engine
* @param rulesets
*/
addAvailableRulesets(rulesets) {
const timestamp = Date.now();
this.registeredRulesets = [...this.registeredRulesets, ...rulesets.map((r) => ({ name: r.name, id: r.id }))];
this.debugEventsSubject$.next(() => ({
timestamp,
type: 'AvailableRulesets',
availableRulesets: this.registeredRulesets
}));
}
/**
* Computes and emits an 'ActiveRulesets' debug event when the active rulesets are changing
* @param ruleSetExecutorMap map off all rulesets executors
* @param restrictiveRuleSets ids of the rulesets to activate; if not provided all registered rulesets will be considered as active
*/
activeRulesetsChange(ruleSetExecutorMap, restrictiveRuleSets) {
const timestamp = Date.now();
const rulesets = Object.keys(ruleSetExecutorMap).map((rulesetId) => ruleSetExecutorMap[rulesetId].engineRuleset);
const activeRulesets = restrictiveRuleSets
? Object.values(rulesets).filter((ruleSet) => restrictiveRuleSets.includes(ruleSet.id))
: Object.values(rulesets);
this.debugEventsSubject$.next(() => ({
timestamp,
type: 'ActiveRulesets',
rulesets: activeRulesets.map((a) => ({ name: ruleSetExecutorMap[a.id].ruleset.name, id: ruleSetExecutorMap[a.id].ruleset.id }))
}));
}
/**
* Emits an 'AllActions' debug event each time the rules engine outputs the list of actions
* @param actions list of outputted actions
*/
allActionsChange(actions) {
const timestamp = Date.now();
this.debugEventsSubject$.next(() => ({ timestamp, type: 'AllActions', actions }));
}
/**
* Emits a 'RulesetExecution' debug event at the output of a successful ruleset execution
* @param ruleset
* @param executionCounter
* @param rulesetInputFacts
* @param allOutputActions
* @param runtimeFactValues
* @param rulesetTriggers
* @param rulesExecutions
*/
addRulesetExecutionEvent(ruleset, executionCounter, rulesetInputFacts, allOutputActions, runtimeFactValues, rulesetTriggers, rulesExecutions) {
const timestamp = Date.now();
this.debugEventsSubject$.next(() => this.rulesetExecution(timestamp, ruleset, executionCounter, rulesetInputFacts, allOutputActions, runtimeFactValues, rulesetTriggers, rulesExecutions));
}
/**
* Emits a 'RulesetExecutionError' debug event at the output of a failing ruleset execution
* @param ruleset
* @param rulesetInputFacts
* @param executionCounter
* @param runtimeFactValues
* @param rulesetTriggers
* @param rulesExecutions
*/
addRulesetExecutionErrorEvent(ruleset, rulesetInputFacts, executionCounter, runtimeFactValues, rulesetTriggers, rulesExecutions) {
const timestamp = Date.now();
this.debugEventsSubject$.next(() => this.rulesetExecutionError(timestamp, ruleset, rulesetInputFacts, executionCounter, runtimeFactValues, rulesetTriggers, rulesExecutions));
}
/**
* Emits a 'AvailableFactsSnapshot' debug event when a fact value is updated
* @param _id
* @param factValue$
*/
addAvailableFactsSnapshotEvent(_id, factValue$) {
factValue$.subscribe(() => this.requestFactsSnapshot.next());
}
/**
* Returns a list of fact name and value pairs
* @param factsNames List of facts names to get the value for
*/
async getFactsSnapshot(factsNames) {
const facts = [];
if (!this.registeredRuleEngine) {
throw new Error('Rule engine not plugged to the debugger');
}
for (const factName of factsNames) {
facts.push({ factName, value: await this.registeredRuleEngine.retrieveFactValue(factName) });
}
return facts;
}
}
/**
* Filter the actions outputted by the rules engine, based on active rulesets
* @param restrictiveRuleSets list of rules sets to get the event stream for
*/
function filterRulesetsEventStream(restrictiveRuleSets) {
return (source$) => source$.pipe(switchMap((ruleSetExecutorMap) => {
const rulesets = Object.keys(ruleSetExecutorMap).map((rulesetId) => ruleSetExecutorMap[rulesetId].engineRuleset);
const activeRulesets = restrictiveRuleSets
? Object.values(rulesets).filter((ruleSet) => restrictiveRuleSets.includes(ruleSet.id))
: Object.values(rulesets);
return activeRulesets?.length > 0
? combineLatest(activeRulesets.map((ruleset) => ruleset.rulesResultsSubject$)).pipe(map((item) => item.reduce((acc, currentValue) => {
acc.push(...currentValue);
return acc;
}, [])))
: of([]);
}), shareReplay(1));
}
/**
* Execute Operator
* @param lhs Left hand side
* @param rhs Right hand side
* @param operator Operator to compare values
* @param operatorFacts Facts that operator can depend on
*/
function executeOperator(lhs, rhs, operator, operatorFacts) {
const validLhs = (!operator.validateLhs || operator.validateLhs(lhs));
const validRhs = (!operator.validateRhs || operator.validateRhs(rhs));
let operatorFactValues;
if (operatorFacts && operator.factImplicitDependencies) {
operatorFactValues = operator.factImplicitDependencies.reduce((acc, dep) => {
if (operatorFacts[dep]) {
acc[dep] = operatorFacts[dep];
}
else {
throw new Error(`The fact "${dep}" requested by ${operator.name} cannot be found.`);
}
return acc;
}, {});
}
if (!validLhs) {
throw new Error(`Invalid left operand: ${JSON.stringify(lhs)}`);
}
if (!validRhs) {
throw new Error(`Invalid right operand: ${JSON.stringify(rhs)}`);
}
const obs = operator.evaluator(lhs, rhs, operatorFactValues);
return obs;
}
/**
* Validate a number operand
* @param operand value of one of the operands
*/
function numberValidator(operand) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- needed to convert any variable in a number (or NaN)
return operand !== '' && !Array.isArray(operand) && !Number.isNaN(+`${operand}`);
}
/**
* Validate an operand is a range of numbers
* @param operatorInput value of one of the operands
*/
function isRangeNumber(operatorInput) {
return Array.isArray(operatorInput)
&& operatorInput.length === 2
&& numberValidator(operatorInput[0])
&& numberValidator(operatorInput[1])
&& operatorInput[0] <= operatorInput[1];
}
/**
* Verifies if the parameter is a valid date for the operator (getTime function available returning a number)
* @param operatorInput
*/
const isValidDate = (operatorInput) => {
if (!operatorInput || typeof operatorInput.getTime !== 'function') {
return false;
}
const getTimeResult = operatorInput.getTime();
return typeof getTimeResult === 'number' && !Number.isNaN(getTimeResult);
};
/**
* Verifies if the parameter is a valid input for Date constructor (new Date returns a valid date)
* @param operatorInput
*/
const isValidDateInput = (operatorInput) => {
return operatorInput === 0 || (!!operatorInput && isValidDate(new Date(operatorInput)));
};
/**
* Verifies if the parameter is a valid date range
* @param operatorInput
*/
const isValidDateRange = (operatorInput) => {
return Array.isArray(operatorInput)
&& operatorInput.length === 2
&& isValidDateInput(operatorInput[0])
&& isValidDateInput(operatorInput[1])
&& new Date(operatorInput[0]) <= new Date(operatorInput[1]);
};
/**
* Validate that a value is a supported simple type
* @param value value to validate
*/
function isSupportedSimpleTypes(value) {
return ['string', 'boolean', 'number', 'undefined'].includes(typeof value) || value === null || isValidDate(value);
}
/**
* Validate that a value is a string
* @param value
*/
function isString(value) {
return typeof value === 'string';
}
/**
* Parse input to return RegExp
* @param inputRegExp
*/
function parseRegExp(inputRegExp) {
if (inputRegExp.startsWith('/')) {
const finalSlash = inputRegExp.lastIndexOf('/');
const regexpPattern = inputRegExp.slice(1, finalSlash);
const regexpFlags = inputRegExp.slice(finalSlash + 1);
return new RegExp(regexpPattern, regexpFlags);
}
return new RegExp(inputRegExp);
}
/**
* Check if any of the variable's value is equal to a specific value
* @title contains
*/
const arrayContains = {
name: 'arrayContains',
evaluator: (value, b) => value.includes(b),
validateLhs: Array.isArray,
validateRhs: isSupportedSimpleTypes
};
/**
* Check if the specified text value is included in the text variable
* @title contains
*/
const stringContains = {
name: 'stringContains',
evaluator: (inputString, substring) => inputString.includes(substring),
validateLhs: isString,
validateRhs: isString
};
/**
* Check if every value of the variable is different from a specific value
* @title does not contain
*/
const notArrayContains = {
name: 'notArrayContains',
evaluator: (array, value) => !array.includes(value),
validateLhs: Array.isArray,
validateRhs: isSupportedSimpleTypes
};
/**
* Check if the specified text value is not included in the text variable
* @title does not contain
*/
const notStringContains = {
name: 'notStringContains',
evaluator: (inputString, substring) => !inputString.includes(substring),
validateLhs: isString,
validateRhs: isString
};
/**
* Check if every value of the variable equals a specific value
* @title all equal to
*/
const allEqual = {
name: 'allEqual',
// eslint-disable-next-line eqeqeq -- possibility of comparing string and number values
evaluator: (array, value) => array.every((elementValue) => elementValue == value),
validateLhs: Array.isArray,
validateRhs: isSupportedSimpleTypes
};
/**
* Check if every numerical value of the variable is greater than a specific value
* @title all >
*/
const allGreater = {
name: 'allGreater',
evaluator: (array, value) => array.every((elementValue) => numberValidator(elementValue) && +elementValue > +value),
validateLhs: Array.isArray,
validateRhs: numberValidator
};
/**
* Check if every value of the variable is in a specific list
* @title all in
*/
const allIn = {
name: 'allIn',
evaluator: (array, value) => array.every((elementValue) => value.includes(elementValue)),
validateLhs: Array.isArray,
validateRhs: Array.isArray
};
/**
* Check if every value of the variable is not in a specific list
* @title none in
*/
const allNotIn = {
name: 'allNotIn',
evaluator: (array, value) => !array.some((elementValue) => value.includes(elementValue)),
validateLhs: Array.isArray,
validateRhs: Array.isArray
};
/**
* Check if every numerical value of the variable is lower than a specific value
* @title all <
*/
const allLower = {
name: 'allLower',
evaluator: (arrayNumber, number) => arrayNumber.every((elementNumber) => elementNumber < +number),
validateLhs: Array.isArray,
validateRhs: numberValidator
};
/**
* Check if every string value of the variable matches a specific pattern
* @title all match
*/
const allMatch = {
name: 'allMatch',
evaluator: (array, inputRegExp) => {
const regExp = parseRegExp(inputRegExp);
return array.every((elementValue) => regExp.test(elementValue));
},
validateLhs: Array.isArray,
validateRhs: isString
};
/**
* Check if every value of the variable is included in a specified range
* @title all between
*/
const allRangeNumber = {
name: 'allRangeNumber',
evaluator: (rangeArray, [from, to]) => rangeArray.every((elementValue) => elementValue >= +from && elementValue <= +to),
validateLhs: Array.isArray,
validateRhs: isRangeNumber
};
/**
* Check if at least one of the values of the variable equals a specific value
* @title one equal to
*/
const oneEquals = {
name: 'oneEquals',
// eslint-disable-next-line eqeqeq -- possibility of comparing string and number values
evaluator: (array, value) => array.some((elementValue) => elementValue == value),
validateLhs: Array.isArray,
validateRhs: isSupportedSimpleTypes
};
/**
* Check if one of the values of the variable is greater than a specific value
* @title one >
*/
const oneGreater = {
name: 'oneGreater',
evaluator: (arrayNumber, number) => arrayNumber.some((elementValue) => elementValue > +number),
validateLhs: Array.isArray,
validateRhs: numberValidator
};
/**
* Check if at least one of the values of the variable is equal to one in a specified list
* @title one in
*/
const oneIn = {
name: 'oneIn',
evaluator: (firstArray, secondArray) => firstArray.some((elementValue) => secondArray.includes(elementValue)),
validateLhs: Array.isArray,
validateRhs: Array.isArray
};
/**
* Check if one of the values of the variable is lower than a specific value
* @title one <
*/
const oneLower = {
name: 'oneLower',
evaluator: (arrayNumber, number) => arrayNumber.some((elementValue) => elementValue < +number),
validateLhs: Array.isArray,
validateRhs: numberValidator
};
/**
* Check if one of the values of the variable matches a specific pattern
* @title one matches
*/
const oneMatches = {
name: 'oneMatches',
evaluator: (arrayString, inputRegExp) => {
const regExp = parseRegExp(inputRegExp);
return arrayString.some((elementValue) => regExp.test(elementValue));
},
validateLhs: Array.isArray,
validateRhs: isString
};
/**
* Check if one of the values of the variable is included in a specified range
* @title one between
*/
const oneRangeNumber = {
name: 'oneRangeNumber',
evaluator: (arrayNumber, [from, to]) => arrayNumber.some((elementValue) => elementValue >= +from && elementValue <= +to),
validateLhs: Array.isArray,
validateRhs: isRangeNumber
};
/**
* Check if the number of values of the variable is equal to a specific value
* @title number of =
*/
const lengthEquals = {
name: 'lengthEquals',
evaluator: (array, length) => array.length === Number(length),
validateLhs: Array.isArray,
validateRhs: numberValidator
};
/**
* Check if the number of values of the variable is different from a specific value
* @title number of ≠
*/
const lengthNotEquals = {
name: 'lengthNotEquals',
evaluator: (array, length) => array.length !== Number(length),
validateLhs: Array.isArray,
validateRhs: numberValidator
};
/**
* Check if the number of values of the variable is lower or equal to a specific value
* @title number of ≤
*/
const lengthLessThanOrEquals = {
name: 'lengthLessThanOrEquals',
evaluator: (array, length) => array.length <= +length,
validateLhs: Array.isArray,
validateRhs: numberValidator
};
/**
* Check if the number of values of the variable is lower than a specific value
* @title number of <
*/
const lengthLessThan = {
name: 'lengthLessThan',
evaluator: (array, length) => array.length < +length,
validateLhs: Array.isArray,
validateRhs: numberValidator
};
/**
* Check if the number of values of the variable is greater or equal to a specific value
* @title number of ≥
*/
const lengthGreaterThanOrEquals = {
name: 'lengthGreaterThanOrEquals',
evaluator: (array, length) => array.length >= +length,
validateLhs: Array.isArray,
validateRhs: numberValidator
};
/**
* Check if the number of values of the variable is greater than a specific value
* @title number of >
*/
const lengthGreaterThan = {
name: 'lengthGreaterThan',
evaluator: (array, length) => array.length > +length,
validateLhs: Array.isArray,
validateRhs: numberValidator
};
/** List of all default array operators */
const arrayBasedOperators = [
allEqual,
allGreater,
allIn,
allLower,
allMatch,
allNotIn,
allRangeNumber,
arrayContains,
lengthEquals,
lengthNotEquals,
lengthGreaterThan,
lengthGreaterThanOrEquals,
lengthLessThan,
lengthLessThanOrEquals,
notArrayContains,
notStringContains,
oneEquals,
oneGreater,
oneIn,
oneLower,
oneMatches,
oneRangeNumber,
stringContains
];
/**
* Check if a variable is equal to a specific value
* @title is equal to
*/
const equals = {
name: 'equals',
// eslint-disable-next-line eqeqeq -- possibility of comparing string and number values
evaluator: (firstValue, secondValue) => firstValue == secondValue
};
/**
* Check if a variable is different from a specific value
* @title is not equal to
*/
const notEquals = {
name: 'notEquals',
// eslint-disable-next-line eqeqeq -- possibility of comparing string and number values
evaluator: (firstValue, secondValue) => firstValue != secondValue
};
/**
* Check if the variable's value is included in a specified list
* @title is in
*/
const inArray = {
name: 'inArray',
evaluator: (value, array) => array.includes(value),
validateLhs: isSupportedSimpleTypes,
validateRhs: Array.isArray
};
/**
* Check if the variable's value is not included in the value list
* @title is not in
*/
const notInArray = {
name: 'notInArray',
evaluator: (value, array) => !array.includes(value),
validateLhs: isSupportedSimpleTypes,
validateRhs: Array.isArray
};
/**
* Check if the text variable is part of the specified value
* @title within
*/
const inString = {
name: 'inString',
evaluator: (value, inputString) => inputString.includes(value),
validateLhs: isString,
validateRhs: isString
};
/**
* Check if the text variable is not part of the specified value
* @title not within
*/
const notInString = {
name: 'notInString',
evaluator: (value, inputString) => !inputString.includes(value),
validateLhs: isString,
validateRhs: isString
};
/**
* Check if the variable and its value are defined
* @title is defined
*/
const isDefined = {
name: 'isDefined',
evaluator: (input) => input !== undefined && input !== null
};
/**
* Check if the variable and its value are undefined
* @title is not defined
*/
const isUndefined = {
name: 'isUndefined',
evaluator: (input) => input === undefined || input === null
};
/**
* Check if the text variable matches the specified RegExp pattern
* @title matches the pattern
*/
const matchesPattern = {
name: 'matchesPattern',
evaluator: (value, inputRegExp) => {
const regExp = parseRegExp(inputRegExp);
return regExp.test(value);
},
validateLhs: isString,
validateRhs: isString
};
/** List of all default basic operators */
const basicOperators = [
equals, inArray, inString, isDefined, isUndefined, matchesPattern, notEquals, notInArray, notInString
];
/**
* Check if a date variable is in a specified date range
* @title is between
*/
const inRangeDate = {
name: 'inRangeDate',
evaluator: (date, [from, to]) => {
const dateObject = new Date(date);
return new Date(from) <= dateObject && new Date(to) >= dateObject;
},
validateLhs: isValidDateInput,
validateRhs: isValidDateRange
};
/**
* Check if the value of the variable is in the next x minutes
* @title is in next minutes
* @returns false for dates before `now` and for dates after `now` + `nextMinutes`, true for dates between `now` and `now` + `nextMinutes`
*/
const dateInNextMinutes = {
name: 'dateInNextMinutes',
evaluator: (leftDateInput, minutes, operatorFactValues) => {
if (!operatorFactValues) {
throw new Error('No operatorFactValues. Unable to retrieve the current time.');
}
if (typeof operatorFactValues.o3rCurrentTime !== 'number') {
throw new Error('o3rCurrentTime value is not a number');
}
const currentTimeValue = operatorFactValues.o3rCurrentTime;
return inRangeDate.evaluator(leftDateInput, [currentTimeValue, currentTimeValue + +minutes * 60_000]);
},
factImplicitDependencies: ['o3rCurrentTime'],
validateLhs: isValidDateInput,
validateRhs: numberValidator
};
/**
* Check if the value of the variable is not in the next x minutes
* @title is not in next minutes
* @returns false for dates before `now` and for dates between `now` and `now` + `nextMinutes`, true for dates after `now` + `nextMinutes`
*/
const dateNotInNextMinutes = {
name: 'dateNotInNextMinutes',
evaluator: (leftDateInput, minutes, operatorFactValues) => {
if (!operatorFactValues) {
throw new Error('No operatorFactValues. Unable to retrieve the current time.');
}
if (typeof operatorFactValues.o3rCurrentTime !== 'number') {
throw new Error('o3rCurrentTime value is not a number');
}
const currentTimeValue = operatorFactValues.o3rCurrentTime;
const now = new Date(currentTimeValue);
const leftDate = new Date(leftDateInput);
const targetDate = new Date(new Date(currentTimeValue).setMinutes(now.getMinutes() + +minutes));
return leftDate >= now && leftDate > targetDate;
},
factImplicitDependencies: ['o3rCurrentTime'],
validateLhs: isValidDateInput,
validateRhs: numberValidator
};
/**
* Check if a date variable is prior than a specified date
* @title is before
*/
const dateBefore = {
name: 'dateBefore',
evaluator: (leftDate, rightDate) => {
const firstDateTime = new Date(leftDate).setHours(0, 0, 0, 0);
const secondDateTime = new Date(rightDate).setHours(0, 0, 0, 0);
return firstDateTime < secondDateTime;
},
validateLhs: isValidDateInput,
validateRhs: isValidDateInput
};
/**
* Check if a date variable is posterior than a specified date
* @title is after
*/
const dateAfter = {
name: 'dateAfter',
evaluator: (leftDate, rightDate) => {
const firstDateTime = new Date(leftDate).setHours(0, 0, 0, 0);
const secondDateTime = new Date(rightDate).setHours(0, 0, 0, 0);
return firstDateTime > secondDateTime;
},
validateLhs: isValidDateInput,
validateRhs: isValidDateInput
};
/**
* Check if a date variable is the same as a specified date
* @title is equal to
*/
const dateEquals = {
name: 'dateEquals',
evaluator: (leftDate, rightDate) => {
const firstDateIgnoringHours = new Date(leftDate).setHours(0, 0, 0, 0);
const secondDateIgnoringHours = new Date(rightDate).setHours(0, 0, 0, 0);
return firstDateIgnoringHours === secondDateIgnoringHours;
},
validateLhs: isValidDateInput,
validateRhs: isValidDateInput
};
/**
* Check if a date variable is different from a specified date
* @title is not equal
*/
const dateNotEquals = {
name: 'dateNotEquals',
evaluator: (leftDate, rightDate) => {
const firstDateIgnoringHours = new Date(leftDate).setHours(0, 0, 0, 0);
const secondDateIgnoringHours = new Date(rightDate).setHours(0, 0, 0, 0);
return firstDateIgnoringHours !== secondDateIgnoringHours;
},
validateLhs: isValidDateInput,
validateRhs: isValidDateInput
};
const dateBasedOperators = [
inRangeDate, dateInNextMinutes, dateNotInNextMinutes, dateAfter, dateBefore, dateEquals, dateNotEquals
];
/**
* Check if the number variable is greater or equal to a specific value
* @title ≥
*/
const greaterThanOrEqual = {
name: 'greaterThanOrEqual',
evaluator: (firstNumber, secondNumber) => firstNumber >= secondNumber,
validateLhs: numberValidator,
validateRhs: numberValidator
};
/**
* Check if the number variable is greater than a specific value
* @title >
*/
const greaterThan = {
name: 'greaterThan',
evaluator: (firstNumber, secondNumber) => firstNumber > secondNumber,
validateLhs: numberValidator,
validateRhs: numberValidator
};
/**
* Check if the number variable is lower or equal to a specific value
* @title ≤
*/
const lessOrEqual = {
name: 'lessOrEqual',
evaluator: (firstNumber, secondNumber) => firstNumber <= secondNumber,
validateLhs: numberValidator,
validateRhs: numberValidator
};
/**
* Check if the number variable is lower than a specific value
* @title <
*/
const lessThan = {
name: 'lessThan',
evaluator: (firstNumber, secondNumber) => firstNumber < secondNumber,
validateLhs: numberValidator,
validateRhs: numberValidator
};
/** List of all default number based operators */
const numberBasedOperators = [greaterThan, greaterThanOrEqual, lessThan, lessOrEqual];
const operatorList = [...arrayBasedOperators, ...basicOperators, ...numberBasedOperators, ...dateBasedOperators];
/**
* Determine if the condition is a properties condition
* @param condition Condition to analyze
*/
function isConditionProperties(condition) {
return condition && typeof condition.operator !== 'undefined' && typeof condition.lhs !== 'undefined';
}
/**
* Determine if the given operand is a Fact operand
* @param operand Operand to analyze
*/
function isOperandFact(operand) {
return operand && operand.type === 'FACT';
}
/**
* Determine if the given operand is a Inner Fact operand
* @param operand Operand to analyze
*/
function isOperandRuntimeFact(operand) {
return operand && operand.type === 'RUNTIME_FACT';
}
/**
* Determine if the given operand is a Static Value operand
* @param operand Operand to analyze
*/
function isOperandLiteral(operand) {
return operand && operand.type === 'LITERAL';
}
/**
* Determine if the given condition is All based child conditions
* @param condition Condition node
*/
function isAllConditions(condition) {
return condition && typeof condition.all !== 'undefined';
}
/**
* Determine if the given condition is Any based child conditions
* @param condition Condition node
*/
function isAnyConditions(condition) {
return condition && typeof condition.any !== 'undefined';
}
/**
* Determine if the given condition is Not based child conditions
* @param condition Condition node
*/
function isNotCondition(condition) {
return condition && typeof condition.not !== 'undefined';
}
/** Ruleset executor */
class RulesetExecutor {
/**
* Create a new ruleset executor
* @param ruleset Ruleset to evaluate
* @param rulesEngine Instance of the rules engine
*/
constructor(ruleset, rulesEngine) {
this.executionCounter = 0;
/**
* Find rule input facts
* @param obj
*/
this.findRuleInputFacts = (obj) => {
const ruleInputFacts = new Set();
this.collectRuleInputFacts(obj, ruleInputFacts);
return Array.from(ruleInputFacts);
};
this.ruleset = ruleset;
this.rulesEngine = rulesEngine;
this.operators = rulesEngine.operators;
this.engineRuleset = this.plugRuleset();
}
/**
* Recursively explores a rule to identify and collect input facts.
* Input facts are identified based on the 'FACT' type and operator-specific implicit dependencies.
* @param currentObject The current object being explored.
* @param ruleInputFacts A set to store the identified input facts for the rule.
*/
collectRuleInputFacts(currentObject, ruleInputFacts) {
if (currentObject && isOperandFact(currentObject)) {
ruleInputFacts.add(currentObject.value);
}
else if (Array.isArray(currentObject)) {
currentObject.forEach((elem) => this.collectRuleInputFacts(elem, ruleInputFacts));
}
else {
for (const key in currentObject) {
if ((key === 'operator') && isConditionProperties(currentObject)) {
const op = this.operators[currentObject[key]];
if (op && op.factImplicitDependencies) {
op.factImplicitDependencies.forEach((dep) => ruleInputFacts.add(dep));
}
}
else if (typeof currentObject[key] === 'object') {
this.collectRuleInputFacts(currentObject[key], ruleInputFacts);
}
}
}
}
/**
* Report performance mark for a rule run
* @param rule Rule to measure
* @param status status of the rule evaluation
*/
performanceMark(rule, status) {
if (this.rulesEngine.performance) {
const markName = `rules-engine:${this.rulesEngine.rulesEngineInstanceName}:${this.ruleset.name}:${rule.name}`;
this.rulesEngine.performance.mark(`${markName}:${status}`);
if (status === 'end') {
this.rulesEngine.performance.measure(markName, `${markName}:start`, `${markName}:end`);
}
}
}
/**
* Get operand value stream according to its type
* @param operand operand of the condition
* @param factsValue
* @param runtimeFactValues
*/
getOperandValue(operand, factsValue, runtimeFactValues) {
if (typeof operand === 'undefined') {
return undefined;
}
else if (isOperandFact(operand)) {
const factValue = factsValue[operand.value];
// eslint-disable-next-line new-cap -- convention for JSONPath
return operand.path ? factValue && JSONPath({ wrap: false, json: factValue, path: operand.path }) : factValue;
}
else if (isOperandLiteral(operand)) {
return operand.value;
}
else if (isOperandRuntimeFact(operand)) {
return runtimeFactValues[operand.value];
}
return undefined;
}
/**
* Process a root rule from a ruleset, and return the associated actions to be processed
* Will also update the runtimeFactValues map that is ruleset wise
* Note that runtimeFactValues will be mutated by all the runtime facts actions executed
* @param rule
* @param factsValue
* @param runtimeFactValues
* @protected
*/
evaluateRule(rule, factsValue, runtimeFactValues) {
return this.evaluateBlock(rule.rootElement, factsValue, runtimeFactValues);
}
/**
* Recursively process a block to extract all the actions keeping the order
* Note that runtimeFactValues will be mutated by all the runtime facts actions executed
* @param element
* @param factsValue
* @param runtimeFactValues This runtime fact map will be mutated by all the runtime facts actions executed
* @param actions
* @protected
*/
evaluateBlock(element, factsValue, runtimeFactValues, actions = []) {
if (this.isIfElseBlock(element)) {
(!element.condition || this.evaluateCondition(element.condition, factsValue, runtimeFactValues) ? element.successElements : element.failureElements)
.forEach((elementResult) => this.evaluateBlock(elementResult, factsValue, runtimeFactValues, actions));
}
else if (this.isActionBlock(element)) {
if (this.isActionSetTemporaryFactBlock(element)) {
runtimeFactValues[element.fact] = element.value;
}
else {
actions.push(element);
}
}
return actions;
}
/**
* Returns true if the element is a IfElse block
* @param element
* @protected
*/
isIfElseBlock(element) {
return element.blockType === 'IF_ELSE';
}
/**
* Returns true if the element is an action block
* @param element
* @protected
*/
isActionBlock(element) {
return 'elementType' in element && element.elementType === 'ACTION';
}
/**
* Returns true if the action sets a temporary fact
* @param element
* @protected
*/
isActionSetTemporaryFactBlock(element) {
return 'actionType' in element && element.actionType === 'SET_FACT';
}
/**
* Evaluate a condition block
* @param nestedCondition
* @param factsValue
* @param runtimeFactValues
* @protected
*/
evaluateCondition(nestedCondition, factsValue, runtimeFactValues) {
if (isConditionProperties(nestedCondition)) {
const operator = this.operators[nestedCondition.operator];
if (operator === undefined) {
throw new Error(`Unknown operator : ${nestedCondition.operator}, skipping the rule execution...`);
}
return executeOperator(this.getOperandValue(nestedCondition.lhs, factsValue, runtimeFactValues), this.getOperandValue('rhs' in nestedCondition ? nestedCondition.rhs : undefined, factsValue, runtimeFactValues), operator, factsValue);
}
if (isNotCondition(nestedCondition)) {
return !this.evaluateCondition(nestedCondition.not, factsValue, runtimeFactValues);
}
if (nestedCondition.all || nestedCondition.any) {
const evaluate = (condition) => this.evaluateCondition(condition, factsValue, runtimeFactValues);
return isAllConditions(nestedCondition) ? nestedCondition.all.every((element) => evaluate(element)) : nestedCondition.any.some((element) => evaluate(element));
}
throw new Error(`Unknown condition block met : ${JSON.stringify(nestedCondition)}`);
}
/**
* Plug ruleset to fact streams and trigger a first evaluation
*/
plugRuleset() {
const inputFactsForRule = {};
this.ruleset.rules.forEach((rule) => inputFactsForRule[rule.id] = this.findRuleInputFacts(rule.rootElement));
const factsThatRerunEverything = [];
this.ruleset.rules.forEach((rule) => {
if (rule.outputRuntimeFacts.length > 0 || rule.inputRuntimeFacts.length > 0) {
factsThatRerunEverything.push(...inputFactsForRule[rule.id]);
}
else { }
});
const triggerFull$ = factsThatRerunEverything.length === 0
? of([])
: combineLatest(factsThatRerunEverything.map((fact) => this.rulesEngine.retrieveOrCreateFactStream(fact)));
const result$ = triggerFull$.pipe(switchMap(() => {
const runtimeFactValues = {};
let rulesetInputFacts;
if (this.rulesEngine.debugMode) {
rulesetInputFacts = Array.from(this.ruleset.rules.reduce((acc, rule) => {
inputFactsForRule[rule.id].forEach((factName) => acc.add(factName));
return acc;
}, new Set()));
}
return combineLatest(this.ruleset.rules.map((rule) => {
const inputFacts = inputFactsForRule[rule.id];
const values$ = inputFacts.map((fact) => this.rulesEngine.retrieveOrCreateFactStream(fact));
return (values$.length > 0 ? combineLatest(values$) : of([[]]))
.pipe(startWith(undefined), pairwise(), tap(() => this.performanceMark(rule, 'start')), map(([oldFactValues, factValues]) => {
const output = { actions: undefined };
try {
output.actions = this.evaluateRule(rule, inputFacts.reduce((acc, id, index) => {
acc[id] = factValues[index];
return acc;
}, {}), runtimeFactValues);
}
catch (error) {
output.actions = undefined;
output.error = error;
}
if (this.rulesEngine.debugMode) {
output.evaluation = handleRuleEvaluationDebug(rule, this.ruleset.name, output.actions, output.error, runtimeFactValues, factValues, oldFactValues, inputFacts);
}
else if (output.error) {
this.rulesEngine.logger?.error(`Error while evaluating rule ID: ${rule.id}`, output.error);
this.rulesEngine.logger?.warn(`Skipping rule ${rule.name}, and the associated ruleset`);
}
return output;
}), tap(() => this.performanceMark(rule, 'end')));
})).pipe(startWith(undefined), pairwise(), map(([prevRes, currRes]) => {
const actionsLists = currRes.map((r) => r.actions);
const allExecutionsValid = actionsLists.every((actions) => !!actions);
let execInfo = { actionsLists: (allExecutionsValid ? actionsLists : [[]]) };
if (this.rulesEngine.engineDebug) {
execInfo = {
...execInfo,
...this.rulesEngine.engineDebug.handleDebugRulesetExecutionInfo(currRes, prevRes, allExecutionsValid, rulesetInputFacts, runtimeFactValues, ++this.executionCounter, this.ruleset)
};
}
return execInfo;
}), map((output) => {
const outputActions = [].concat(...output.actionsLists);
if (this.rulesEngine.engineDebug && output.allExecutionsValid) {
this.rulesEngine.engineDebug.addRulesetExecutionEvent(this.ruleset, output.executionCounter, rulesetInputFacts, outputActions, runtimeFactValues, output.rulesetTriggers, output.rulesetOutputExecution);
}
return outputActions;
}), distinctUntilChanged((prev, curr) => prev.length === 0 && curr.length === 0));
}), shareReplay({ bufferSize: 1, refCount: true }));
return {
id: this.ruleset.id,
validityRange: this.ruleset.validityRange,
linkedComponents: this.ruleset.linkedComponents,
rulesResultsSubject$: result$
};
}
}
/** Rules engine */
class RulesEngine {
/**
* Flag to check if the run is in debug mode or not
*/
get debugMode() {
return !!this.engineDebug;
}
/**
* Rules engine
* @param options rules engine options
*/
constructor(options) {
/** Map of registered fact stream, this map is mutated by the ruleset executors */
this.factMap = {};
/** Subject containing the rulesets and the results stream*/
this.rulesetMapSubject = new BehaviorSubject({});
this.performance = options?.performance || (typeof window === 'undefined' ? undefined : window.performance);
this.engineDebug = options?.debugger;
this.engineDebug?.registerRuleEngine(this);
this.logger = options?.logger;
this.rulesEngineInstanceName = options?.rulesEngineInstanceName || 'RulesEngine';
this.factDefaultDelay = options?.factDefaultDelay;
// Load default operators
this.operators = operatorList.reduce((acc, operator) => {
acc[operator.name] = operator;
return acc;
}, {});
this.events$ = this.rulesetMapSubject.pipe(this.prepareActionsStream(), this.handleActionsStreamOutput());
if (options?.facts) {
this.upsertFacts(options.facts);
}
if (options?.rules) {
this.upsertRulesets(options.rules);
}
if (options?.operators) {
this.upsertOperators(options.operators);
}
}
/**
* Attach debug events to actions stream if debug engine is activated
*/
handleActionsStreamOutput() {
return (actionsStream$) => this.engineDebug ? actionsStream$.pipe(tap((allActions) => this.engineDebug.allActionsChange(allActions))) : actionsStream$;
}
/**
* Create the actions stream event based on provided active rulesets ids; Handle debug too
* @param ruleSets
*/
prepareActionsStream(ruleSets) {
return (rulesetMapSubject$) => (this.engineDebug
? rulesetMapSubject$.pipe(tap((ruleSetExecutorMap) => this.engineDebug.activeRulesetsChange(ruleSetExecutorMap, ruleSets)), filterRulesetsEventStream(ruleSets))
: rulesetMapSubject$.pipe(filterRulesetsEventStream(ruleSets)));
}
/**
* Create or retrieve a fact stream
* The fact stream created will be registered in the engine
* @param id ID of the fact to retrieve
* @param factValue$ Value stream for the fact
*/
retrieveOrCreateFactStream(id, factValue$) {
// trick to emit undefined if the observable is not immediately emitting (to not bloc execution)
const obs$ = factValue$
? merge(factValue$, of(undefined).pipe(delay(this.factDefaultDelay || 0), takeUntil(factValue$)))
: factValue$;
const factObj = this.factMap[id];
if (factObj) {
if (factValue$) {
factObj.subject.next(obs$);
}
return factObj.value$;
}
const subject = new BehaviorSubject(obs$);
const value$ = subject.pipe(switchMap((value) => value || of(undefined)), distinctUntilChanged(), shareReplay(1));
this.factMap[id] = {
subject,
value$
};
return value$;
}
/**
* Retrieve the promise of the latest value of a fact.
* Return undefined if the fact is not defined.
* @param id ID of the fact to retrieve
*/
retrieveFactValue(id) {
return this.factMap[id].value$ && firstValueFrom(this.retrieveOrCreateFactStream(id), { defaultValue: undefined });
}
/**
* Update or insert fact in rules engine
* @param facts fact list to add / update
*/
upsertFacts(facts) {
(Array.isArray(facts) ? facts : [facts]).forEach(({ id, value$ }) => {
this.engineDebug?.addAvailableFactsSnapshotEvent(id, value$);
this.retrieveOrCreateFactStream(id, value$);
});
}
/**
* Update or insert rule in rules engine
* @param rulesets
*/
upsertRulesets(rulesets) {
this.engineDebug?.addAvailableRulesets(rulesets);
this.rulesetMapSubject.next(rulesets.reduce((accRuleset, ruleset) => {
accRuleset[ruleset.id] = new RulesetExecutor(ruleset, this);
return accRuleset;
}, { ...this.rulesetMapSubject.value }));
}
/**
* Update or insert operator in rules engine
* @param operators operator list to add / update
*/
upsertOperators(operators) {
this.operators = operators.reduce((acc, operator) => {
acc[operator.name] = operator;
return acc;
}, { ...this.operators });
}
/**
* Operator to apply on a stream of rulesets ids
* Returns a stream of actions outputted by the rules engine, corresponding to the rulesetsIds
*/
getEventStream() {
return (rulesetsIds$) => rulesetsIds$.pipe(switchMap((ruleSets) => this.rulesetMapSubject.pipe(this.prepareActionsStream(ruleSets))), this.handleActionsStreamOutput());
}
/** Get the list of registered facts names */
getRegisteredFactsNames() {
return Object.keys(this.factMap);
}
}
class RulesEngineRunnerService {
constructor(store, logger, engineConfig) {
this.store = store;
this.logger = logger;
/** Observable of component linked to the component */
this.linkedComponents$ = new BehaviorSubject({});
/**
* List of action handlers
* @deprecated will become protected in Otter v13, instead use {@link registerActionHandlers}
*/
this.actionHandlers = new Set();
this.enabled = !engineConfig?.dryRun;
this.engine = new RulesEngine({
debugger: engineConfig?.debug ? new EngineDebugger({ eventsStackLimit: engineConfig?.debugEventsStackLimit }) : undefined,
logger: this.logger
});
this.ruleSets$ = combineLatest([
this.store.pipe(select(selectActiveRuleSets)),
this.linkedComponents$.pipe(switchMap((linkedComponentsNamesMap) => this.store.pipe(select(selectComponentsLinkedToRuleset), map((rulesetsWithLinkedComponentsMap) => Object.keys(rulesetsWithLinkedComponentsMap.or).filter((rulesetId) => rulesetsWithLinkedComponentsMap.or[rulesetId].some((componentId) => linkedComponentsNamesMap[componentId] > 0))))))
]).pipe(map(([activeRulesets, linkedComponentsRulesetsIds]) => ([...activeRulesets, ...linkedComponentsRulesetsIds])));
this.events$ = this.ruleSets$.pipe(this.engine.getEventStream(), shareReplay(1));
this.upsertOperators(operatorList);
this.store.pipe(select(selectAllRulesets), takeUntilDestroyed()).subscribe((rulesets) => this.engine.upsertRulesets(rulesets));
this.events$.pipe(takeUntilDestroyed(), filter(() => this.enabled)).subscribe((events) => {
void this.executeActions(events);
});
}
/**
* Execute the list of actions
* @param actions
*/
async executeActions(actions) {
const actionHandlers = [...this.actionHandlers];
const supportedActions = new Set(actionHandlers.flatMap((handler) => handler.supportingActions));
const actionMaps = actions
.filter((action) => {
const isKnown = supportedActions.has(action.actionType);
if (!isKnown) {
this.logger.warn(`The action ${action.actionType} does not have registered handler`);
}
return isKnown;
})
.reduce((acc, action) => {
acc[action.actionType] ||= [];
acc[action.actionType].push(action);
return acc;
}, {});
const handling = actionHandlers
.map((handler) => handler.executeActions(handler.supportingActions
.filter((supportedAction) => actionMaps[supportedAction])
.reduce((acc, supportedAction) => acc.concat(actionMaps[supportedAction]), [])));
await Promise.all(handling);
}
/**
* Update or insert fact in the rules engine
* @param facts fact list to add / update
*/
upsertFacts(facts) {
this.engine.upsertFacts(facts);
}
/**
* Update or insert operator in the rules engine
* @param operators operator list to add / update
*/
upsertOperators(operators) {
this.engine.upsertOperators(operators);
}
/**
* Upsert a list of RuleSets to be run in the rules engine
* @param ruleSets
*/
upsertRulesets(ruleSets) {
this.store.dispatch(setRulesetsEntities({ entities: ruleSets }));
}
/**
* Add action handlers in the rules engine
* @param actionHandlers
*/
registerActionHandlers(...actionHandlers) {
actionHandlers.forEach((actionHandler) => this.actionHandlers.add(actionHandler));
}
/**
* Remove action handlers in the rules engine
* @param actionHandlers
*/
unregisterActionHandlers(...actionHandlers) {
actionHandlers.forEach((actionHandler) => this.actionHandlers.delete(actionHandler));
}
/**
* Enable temporary a rule set
* @param componentComputedName Name of the component to enable the ruleset for
*/
enableRuleSetFor(componentComputedName) {
const newMap = this.linkedComponents$.value;
newMap[componentComputedName] = newMap[componentComputedName] ? newMap[componentComputedName] + 1 : 1;
this.linkedComponents$.next(newMap);
}
/**
* Disable temporary a rule set
* @param componentComputedName Name of the component to disable the ruleset for
*/
disableRuleSetFor(componentComputedName) {
const newMap = this.linkedComponents$.value;
if (newMap[componentComputedName] > 0) {
newMap[componentComputedName]--;
this.linkedComponents$.next(newMap);
}
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineRunnerService, deps: [{ token: i1$3.Store }, { token: i2.LoggerService }, { token: RULES_ENGINE_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineRunnerService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineRunnerService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1$3.Store }, { type: i2.LoggerService }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [RULES_ENGINE_OPTIONS]
}] }] });
class RulesEngineRunnerModule {
static forRoot(options) {
const opts = options ? { ...DEFAULT_RULES_ENGINE_OPTIONS, ...options } : DEFAULT_RULES_ENGINE_OPTIONS;
return {
ngModule: RulesEngineRunnerModule,
providers: [
{ provide: RULES_ENGINE_OPTIONS, useValue: opts },
RulesEngineRunnerService
]
};
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineRunnerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineRunnerModule, imports: [StoreModule,
RulesetsStoreModule,
LoggerModule] }); }
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineRunnerModule, imports: [StoreModule,
RulesetsStoreModule,
LoggerModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineRunnerModule, decorators: [{
type: NgModule,
args: [{
imports: [
StoreModule,
RulesetsStoreModule,
LoggerModule
]
}]
}] });
const OTTER_RULES_ENGINE_DEVTOOLS_DEFAULT_OPTIONS = {
isActivatedOnBootstrap: false
};
const OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS = new InjectionToken('Otter RulesEngine Devtools options');
class OtterRulesEngineDevtools {
/**
* Return true if the rules engine debug option is activated
*/
get isRulesEngineDebugActivated() {
return !!this.rulesEngineService.engine.debugMode;
}
constructor(store, rulesEngineService, options) {
this.store = store;
this.rulesEngineService = rulesEngineService;
const eventsStackLimit = (options || OTTER_RULES_ENGINE_DEVTOOLS_DEFAULT_OPTIONS).rulesEngineStackLimit;
this.rulesEngineEvents$ = this.rulesEngineService.engine.engineDebug?.debugEvents$.pipe(scan((previousEvents, currentEvent) => {
const stack = eventsStackLimit && previousEvents.length === eventsStackLimit ? previousEvents.slice(1) : previousEvents;
return [...stack, currentEvent];
}, []), shareReplay(1));
this.rulesEngineReport$ = this.rulesEngineEvents$ && combineLatest([this.rulesEngineEvents$, this.store.pipe(select(selectRulesetsEntities))]).pipe(map(([events, rulesetEntities]) => {
const rulesetMap = Object.entries(rulesetEntities)
.reduce((acc, [id, ruleset]) => {
if (ruleset) {
acc[id] = ruleset;
}
return acc;
}, {});
return { events, rulesetMap };
}), shareReplay(1));
}
/** Return the list of debug events emitted by rules engine */
async getCurrentRulesEngineEventsStack() {
return this.rulesEngineEvents$ && firstValueFrom(this.rulesEngineEvents$);
}
/** Returns the list of active rulesets (name and id) at the moment when the function is called */
async getActiveRulesets() {
const lastActiveRulesetsEvent = (this.rulesEngineEvents$ && await firstValueFrom(this.rulesEngineEvents$))?.filter((e) => e.type === 'ActiveRulesets').reverse()[0];
return lastActiveRulesetsEvent?.rulesets;
}
/** Returns the list of available rulesets (name and id) at the moment when the function is called */
async getAvailableRulesets() {
const lastAvailableRulesetsEvent = (this.rulesEngineEvents$ && await firstValueFrom(this.rulesEngineEvents$))?.filter((e) => e.type === 'AvailableRulesets').reverse()[0];
return lastAvailableRulesetsEvent?.availableRulesets;
}
/** Returns the list of output actions emitted by the rules engine at the moment when the function is called */
async getAllOutputActions() {
return (this.rulesEngineEvents$ && await firstValueFrom(this.rulesEngineEvents$))?.filter((e) => e.type === 'AllActions')?.reverse()[0];
}
/**
* Get the list of executions for the given ruleset
* @param rulesetId
*/
async getRulesetExecutions(rulesetId) {
return (this.rulesEngineEvents$ && await firstValueFrom(this.rulesEngineEvents$))?.filter((e) => (e.type === 'RulesetExecution' || e.type === 'RulesetExecutionError')
&& e.rulesetId === rulesetId);
}
/**
* Check if the ruleset is activ in the moment when the function is called
* @param rulesetId
* @returns True if the ruleset is active; False if the ruleset is inactive or it does not exist
*/
async isRulesetActive(rulesetId) {
return !!(await this.getActiveRulesets())?.find((r) => r.id === rulesetId);
}
/**
* Get the list of rules executed for the specified ruleset
* @param rulesetId
*/
async getRulesEvaluationsForRuleset(rulesetId) {
const rulesetExec = await this.getRulesetExecutions(rulesetId);
return rulesetExec?.map((e) => e?.rulesEvaluations?.filter((re) => !re.cached)).flat();
}
/**
* Get the list of input facts (name, current value) for the specified ruleset, at the moment when the function is called
* @param rulesetId
*/
async getInputFactsForRuleset(rulesetId) {
const rulesetExecutions = await this.getRulesetExecutions(rulesetId);
return rulesetExecutions ? rulesetExecutions.at(-1).inputFacts : undefined;
}
/**
* Get the list of triggers for the specified ruleset
* @param rulesetId
*/
async getTriggersForRuleset(rulesetId) {
return (await this.getRulesEvaluationsForRuleset(rulesetId))?.map((e) => e.triggers).flat().flatMap((triggersMap) => Object.values(triggersMap));
}
/**
* Get the list of outputed actions emitted by the given ruleset, at the moment when the function is called
* @param rulesetId
*/
async getOutputActionsForRuleset(rulesetId) {
const rulesetExecutions = await this.getRulesetExecutions(rulesetId);
return rulesetExecutions ? rulesetExecutions.at(-1).outputActions : undefined;
}
/** Get the list of fact names and corresponding values */
getAllFactsSnapshot() {
const registeredFacts = this.rulesEngineService?.engine.getRegisteredFactsNames();
if (registeredFacts) {
return this.rulesEngineService.engine.engineDebug?.getFactsSnapshot(registeredFacts);
}
}
/**
* Retrieve the ruleset information (rules, linkedComponents, validity range etc.) for a ruleset id
* @param rulesetId
*/
getRulesetInformation(rulesetId) {
return firstValueFrom(this.store.pipe(select(selectRulesetsEntities), map((entities) => entities[rulesetId])));
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: OtterRulesEngineDevtools, deps: [{ token: i1$3.Store }, { token: RulesEngineRunnerService }, { token: OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: OtterRulesEngineDevtools, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: OtterRulesEngineDevtools, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: i1$3.Store }, { type: RulesEngineRunnerService }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS]
}] }] });
/* eslint-disable no-console -- purpose of the service is to log in the console */
class RulesEngineDevtoolsConsoleService {
/** Name of the Window property to access to the devtools */
static { this.windowModuleName = 'rulesEngine'; }
constructor(rulesEngineDevtools, options) {
this.rulesEngineDevtools = rulesEngineDevtools;
this.options = {
...OTTER_RULES_ENGINE_DEVTOOLS_DEFAULT_OPTIONS,
...options
};
if (this.options.isActivatedOnBootstrap) {
this.activate();
}
}
/** @inheritDoc */
activate() {
const windowWithDevtools = window;
windowWithDevtools._OTTER_DEVTOOLS_ ||= {};
windowWithDevtools._OTTER_DEVTOOLS_[RulesEngineDevtoolsConsoleService.windowModuleName] = this;
console.info(`Otter rules engine Devtools is now accessible via the _OTTER_DEVTOOLS_.${RulesEngineDevtoolsConsoleService.windowModuleName} variable`);
}
/** Return the list of debug events emitted by rules engine */
async getCurrentRulesEngineEventsStack() {
console.log(await this.rulesEngineDevtools.getCurrentRulesEngineEventsStack());
}
/** Returns the list of active rulesets (name and id) at the moment when the function is called */
async getActiveRulesets() {
console.log(await this.rulesEngineDevtools.getActiveRulesets());
}
/** Returns the list of available rulesets (name and id) at the moment when the function is called */
async getAvailableRulesets() {
console.log(await this.rulesEngineDevtools.getAvailableRulesets());
}
/** Returns the list of output actions emitted by the rules engine at the moment when the function is called */
async getAllOutputActions() {
console.log(await this.rulesEngineDevtools.getAllOutputActions());
}
/**
* Get the list of executions for the given ruleset
* @param rulesetId
*/
async getRulesetExecutions(rulesetId) {
console.log(await this.rulesEngineDevtools.getRulesetExecutions(rulesetId));
}
/**
* Check if the ruleset is activ in the moment when the function is called
* @param rulesetId
* @returns True if the ruleset is active; False if the ruleset is inactive or it does not exist
*/
async isRulesetActive(rulesetId) {
console.log(await this.rulesEngineDevtools.isRulesetActive(rulesetId));
}
/**
* Get the list of rules executed for the specified ruleset
* @param rulesetId
*/
async getRulesEvaluationsForRuleset(rulesetId) {
console.log(await this.rulesEngineDevtools.getRulesEvaluationsForRuleset(rulesetId));
}
/**
* Get the list of input facts (name, current value) for the specified ruleset, at the moment when the function is called
* @param rulesetId
*/
async getInputFactsForRuleset(rulesetId) {
console.log(await this.rulesEngineDevtools.getInputFactsForRuleset(rulesetId));
}
/**
* Get the list of triggers for the specified ruleset
* @param rulesetId
*/
async getTriggersForRuleset(rulesetId) {
console.log(await this.rulesEngineDevtools.getTriggersForRuleset(rulesetId));
}
/**
* Get the list of outputed actions emitted by the given ruleset, at the moment when the function is called
* @param rulesetId
*/
async getOutputActionsForRuleset(rulesetId) {
console.log(await this.rulesEngineDevtools.getOutputActionsForRuleset(rulesetId));
}
/** Get the list of fact names and corresponding values */
async getAllFactsSnapshot() {
console.log(await this.rulesEngineDevtools.getAllFactsSnapshot());
}
/**
* Retrieve the ruleset information (rules, linkedComponents, validity range etc.) for a ruleset id
* @param rulesetId
*/
async getRulesetInformation(rulesetId) {
console.log(await this.rulesEngineDevtools.getRulesetInformation(rulesetId));
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineDevtoolsConsoleService, deps: [{ token: OtterRulesEngineDevtools }, { token: OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineDevtoolsConsoleService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineDevtoolsConsoleService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: OtterRulesEngineDevtools }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS]
}] }] });
class RulesEngineDevtoolsMessageService {
constructor(rulesEngineDevtools, logger, options) {
this.rulesEngineDevtools = rulesEngineDevtools;
this.logger = logger;
this.forceEmitRulesEngineReport = new BehaviorSubject(undefined);
this.sendMessage = (sendOtterMessage);
this.destroyRef = inject(DestroyRef);
this.serializeError = (error) => error instanceof Error ? error.toString() : error;
this.options = {
...OTTER_RULES_ENGINE_DEVTOOLS_DEFAULT_OPTIONS,
...options
};
if (this.options.isActivatedOnBootstrap) {
this.activate();
}
}
/**
* Function to trigger a re-send a requested messages to the Otter Chrome DevTools extension
* @param only restricted list of messages to re-send
*/
handleReEmitRequest(only) {
if (!only || only.includes('rulesEngineEvents')) {
this.forceEmitRulesEngineReport.next();
}
}
/**
* Function to handle the incoming messages from Otter Chrome DevTools extension
* @param message
*/
handleEvents(message) {
this.logger.debug('Message handling by the configuration service', message);
switch (message.dataType) {
case 'connect': {
this.connectPlugin();
break;
}
case 'requestMessages': {
this.handleReEmitRequest(message.only);
break;
}
default: {
this.logger.warn('Message ignored by the configuration service', message);
}
}
}
/**
* Serialize exceptions in a way that will display the error message after a JSON.stringify()
* @param debugEvent
*/
serializeReportEvent(debugEvent) {
if (debugEvent.type !== 'RulesetExecutionError') {
return debugEvent;
}
return {
...debugEvent,
rulesEvaluations: debugEvent.rulesEvaluations.map((ruleEvaluation) => ({
...ruleEvaluation,
error: this.serializeError(ruleEvaluation.error)
})),
// eslint-disable-next-line @typescript-eslint/no-unsafe-return -- type is explicitly `any`
errors: debugEvent.errors.map((error) => this.serializeError(error))
};
}
/**
* Function to start the rules engine reporting to the Otter Chrome DevTools extension
*/
startRulesEngineReport() {
if (this.rulesEngineDevtools.rulesEngineReport$) {
combineLatest([
this.forceEmitRulesEngineReport,
this.rulesEngineDevtools.rulesEngineReport$
]).pipe(takeUntilDestroyed(this.destroyRef)).subscribe(([, report]) => {
const sanitizedReport = { ...report, events: report.events.map((reportEvents) => this.serializeReportEvent(reportEvents)) };
this.sendMessage('rulesEngineEvents', sanitizedReport);
});
}
}
/**
* Function to connect the plugin to the Otter DevTools extension
*/
connectPlugin() {
this.logger.info('Otter DevTools is plugged to the application');
this.forceEmitRulesEngineReport.next();
}
/** Activate the Otter DevTools */
activate() {
this.startRulesEngineReport();
fromEvent(window, 'message').pipe(takeUntilDestroyed(this.destroyRef), filterMessageContent(isRulesEngineMessage)).subscribe((e) => this.handleEvents(e));
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineDevtoolsMessageService, deps: [{ token: OtterRulesEngineDevtools }, { token: i2.LoggerService }, { token: OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineDevtoolsMessageService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineDevtoolsMessageService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: OtterRulesEngineDevtools }, { type: i2.LoggerService }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS]
}] }] });
class RulesEngineDevtoolsModule {
/**
* Initialize Otter Devtools
* @param options
*/
static instrument(options) {
return {
ngModule: RulesEngineDevtoolsModule,
providers: [
{ provide: OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS, useValue: { ...OTTER_RULES_ENGINE_DEVTOOLS_DEFAULT_OPTIONS, ...options }, multi: false },
RulesEngineDevtoolsMessageService,
RulesEngineDevtoolsConsoleService
]
};
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineDevtoolsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineDevtoolsModule, imports: [StoreModule,
RulesetsStoreModule] }); }
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineDevtoolsModule, providers: [
{ provide: OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS, useValue: OTTER_RULES_ENGINE_DEVTOOLS_DEFAULT_OPTIONS },
RulesEngineDevtoolsMessageService,
RulesEngineDevtoolsConsoleService
], imports: [StoreModule,
RulesetsStoreModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: RulesEngineDevtoolsModule, decorators: [{
type: NgModule,
args: [{
imports: [
StoreModule,
RulesetsStoreModule
],
providers: [
{ provide: OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS, useValue: OTTER_RULES_ENGINE_DEVTOOLS_DEFAULT_OPTIONS },
RulesEngineDevtoolsMessageService,
RulesEngineDevtoolsConsoleService
]
}]
}] });
/** Abstract fact set service */
class FactsService {
constructor(rulesEngine) {
this.rulesEngine = rulesEngine;
}
/** Register the set of facts */
register() {
this.rulesEngine.upsertFacts(Object.entries(this.facts)
.map(([id, factValue]) => ({ id, value$: factValue })));
}
}
class CurrentTimeFactsService extends FactsService {
constructor(rulesEngine) {
super(rulesEngine);
this.currentTimeSubject$ = new BehaviorSubject(Date.now());
/** @inheritDoc */
this.facts = {
o3rCurrentTime: this.currentTimeSubject$.asObservable()
};
}
/** Compute the current time */
tick() {
this.currentTimeSubject$.next(Date.now());
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: CurrentTimeFactsService, deps: [{ token: RulesEngineRunnerService }], target: i0.ɵɵFactoryTarget.Injectable }); }
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: CurrentTimeFactsService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: CurrentTimeFactsService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: RulesEngineRunnerService }] });
/**
* Generated bundle index. Do not edit.
*/
export { CurrentTimeFactsService, DEFAULT_RULES_ENGINE_OPTIONS, EngineDebugger, FactsService, FactsSnapshotComponent, O3rFallbackToPipe, O3rJsonOrStringPipe, OTTER_RULES_ENGINE_DEVTOOLS_DEFAULT_OPTIONS, OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS, OtterRulesEngineDevtools, RULESETS_REDUCER_TOKEN, RULESETS_STORE_NAME, RULES_ENGINE_OPTIONS, RulesEngine, RulesEngineDevtoolsConsoleService, RulesEngineDevtoolsMessageService, RulesEngineDevtoolsModule, RulesEngineRunnerModule, RulesEngineRunnerService, RulesetHistoryPresComponent, RulesetHistoryPresModule, RulesetsEffect, RulesetsStoreModule, allEqual, allGreater, allIn, allLower, allMatch, allNotIn, allRangeNumber, arrayBasedOperators, arrayContains, basicOperators, cancelRulesetsRequest, clearRulesetsEntities, dateAfter, dateBasedOperators, dateBefore, dateEquals, dateInNextMinutes, dateNotEquals, dateNotInNextMinutes, equals, executeOperator, failRulesetsEntities, getDefaultRulesetsReducer, getStatus, greaterThan, greaterThanOrEqual, inArray, inRangeDate, inString, isAllConditions, isAnyConditions, isConditionProperties, isDefined, isNotCondition, isOperandFact, isOperandLiteral, isOperandRuntimeFact, isRangeNumber, isRulesEngineMessage, isString, isSupportedSimpleTypes, isUndefined, isValidDate, isValidDateInput, isValidDateRange, lengthEquals, lengthGreaterThan, lengthGreaterThanOrEquals, lengthLessThan, lengthLessThanOrEquals, lengthNotEquals, lessOrEqual, lessThan, matchesPattern, notArrayContains, notEquals, notInArray, notInString, notStringContains, numberBasedOperators, numberValidator, oneEquals, oneGreater, oneIn, oneLower, oneMatches, oneRangeNumber, operatorList, parseRegExp, resetRulesets, rulesetReportToHistory, rulesetsAdapter, rulesetsInitialState, rulesetsReducer, rulesetsReducerFeatures, rulesetsStorageDeserializer, rulesetsStorageSerializer, rulesetsStorageSync, selectActiveRuleSets, selectAllRulesets, selectComponentsLinkedToRuleset, selectRuleSetsInRange, selectRulesetsEntities, selectRulesetsIds, selectRulesetsState, selectRulesetsStorePendingStatus, selectRulesetsTotal, setRulesets, setRulesetsEntities, setRulesetsEntitiesFromApi, stringContains, updateRulesets, upsertRulesetsEntities, upsertRulesetsEntitiesFromApi };
//# sourceMappingURL=o3r-rules-engine.mjs.map