From 7a2271447542a640f753dd5602480a54f20c1639 Mon Sep 17 00:00:00 2001 From: matthieu-crouzet Date: Tue, 26 Nov 2024 16:33:37 +0100 Subject: [PATCH] deprecate(rules-engine): actionHandlers will become protected in v13 --- .../app/placeholder/placeholder.component.ts | 2 +- .../rules-engine/rules-engine.component.ts | 8 +- docs/rules-engine/how-to-use/custom-action.md | 2 +- packages/@o3r/rules-engine/migration.json | 5 + .../schematics/ng-update/index.ts | 27 ++++- .../use-register-action-handlers.spec.ts | 100 ++++++++++++++++++ .../v11.6/use-register-action-handlers.ts | 20 ++++ .../runner/rules-engine.runner.service.ts | 27 ++++- 8 files changed, 178 insertions(+), 13 deletions(-) create mode 100644 packages/@o3r/rules-engine/schematics/ng-update/v11.6/use-register-action-handlers.spec.ts create mode 100644 packages/@o3r/rules-engine/schematics/ng-update/v11.6/use-register-action-handlers.ts diff --git a/apps/showcase/src/app/placeholder/placeholder.component.ts b/apps/showcase/src/app/placeholder/placeholder.component.ts index e66a35082c..cb5a6f7377 100644 --- a/apps/showcase/src/app/placeholder/placeholder.component.ts +++ b/apps/showcase/src/app/placeholder/placeholder.component.ts @@ -43,7 +43,7 @@ export class PlaceholderComponent implements AfterViewInit { // We recommend to do the next lines in the AppComponent // Here we do it for the sake of the example inject(TripFactsService).register(); - inject(RulesEngineRunnerService).actionHandlers.add(inject(PlaceholderRulesEngineActionHandler)); + inject(RulesEngineRunnerService).registerActionHandlers(inject(PlaceholderRulesEngineActionHandler)); void this.loadRuleSet(); } diff --git a/apps/showcase/src/app/rules-engine/rules-engine.component.ts b/apps/showcase/src/app/rules-engine/rules-engine.component.ts index b5491ae784..a2ad6f48ea 100644 --- a/apps/showcase/src/app/rules-engine/rules-engine.component.ts +++ b/apps/showcase/src/app/rules-engine/rules-engine.component.ts @@ -81,9 +81,11 @@ export class RulesEngineComponent implements AfterViewInit { constructor() { // We recommend to do the next lines in the AppComponent // Here we do it for the sake of the example - this.rulesEngineService.actionHandlers.add(inject(ConfigurationRulesEngineActionHandler)); - this.rulesEngineService.actionHandlers.add(inject(AssetRulesEngineActionHandler)); - this.rulesEngineService.actionHandlers.add(inject(LocalizationRulesEngineActionHandler)); + this.rulesEngineService.registerActionHandlers( + inject(ConfigurationRulesEngineActionHandler), + inject(AssetRulesEngineActionHandler), + inject(LocalizationRulesEngineActionHandler) + ); this.rulesEngineService.engine.upsertOperators([duringSummer] as UnaryOperator[]); this.rulesEngineService.engine.upsertOperators([dateInNextMinutes] as Operator[]); inject(TripFactsService).register(); diff --git a/docs/rules-engine/how-to-use/custom-action.md b/docs/rules-engine/how-to-use/custom-action.md index 295cc57772..0837cd755d 100644 --- a/docs/rules-engine/how-to-use/custom-action.md +++ b/docs/rules-engine/how-to-use/custom-action.md @@ -141,7 +141,7 @@ bootstrapApplication(AppComponent, appConfig) .then((m) => { runInInjectionContext(m.injector, () => { inject(RulesEngineRunnerService); - ruleEngine.actionHandlers.add(inject(PopupActionHandler)); + ruleEngine.registerActionHandlers(inject(PopupActionHandler)); }); }) // eslint-disable-next-line no-console diff --git a/packages/@o3r/rules-engine/migration.json b/packages/@o3r/rules-engine/migration.json index fa95d4ce3f..7936a45fc3 100644 --- a/packages/@o3r/rules-engine/migration.json +++ b/packages/@o3r/rules-engine/migration.json @@ -5,6 +5,11 @@ "version": "10.0.0-alpha.0", "description": "Updates of @o3r/rules-engine to v10.0.*", "factory": "./schematics/ng-update/index#updateV100" + }, + "migration-v11_6": { + "version": "11.6.0-alpha.0", + "description": "Updates of @o3r/rules-engine to v11.6.*", + "factory": "./schematics/ng-update/index#updateV116" } } } diff --git a/packages/@o3r/rules-engine/schematics/ng-update/index.ts b/packages/@o3r/rules-engine/schematics/ng-update/index.ts index 945b61aa67..ae44991a02 100644 --- a/packages/@o3r/rules-engine/schematics/ng-update/index.ts +++ b/packages/@o3r/rules-engine/schematics/ng-update/index.ts @@ -1,10 +1,10 @@ import { chain, Rule } from '@angular-devkit/schematics'; +import { createSchematicWithMetricsIfInstalled } from '@o3r/schematics'; import { updateRuleEngineService } from './v10.0/action-module-split'; +import { useRegisterActionHandlers } from './v11.6/use-register-action-handlers'; -/** - * update of Otter library V10.0 - */ -export function updateV100(): Rule { + +function updateV100Fn(): Rule { return (tree, context) => { const updateRules: Rule[] = [ @@ -18,3 +18,22 @@ export function updateV100(): Rule { return chain(updateRules)(tree, context); }; } + +/** + * update of Otter library V10.0 + */ +export const updateV100 = createSchematicWithMetricsIfInstalled(updateV100Fn); + +/** + * + */ +export function updateV116Fn(): Rule { + return chain([ + useRegisterActionHandlers + ]); +} + +/** + * Update of Otter library V11.6 + */ +export const updateV116 = createSchematicWithMetricsIfInstalled(updateV116Fn); diff --git a/packages/@o3r/rules-engine/schematics/ng-update/v11.6/use-register-action-handlers.spec.ts b/packages/@o3r/rules-engine/schematics/ng-update/v11.6/use-register-action-handlers.spec.ts new file mode 100644 index 0000000000..d0c4f86abd --- /dev/null +++ b/packages/@o3r/rules-engine/schematics/ng-update/v11.6/use-register-action-handlers.spec.ts @@ -0,0 +1,100 @@ +import { callRule, type SchematicContext, Tree } from '@angular-devkit/schematics'; +import { lastValueFrom } from 'rxjs'; +import { useRegisterActionHandlers } from './use-register-action-handlers'; + +let initialTree: Tree; +let tree: Tree; +const untouchedFile = 'untouchedFile.ts'; +const fileWithActionHandlerAdd = 'fileWithActionHandlerAdd.ts'; +const fileWithActionHandlerDelete = 'fileWithActionHandlerDelete.ts'; +const multiplePresenceFile = 'multiplePresence.ts'; + +describe('useRegisterActionHandlers', () => { + beforeEach(() => { + initialTree = Tree.empty(); + }); + + it('should not touch files without references', async () => { + initialTree.create(untouchedFile, 'export {};'); + tree = await lastValueFrom(callRule(useRegisterActionHandlers, initialTree, {} as SchematicContext)); + expect(tree.readText(untouchedFile)).toBe(initialTree.readText(untouchedFile)); + }); + + it('should change file with a reference to actionHandlers.add', async () => { + initialTree.create(fileWithActionHandlerAdd, ` + import {inject, runInInjectionContext} from '@angular/core'; + import {RulesEngineRunnerService} from '@o3r/rules-engine'; + import {appConfig} from './app/app.config'; + import {AppComponent} from './app/app.component'; + import {PopupActionHandler} from './services/popup-action-handler'; + + bootstrapApplication(AppComponent, appConfig) + .then((m) => { + runInInjectionContext(m.injector, () => { + inject(RulesEngineRunnerService); + ruleEngine.actionHandlers.add(inject(PopupActionHandler)); + }); + }) + // eslint-disable-next-line no-console + .catch(err => console.error(err)); + `); + tree = await lastValueFrom(callRule(useRegisterActionHandlers, initialTree, {} as SchematicContext)); + expect(tree.readText(fileWithActionHandlerAdd)).not.toContain('actionHandlers.add'); + expect(tree.readText(fileWithActionHandlerAdd)).toContain('ruleEngine.registerActionHandlers(inject(PopupActionHandler))'); + }); + + it('should change file with a reference to actionHandlers.delete', async () => { + initialTree.create(fileWithActionHandlerDelete, ` + import {inject, runInInjectionContext} from '@angular/core'; + import {RulesEngineRunnerService} from '@o3r/rules-engine'; + import {appConfig} from './app/app.config'; + import {AppComponent} from './app/app.component'; + import {PopupActionHandler} from './services/popup-action-handler'; + + bootstrapApplication(AppComponent, appConfig) + .then((m) => { + runInInjectionContext(m.injector, () => { + inject(RulesEngineRunnerService); + ruleEngine.actionHandlers.delete(inject(PopupActionHandler)); + }); + }) + // eslint-disable-next-line no-console + .catch(err => console.error(err)); + `); + tree = await lastValueFrom(callRule(useRegisterActionHandlers, initialTree, {} as SchematicContext)); + expect(tree.readText(fileWithActionHandlerDelete)).not.toContain('actionHandlers.delete'); + expect(tree.readText(fileWithActionHandlerDelete)).toContain('ruleEngine.unregisterActionHandlers(inject(PopupActionHandler))'); + }); + + it('should change file with multiple reference', async () => { + initialTree.create(multiplePresenceFile, ` + import {inject, runInInjectionContext} from '@angular/core'; + import {RulesEngineRunnerService} from '@o3r/rules-engine'; + import {ConfigurationRulesEngineActionHandler} from '@o3r/configuration/rules-engine'; + import {appConfig} from './app/app.config'; + import {AppComponent} from './app/app.component'; + import {PopupActionHandler} from './services/popup-action-handler'; + + bootstrapApplication(AppComponent, appConfig) + .then((m) => { + runInInjectionContext(m.injector, () => { + inject(RulesEngineRunnerService); + ruleEngine.actionHandlers.add(inject(PopupActionHandler)); + ruleEngine.actionHandlers.add(inject(ConfigurationRulesEngineActionHandler)); + ruleEngine.actionHandlers.delete(inject(PopupActionHandler)); + ruleEngine.actionHandlers.delete(inject(ConfigurationRulesEngineActionHandler)); + }); + }) + // eslint-disable-next-line no-console + .catch(err => console.error(err)); + `); + tree = await lastValueFrom(callRule(useRegisterActionHandlers, initialTree, {} as SchematicContext)); + const text = tree.readText(multiplePresenceFile); + expect(text).not.toContain('actionHandlers.add'); + expect(tree.readText(multiplePresenceFile)).toContain('ruleEngine.registerActionHandlers(inject(PopupActionHandler))'); + expect(tree.readText(multiplePresenceFile)).toContain('ruleEngine.registerActionHandlers(inject(ConfigurationRulesEngineActionHandler))'); + expect(text).not.toContain('actionHandlers.delete'); + expect(tree.readText(multiplePresenceFile)).toContain('ruleEngine.unregisterActionHandlers(inject(PopupActionHandler))'); + expect(tree.readText(multiplePresenceFile)).toContain('ruleEngine.unregisterActionHandlers(inject(ConfigurationRulesEngineActionHandler))'); + }); +}); diff --git a/packages/@o3r/rules-engine/schematics/ng-update/v11.6/use-register-action-handlers.ts b/packages/@o3r/rules-engine/schematics/ng-update/v11.6/use-register-action-handlers.ts new file mode 100644 index 0000000000..2c17f01c2d --- /dev/null +++ b/packages/@o3r/rules-engine/schematics/ng-update/v11.6/use-register-action-handlers.ts @@ -0,0 +1,20 @@ +import type { Rule, Tree } from '@angular-devkit/schematics'; +import { findFilesInTree } from '@o3r/schematics'; + +/** + * Replace `actionHandlers.add` or `actionHandlers.delete` by `registerActionHandlers` or `unregisterActionHandlers` in file + * @param tree + */ +export const useRegisterActionHandlers: Rule = (tree: Tree) => { + const files = findFilesInTree(tree.root, (file) => /\.actionHandlers\.(add|delete)/.test(tree.readText(file))); + files.forEach(({ content, path }) => { + tree.overwrite( + path, + content + .toString() + .replaceAll(/\.actionHandlers\.add/g, '.registerActionHandlers') + .replaceAll(/\.actionHandlers\.delete/g, '.unregisterActionHandlers') + ); + }); + return tree; +}; diff --git a/packages/@o3r/rules-engine/src/services/runner/rules-engine.runner.service.ts b/packages/@o3r/rules-engine/src/services/runner/rules-engine.runner.service.ts index d0412b2d93..ec3aa7dea6 100644 --- a/packages/@o3r/rules-engine/src/services/runner/rules-engine.runner.service.ts +++ b/packages/@o3r/rules-engine/src/services/runner/rules-engine.runner.service.ts @@ -35,7 +35,10 @@ export class RulesEngineRunnerService implements OnDestroy { /** Enable action execution on new state change */ public enabled: boolean; - /** List of action handlers */ + /** + * List of action handlers + * @deprecated will become protected in Otter v13, instead use {@link registerActionHandlers} + */ public readonly actionHandlers = new Set(); constructor( @@ -119,7 +122,7 @@ export class RulesEngineRunnerService implements OnDestroy { } /** - * Update or insert fact in rules engine + * Update or insert fact in the rules engine * @param facts fact list to add / update */ public upsertFacts(facts: Fact | Fact[]) { @@ -127,7 +130,7 @@ export class RulesEngineRunnerService implements OnDestroy { } /** - * Update or insert operator in rules engine + * Update or insert operator in the rules engine * @param operators operator list to add / update */ public upsertOperators(operators: (Operator | UnaryOperator)[]) { @@ -135,13 +138,29 @@ export class RulesEngineRunnerService implements OnDestroy { } /** - * Upsert a list of RuleSets to be run in the engine + * Upsert a list of RuleSets to be run in the rules engine * @param ruleSets */ public upsertRulesets(ruleSets: Ruleset[]) { this.store.dispatch(setRulesetsEntities({entities: ruleSets})); } + /** + * Add action handlers in the rules engine + * @param actionHandlers + */ + public registerActionHandlers(...actionHandlers: RulesEngineActionHandler[]) { + actionHandlers.forEach((actionHandler) => this.actionHandlers.add(actionHandler)); + } + + /** + * Remove action handlers in the rules engine + * @param actionHandlers + */ + public unregisterActionHandlers(...actionHandlers: RulesEngineActionHandler[]) { + actionHandlers.forEach((actionHandler) => this.actionHandlers.delete(actionHandler)); + } + /** @inheritdoc */ public ngOnDestroy(): void { this.subscription.unsubscribe();