Skip to content

Commit

Permalink
deprecate(rules-engine): actionHandlers will become protected in v13 (#…
Browse files Browse the repository at this point in the history
…2518)

## Proposed change
It's not a good practice to expose a `Set`.
I added a method `registerActionHandlers` to replace the usage of
`actionHandlers.add(...)`.
It will be future proof if we want to do something else when an action
handler is registered.
  • Loading branch information
matthieu-crouzet authored Nov 27, 2024
2 parents a1182f5 + 7a22714 commit 8dd1971
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 13 deletions.
2 changes: 1 addition & 1 deletion apps/showcase/src/app/placeholder/placeholder.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
8 changes: 5 additions & 3 deletions apps/showcase/src/app/rules-engine/rules-engine.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion docs/rules-engine/how-to-use/custom-action.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions packages/@o3r/rules-engine/migration.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
27 changes: 23 additions & 4 deletions packages/@o3r/rules-engine/schematics/ng-update/index.ts
Original file line number Diff line number Diff line change
@@ -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[] = [
Expand All @@ -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);
Original file line number Diff line number Diff line change
@@ -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))');
});
});
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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<RulesEngineActionHandler>();

constructor(
Expand Down Expand Up @@ -119,29 +122,45 @@ 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<unknown> | Fact<unknown>[]) {
this.engine.upsertFacts(facts);
}

/**
* 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<any, any> | UnaryOperator<any>)[]) {
this.engine.upsertOperators(operators);
}

/**
* 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();
Expand Down

0 comments on commit 8dd1971

Please sign in to comment.