mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-31 17:38:28 +00:00
extensibility: rules engine (#511)
* rules format prototype * config container * lightweight rules * fdescribe * basic rule integration * migrate "create folder" to click actions * migrate toolbar to new action handlers * rule support for "create folder" (toolbar) * upgrade "View" toolbar command * migrate to rules * cleanup tests
This commit is contained in:
committed by
Cilibiu Bogdan
parent
d5763f585d
commit
51af2071c2
@@ -42,6 +42,28 @@
|
||||
"plugin2.json"
|
||||
],
|
||||
"core": {
|
||||
"rules": [
|
||||
{
|
||||
"id": "app.create.canCreateFolder",
|
||||
"type": "app.navigation.folder.canCreate"
|
||||
},
|
||||
{
|
||||
"id": "app.toolbar.canEditFolder",
|
||||
"type": "core.every",
|
||||
"parameters": [
|
||||
{ "type": "rule", "value": "app.selection.folder" },
|
||||
{ "type": "rule", "value": "app.selection.folder.canUpdate" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "app.toolbar.canViewFile",
|
||||
"type": "app.selection.file"
|
||||
},
|
||||
{
|
||||
"id": "app.toolbar.canDownload",
|
||||
"type": "app.selection.canDownload"
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"id": "aca:routes/about",
|
||||
@@ -81,11 +103,6 @@
|
||||
"type": "SNACKBAR_INFO",
|
||||
"payload": "I'm a nice little popup raised by extension."
|
||||
},
|
||||
{
|
||||
"id": "aca:actions/error",
|
||||
"type": "SNACKBAR_ERROR",
|
||||
"payload": "Aw, Snap!"
|
||||
},
|
||||
{
|
||||
"id": "aca:actions/node-name",
|
||||
"type": "SNACKBAR_INFO",
|
||||
@@ -100,14 +117,14 @@
|
||||
"features": {
|
||||
"create": [
|
||||
{
|
||||
"disabled": false,
|
||||
"id": "aca:create/folder",
|
||||
"order": 100,
|
||||
"id": "app.create.folder",
|
||||
"icon": "create_new_folder",
|
||||
"title": "ext: Create Folder",
|
||||
"target": {
|
||||
"permissions": ["create"],
|
||||
"action": "aca:actions/create-folder"
|
||||
"actions": {
|
||||
"click": "aca:actions/create-folder"
|
||||
},
|
||||
"rules": {
|
||||
"enabled": "app.create.canCreateFolder"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -210,10 +227,11 @@
|
||||
"order": 10,
|
||||
"title": "APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER",
|
||||
"icon": "create_new_folder",
|
||||
"target": {
|
||||
"types": [],
|
||||
"permissions": ["parent.create"],
|
||||
"action": "aca:actions/create-folder"
|
||||
"actions": {
|
||||
"click": "aca:actions/create-folder"
|
||||
},
|
||||
"rules": {
|
||||
"visible": "app.create.canCreateFolder"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -222,10 +240,11 @@
|
||||
"order": 15,
|
||||
"title": "APP.ACTIONS.VIEW",
|
||||
"icon": "open_in_browser",
|
||||
"target": {
|
||||
"types": ["file"],
|
||||
"permissions": [],
|
||||
"action": "aca:actions/preview"
|
||||
"actions": {
|
||||
"click": "aca:actions/preview"
|
||||
},
|
||||
"rules": {
|
||||
"visible": "app.toolbar.canViewFile"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -234,11 +253,11 @@
|
||||
"order": 20,
|
||||
"title": "APP.ACTIONS.DOWNLOAD",
|
||||
"icon": "get_app",
|
||||
"target": {
|
||||
"types": ["file", "folder"],
|
||||
"permissions": [],
|
||||
"action": "aca:actions/download",
|
||||
"multiple": true
|
||||
"actions": {
|
||||
"click": "aca:actions/download"
|
||||
},
|
||||
"rules": {
|
||||
"visible": "app.toolbar.canDownload"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -247,10 +266,11 @@
|
||||
"order": 30,
|
||||
"title": "APP.ACTIONS.EDIT",
|
||||
"icon": "create",
|
||||
"target": {
|
||||
"types": ["folder"],
|
||||
"permissions": ["update"],
|
||||
"action": "aca:actions/edit-folder"
|
||||
"actions": {
|
||||
"click": "aca:actions/edit-folder"
|
||||
},
|
||||
"rules": {
|
||||
"visible": "app.toolbar.canEditFolder"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -270,21 +290,8 @@
|
||||
"type": "button",
|
||||
"title": "Settings",
|
||||
"icon": "settings_applications",
|
||||
"target": {
|
||||
"types": [],
|
||||
"permissions": [],
|
||||
"action": "aca:actions/settings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "aca:action4",
|
||||
"type": "button",
|
||||
"title": "Error",
|
||||
"icon": "report_problem",
|
||||
"target": {
|
||||
"types": ["file"],
|
||||
"permissions": ["update", "delete"],
|
||||
"action": "aca:actions/error"
|
||||
"actions": {
|
||||
"click": "aca:actions/settings"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@@ -85,6 +85,7 @@ import { SearchResultsRowComponent } from './components/search/search-results-ro
|
||||
import { NodePermissionsDialogComponent } from './dialogs/node-permissions/node-permissions.dialog';
|
||||
import { NodePermissionsDirective } from './common/directives/node-permissions.directive';
|
||||
import { PermissionsManagerComponent } from './components/permission-manager/permissions-manager.component';
|
||||
import { RuleService } from './extensions/rules/rule.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -161,7 +162,8 @@ import { PermissionsManagerComponent } from './components/permission-manager/per
|
||||
ProfileResolver,
|
||||
ExperimentalGuard,
|
||||
ContentApiService,
|
||||
ExtensionService
|
||||
ExtensionService,
|
||||
RuleService
|
||||
],
|
||||
entryComponents: [
|
||||
LibraryDialogComponent,
|
||||
|
@@ -83,8 +83,7 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
||||
if (selection.isEmpty) {
|
||||
this.infoDrawerOpened = false;
|
||||
}
|
||||
const selectedNodes = selection ? selection.nodes : null;
|
||||
this.actions = this.extensions.getAllowedContentActions(selectedNodes, this.node);
|
||||
this.actions = this.extensions.getAllowedContentActions();
|
||||
this.canUpdateFile = this.selection.file && this.content.canUpdateNode(selection.file);
|
||||
this.canUpdateNode = this.selection.count === 1 && this.content.canUpdateNode(selection.first);
|
||||
this.canDelete = !this.selection.isEmpty && this.content.canDeleteNodes(selection.nodes);
|
||||
|
@@ -10,7 +10,7 @@
|
||||
<button *ngFor="let entry of createActions"
|
||||
mat-menu-item
|
||||
[disabled]="entry.disabled"
|
||||
(click)="runAction(entry.target.action)">
|
||||
(click)="runAction(entry.actions.click)">
|
||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||
<span>{{ entry.title | translate }}</span>
|
||||
</button>
|
||||
|
@@ -3,7 +3,7 @@
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
title="{{ entry.title | translate }}"
|
||||
(click)="runAction(entry.target.action)">
|
||||
(click)="runAction(entry.actions.click)">
|
||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||
</button>
|
||||
<adf-toolbar-divider *ngSwitchCase="'separator'"></adf-toolbar-divider>
|
||||
@@ -20,7 +20,7 @@
|
||||
[overlapTrigger]="false">
|
||||
<ng-container *ngFor="let child of entry.children">
|
||||
<button mat-menu-item
|
||||
(click)="runAction(child.target.action)">
|
||||
(click)="runAction(child.actions.click)">
|
||||
<mat-icon>{{ child.icon }}</mat-icon>
|
||||
<span>{{ child.title | translate }}</span>
|
||||
</button>
|
||||
|
@@ -38,10 +38,13 @@ export interface ContentActionExtension {
|
||||
icon?: string;
|
||||
disabled?: boolean;
|
||||
children?: Array<ContentActionExtension>;
|
||||
target: {
|
||||
types: Array<string>;
|
||||
permissions: Array<string>,
|
||||
action: string;
|
||||
multiple?: boolean;
|
||||
actions?: {
|
||||
click?: string;
|
||||
[key: string]: string;
|
||||
};
|
||||
rules: {
|
||||
enabled?: string;
|
||||
visible?: string;
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
35
src/app/extensions/extension.config.ts
Normal file
35
src/app/extensions/extension.config.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { RouteExtension } from './route.extension';
|
||||
import { ActionExtension } from './action.extension';
|
||||
import { RuleRef } from './rules/rule-ref';
|
||||
|
||||
export interface ExtensionConfig {
|
||||
rules?: Array<RuleRef>;
|
||||
routes?: Array<RouteExtension>;
|
||||
actions?: Array<ActionExtension>;
|
||||
features?: { [key: string]: any };
|
||||
}
|
@@ -491,63 +491,6 @@ describe('ExtensionService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('permissions', () => {
|
||||
it('should approve node permission', () => {
|
||||
const node: any = {
|
||||
allowableOperations: ['create']
|
||||
};
|
||||
|
||||
expect(extensions.nodeHasPermission(node, 'create')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not approve node permission', () => {
|
||||
const node: any = {
|
||||
allowableOperations: ['create']
|
||||
};
|
||||
|
||||
expect(extensions.nodeHasPermission(node, 'update')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not approve node permission when node missing property', () => {
|
||||
const node: any = {
|
||||
allowableOperations: null
|
||||
};
|
||||
|
||||
expect(extensions.nodeHasPermission(node, 'update')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should require node to check permission', () => {
|
||||
expect(extensions.nodeHasPermission(null, 'create')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should require permission value to check', () => {
|
||||
const node: any = {
|
||||
allowableOperations: ['create']
|
||||
};
|
||||
expect(extensions.nodeHasPermission(node, null)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should approve multiple permissions', () => {
|
||||
const node: any = {
|
||||
allowableOperations: ['create', 'update', 'delete']
|
||||
};
|
||||
expect(
|
||||
extensions.nodeHasPermissions(node, ['create', 'delete'])
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should require node to check multiple permissions', () => {
|
||||
expect(extensions.nodeHasPermissions(null, ['create'])).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should require multiple permissions to check', () => {
|
||||
const node: any = {
|
||||
allowableOperations: ['create', 'update', 'delete']
|
||||
};
|
||||
expect(extensions.nodeHasPermissions(node, null)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('sorting', () => {
|
||||
it('should sort by provided order', () => {
|
||||
const sorted = [
|
||||
|
@@ -36,7 +36,8 @@ import { AppStore } from '../store/states';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { NavigationExtension } from './navigation.extension';
|
||||
import { Route } from '@angular/router';
|
||||
import { Node, MinimalNodeEntity } from 'alfresco-js-api';
|
||||
import { Node } from 'alfresco-js-api';
|
||||
import { RuleService } from './rules/rule.service';
|
||||
|
||||
@Injectable()
|
||||
export class ExtensionService {
|
||||
@@ -52,7 +53,8 @@ export class ExtensionService {
|
||||
|
||||
constructor(
|
||||
private config: AppConfigService,
|
||||
private store: Store<AppStore>
|
||||
private store: Store<AppStore>,
|
||||
private ruleService: RuleService
|
||||
) {}
|
||||
|
||||
// initialise extension service
|
||||
@@ -89,6 +91,8 @@ export class ExtensionService {
|
||||
[]
|
||||
)
|
||||
.sort(this.sortByOrder);
|
||||
|
||||
this.ruleService.init();
|
||||
}
|
||||
|
||||
getRouteById(id: string): RouteExtension {
|
||||
@@ -183,38 +187,28 @@ export class ExtensionService {
|
||||
|
||||
// evaluates create actions for the folder node
|
||||
getFolderCreateActions(folder: Node): Array<ContentActionExtension> {
|
||||
return this.createActions.filter(this.filterEnabled).map(action => {
|
||||
if (
|
||||
action.target &&
|
||||
action.target.permissions &&
|
||||
action.target.permissions.length > 0
|
||||
) {
|
||||
return this.createActions
|
||||
.filter(this.filterEnabled)
|
||||
.filter(action => this.filterByRules(action))
|
||||
.map(action => {
|
||||
let disabled = false;
|
||||
|
||||
if (action.rules && action.rules.enabled) {
|
||||
disabled = !this.ruleService.evaluateRule(action.rules.enabled);
|
||||
}
|
||||
|
||||
return {
|
||||
...action,
|
||||
disabled: !this.nodeHasPermissions(
|
||||
folder,
|
||||
action.target.permissions
|
||||
),
|
||||
target: {
|
||||
...action.target
|
||||
}
|
||||
disabled
|
||||
};
|
||||
}
|
||||
return action;
|
||||
});
|
||||
}
|
||||
|
||||
// evaluates content actions for the selection and parent folder node
|
||||
getAllowedContentActions(
|
||||
nodes: MinimalNodeEntity[],
|
||||
parentNode: Node
|
||||
): Array<ContentActionExtension> {
|
||||
getAllowedContentActions(): Array<ContentActionExtension> {
|
||||
return this.contentActions
|
||||
.filter(this.filterEnabled)
|
||||
.filter(action => this.filterByTarget(nodes, action))
|
||||
.filter(action =>
|
||||
this.filterByPermission(nodes, action, parentNode)
|
||||
)
|
||||
.filter(action => this.filterByRules(action))
|
||||
.reduce(this.reduceSeparators, [])
|
||||
.map(action => {
|
||||
if (action.type === ContentActionType.menu) {
|
||||
@@ -222,14 +216,7 @@ export class ExtensionService {
|
||||
if (copy.children && copy.children.length > 0) {
|
||||
copy.children = copy.children
|
||||
.filter(childAction =>
|
||||
this.filterByTarget(nodes, childAction)
|
||||
)
|
||||
.filter(childAction =>
|
||||
this.filterByPermission(
|
||||
nodes,
|
||||
childAction,
|
||||
parentNode
|
||||
)
|
||||
this.filterByRules(childAction)
|
||||
)
|
||||
.reduce(this.reduceSeparators, []);
|
||||
}
|
||||
@@ -301,108 +288,10 @@ export class ExtensionService {
|
||||
};
|
||||
}
|
||||
|
||||
filterByTarget(
|
||||
nodes: MinimalNodeEntity[],
|
||||
action: ContentActionExtension
|
||||
): boolean {
|
||||
if (!action) {
|
||||
return false;
|
||||
filterByRules(action: ContentActionExtension): boolean {
|
||||
if (action && action.rules && action.rules.visible) {
|
||||
return this.ruleService.evaluateRule(action.rules.visible);
|
||||
}
|
||||
|
||||
if (!action.target) {
|
||||
return (
|
||||
action.type === ContentActionType.separator ||
|
||||
action.type === ContentActionType.menu
|
||||
);
|
||||
}
|
||||
|
||||
const types = action.target.types || [];
|
||||
|
||||
if (types.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nodes && nodes.length > 0) {
|
||||
return types.some(type => {
|
||||
if (type === 'folder') {
|
||||
return action.target.multiple
|
||||
? nodes.some(node => node.entry.isFolder)
|
||||
: nodes.length === 1 &&
|
||||
nodes.every(node => node.entry.isFolder);
|
||||
}
|
||||
if (type === 'file') {
|
||||
return action.target.multiple
|
||||
? nodes.some(node => node.entry.isFile)
|
||||
: nodes.length === 1 &&
|
||||
nodes.every(node => node.entry.isFile);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
filterByPermission(
|
||||
nodes: MinimalNodeEntity[],
|
||||
action: ContentActionExtension,
|
||||
parentNode: Node
|
||||
): boolean {
|
||||
if (!action) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!action.target) {
|
||||
return (
|
||||
action.type === ContentActionType.separator ||
|
||||
action.type === ContentActionType.menu
|
||||
);
|
||||
}
|
||||
|
||||
const permissions = action.target.permissions || [];
|
||||
|
||||
if (permissions.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return permissions.some(permission => {
|
||||
if (permission.startsWith('parent.')) {
|
||||
if (parentNode) {
|
||||
const parentQuery = permission.split('.')[1];
|
||||
return this.nodeHasPermission(parentNode, parentQuery);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nodes && nodes.length > 0) {
|
||||
return action.target.multiple
|
||||
? nodes.some(node =>
|
||||
this.nodeHasPermission(node.entry, permission)
|
||||
)
|
||||
: nodes.length === 1 &&
|
||||
nodes.every(node =>
|
||||
this.nodeHasPermission(node.entry, permission)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
nodeHasPermissions(node: Node, permissions: string[]): boolean {
|
||||
if (node && permissions && permissions.length > 0) {
|
||||
return permissions.some(permission =>
|
||||
this.nodeHasPermission(node, permission)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
nodeHasPermission(node: Node, permission: string): boolean {
|
||||
if (node && permission) {
|
||||
const allowableOperations = node.allowableOperations || [];
|
||||
return allowableOperations.includes(permission);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
72
src/app/extensions/rules/app.evaluators.ts
Normal file
72
src/app/extensions/rules/app.evaluators.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { RuleContext } from './rule-context';
|
||||
import { RuleParameter } from './rule-parameter';
|
||||
import { Node } from 'alfresco-js-api';
|
||||
|
||||
export function canCreateFolder(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||
const folder = context.navigation.currentFolder;
|
||||
if (folder) {
|
||||
return nodeHasPermission(folder, 'create');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function canDownloadSelection(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||
if (!context.selection.isEmpty) {
|
||||
return context.selection.nodes.every(node => {
|
||||
return node.entry && (node.entry.isFile || node.entry.isFolder);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
export function hasFolderSelected(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||
const folder = context.selection.folder;
|
||||
return folder ? true : false;
|
||||
}
|
||||
|
||||
export function hasFileSelected(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||
const file = context.selection.file;
|
||||
return file ? true : false;
|
||||
}
|
||||
|
||||
export function canUpdateSelectedFolder(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||
const folder = context.selection.folder;
|
||||
if (folder && folder.entry) {
|
||||
return nodeHasPermission(folder.entry, 'update');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function nodeHasPermission(node: Node, permission: string): boolean {
|
||||
if (node && permission) {
|
||||
const allowableOperations = node.allowableOperations || [];
|
||||
return allowableOperations.includes(permission);
|
||||
}
|
||||
return false;
|
||||
}
|
47
src/app/extensions/rules/core.evaluators.ts
Normal file
47
src/app/extensions/rules/core.evaluators.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { RuleContext } from './rule-context';
|
||||
import { RuleParameter } from './rule-parameter';
|
||||
|
||||
export function every(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||
if (!args || args.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return args
|
||||
.map(arg => context.evaluators[arg.value])
|
||||
.every(evaluator => evaluator(context));
|
||||
}
|
||||
|
||||
export function some(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||
if (!args || args.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return args
|
||||
.map(arg => context.evaluators[arg.value])
|
||||
.some(evaluator => evaluator(context));
|
||||
}
|
34
src/app/extensions/rules/rule-context.ts
Normal file
34
src/app/extensions/rules/rule-context.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { SelectionState } from '../../store/states';
|
||||
import { RuleEvaluator } from './rule.service';
|
||||
import { NavigationState } from '../../store/states/navigation.state';
|
||||
|
||||
export interface RuleContext {
|
||||
selection: SelectionState;
|
||||
navigation: NavigationState;
|
||||
evaluators: { [key: string]: RuleEvaluator };
|
||||
}
|
29
src/app/extensions/rules/rule-parameter.ts
Normal file
29
src/app/extensions/rules/rule-parameter.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export interface RuleParameter {
|
||||
type: string;
|
||||
value: any;
|
||||
}
|
34
src/app/extensions/rules/rule-ref.ts
Normal file
34
src/app/extensions/rules/rule-ref.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { RuleParameter } from './rule-parameter';
|
||||
import { RuleEvaluator } from './rule.service';
|
||||
|
||||
export class RuleRef {
|
||||
type: string;
|
||||
id?: string;
|
||||
parameters?: Array<RuleParameter>;
|
||||
evaluator?: RuleEvaluator;
|
||||
}
|
97
src/app/extensions/rules/rule.service.ts
Normal file
97
src/app/extensions/rules/rule.service.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AppConfigService } from '@alfresco/adf-core';
|
||||
import { every, some } from './core.evaluators';
|
||||
import { RuleContext } from './rule-context';
|
||||
import { RuleRef } from './rule-ref';
|
||||
import { createSelector, Store } from '@ngrx/store';
|
||||
import {
|
||||
appSelection,
|
||||
appNavigation
|
||||
} from '../../store/selectors/app.selectors';
|
||||
import { AppStore, SelectionState } from '../../store/states';
|
||||
import { NavigationState } from '../../store/states/navigation.state';
|
||||
import { canCreateFolder, hasFolderSelected, canUpdateSelectedFolder, hasFileSelected, canDownloadSelection } from './app.evaluators';
|
||||
|
||||
export type RuleEvaluator = (context: RuleContext, ...args: any[]) => boolean;
|
||||
|
||||
export const selectionWithFolder = createSelector(
|
||||
appSelection,
|
||||
appNavigation,
|
||||
(selection, navigation) => {
|
||||
return {
|
||||
selection,
|
||||
navigation
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@Injectable()
|
||||
export class RuleService implements RuleContext {
|
||||
rules: Array<RuleRef> = [];
|
||||
evaluators: { [key: string]: RuleEvaluator } = {};
|
||||
selection: SelectionState;
|
||||
navigation: NavigationState;
|
||||
|
||||
constructor(
|
||||
private config: AppConfigService,
|
||||
private store: Store<AppStore>
|
||||
) {
|
||||
this.evaluators['core.every'] = every;
|
||||
this.evaluators['core.some'] = some;
|
||||
this.evaluators['app.selection.canDownload'] = canDownloadSelection;
|
||||
this.evaluators['app.selection.file'] = hasFileSelected;
|
||||
this.evaluators['app.selection.folder'] = hasFolderSelected;
|
||||
this.evaluators['app.selection.folder.canUpdate'] = canUpdateSelectedFolder;
|
||||
this.evaluators['app.navigation.folder.canCreate'] = canCreateFolder;
|
||||
|
||||
this.store
|
||||
.select(selectionWithFolder)
|
||||
.subscribe(result => {
|
||||
this.selection = result.selection;
|
||||
this.navigation = result.navigation;
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
this.rules = this.config
|
||||
.get<Array<RuleRef>>('extensions.core.rules', [])
|
||||
.map(rule => {
|
||||
rule.evaluator = this.evaluators[rule.type];
|
||||
return rule;
|
||||
});
|
||||
}
|
||||
|
||||
evaluateRule(ruleId: string): boolean {
|
||||
const ruleRef = this.rules.find(ref => ref.id === ruleId);
|
||||
|
||||
if (ruleRef.evaluator) {
|
||||
return ruleRef.evaluator(this, ...ruleRef.parameters);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -34,4 +34,5 @@ export const appSelection = createSelector(selectApp, state => state.selection)
|
||||
export const appLanguagePicker = createSelector(selectApp, state => state.languagePicker);
|
||||
export const selectUser = createSelector(selectApp, state => state.user);
|
||||
export const sharedUrl = createSelector(selectApp, state => state.sharedUrl);
|
||||
export const appNavigation = createSelector(selectApp, state => state.navigation);
|
||||
export const currentFolder = createSelector(selectApp, state => state.navigation.currentFolder);
|
||||
|
@@ -60,6 +60,7 @@ import { NodeActionsService } from '../common/services/node-actions.service';
|
||||
import { NodePermissionService } from '../common/services/node-permission.service';
|
||||
import { ContentApiService } from '../services/content-api.service';
|
||||
import { ExtensionService } from '../extensions/extension.service';
|
||||
import { RuleService } from '../extensions/rules/rule.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -112,7 +113,8 @@ import { ExtensionService } from '../extensions/extension.service';
|
||||
NodeActionsService,
|
||||
NodePermissionService,
|
||||
ContentApiService,
|
||||
ExtensionService
|
||||
ExtensionService,
|
||||
RuleService
|
||||
]
|
||||
})
|
||||
export class AppTestingModule {}
|
||||
|
Reference in New Issue
Block a user