extensibility improvements (#4484)

* add missing interfaces to extensions library

* separate rule service

* api enhancements

* fix test

* improve APIs
This commit is contained in:
Denys Vuika
2019-03-25 12:17:40 +00:00
committed by Eugenio Romano
parent 80aaaef65d
commit 26c5982a1a
13 changed files with 235 additions and 49 deletions

View File

@@ -0,0 +1,29 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ExtensionElement } from './extension-element';
export interface DocumentListPresetRef extends ExtensionElement {
key: string;
type: string; // text|image|date
title?: string;
format?: string;
class?: string;
sortable: boolean;
template: string;
desktopOnly: boolean;
}

View File

@@ -0,0 +1,22 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ExtensionElement } from './extension-element';
export interface IconRef extends ExtensionElement {
value: string;
}

View File

@@ -19,10 +19,13 @@ import { SelectionState } from '../store/states/selection.state';
import { NavigationState } from '../store/states/navigation.state';
import { NodePermissions } from './permission.extensions';
import { ProfileState } from '../store/states/profile.state';
import { RepositoryInfo } from '@alfresco/js-api';
export type RuleEvaluator = (context: RuleContext, ...args: any[]) => boolean;
export interface RuleContext {
repository: RepositoryInfo;
auth: any;
selection: SelectionState;
navigation: NavigationState;
profile: ProfileState;

View File

@@ -20,4 +20,9 @@ import { ExtensionElement } from './extension-element';
export interface ViewerExtensionRef extends ExtensionElement {
fileExtension: string;
component: string;
rules?: {
visible?: string;
[key: string]: string;
};
}

View File

@@ -19,6 +19,7 @@ import { Injectable } from '@angular/core';
import { ExtensionConfig, ExtensionRef } from '../config/extension.config';
import { ExtensionService } from '../services/extension.service';
import { Observable, BehaviorSubject } from 'rxjs';
import { ViewerExtensionRef } from '../config/viewer.extensions';
@Injectable({
providedIn: 'root'
@@ -47,4 +48,28 @@ export class AppExtensionService {
.map((entry) => <ExtensionRef> entry);
this._references.next(references);
}
/**
* Provides a list of the Viewer content extensions,
* filtered by disabled state and rules.
*/
getViewerExtensions(): ViewerExtensionRef[] {
return this.extensionService
.getElements<ViewerExtensionRef>('features.viewer.content')
.filter((extension) => !this.isViewerExtensionDisabled(extension));
}
protected isViewerExtensionDisabled(extension: ViewerExtensionRef): boolean {
if (extension) {
if (extension.disabled) {
return true;
}
if (extension.rules && extension.rules.disabled) {
return this.extensionService.evaluateRule(extension.rules.disabled);
}
}
return false;
}
}

View File

@@ -105,6 +105,12 @@ export class ExtensionLoaderService {
});
}
/**
* Retrieves configuration elements.
* Filters element by **enabled** and **order** attributes.
* Example:
* `getElements<ViewerExtensionRef>(config, 'features.viewer.content')`
*/
getElements<T extends ExtensionElement>(
config: ExtensionConfig,
key: string,

View File

@@ -22,6 +22,7 @@ import { RuleRef } from '../config/rule.extensions';
import { RouteRef } from '../config/routing.extensions';
import { ActionRef } from '../config/action.extensions';
import { ComponentRegisterService } from './component-register.service';
import { RuleService } from './rule.service';
describe('ExtensionService', () => {
const blankConfig: ExtensionConfig = {
@@ -36,11 +37,13 @@ describe('ExtensionService', () => {
let loader: ExtensionLoaderService;
let componentRegister: ComponentRegisterService;
let service: ExtensionService;
let ruleService: RuleService;
beforeEach(() => {
loader = new ExtensionLoaderService(null);
componentRegister = new ComponentRegisterService();
service = new ExtensionService(loader, componentRegister);
ruleService = new RuleService(loader);
service = new ExtensionService(loader, componentRegister, ruleService);
});
it('should load and setup a config', async () => {

View File

@@ -16,32 +16,35 @@
*/
import { Injectable, Type } from '@angular/core';
import { RuleEvaluator, RuleRef, RuleContext, RuleParameter } from '../config/rule.extensions';
import { RuleEvaluator, RuleRef, RuleContext } from '../config/rule.extensions';
import { ExtensionConfig } from '../config/extension.config';
import { ExtensionLoaderService } from './extension-loader.service';
import { RouteRef } from '../config/routing.extensions';
import { ActionRef } from '../config/action.extensions';
import * as core from '../evaluators/core.evaluators';
import { ComponentRegisterService } from './component-register.service';
import { RuleService } from './rule.service';
import { ExtensionElement } from '../config/extension-element';
@Injectable({
providedIn: 'root'
})
export class ExtensionService {
protected config: ExtensionConfig = null;
configPath = 'assets/app.extensions.json';
pluginsPath = 'assets/plugins';
rules: Array<RuleRef> = [];
routes: Array<RouteRef> = [];
actions: Array<ActionRef> = [];
features: Array<any> = [];
authGuards: { [key: string]: Type<{}> } = {};
evaluators: { [key: string]: RuleEvaluator } = {};
constructor(
private loader: ExtensionLoaderService,
private componentRegister: ComponentRegisterService
protected loader: ExtensionLoaderService,
protected componentRegister: ComponentRegisterService,
protected ruleService: RuleService
) {
}
@@ -68,16 +71,19 @@ export class ExtensionService {
return;
}
this.config = config;
this.setEvaluators({
'core.every': core.every,
'core.some': core.some,
'core.not': core.not
});
this.rules = this.loader.getRules(config);
this.actions = this.loader.getActions(config);
this.routes = this.loader.getRoutes(config);
this.features = this.loader.getFeatures(config);
this.ruleService.setup(config);
}
/**
@@ -90,14 +96,16 @@ export class ExtensionService {
return properties.reduce((prev, curr) => prev && prev[curr], this.features) || [];
}
getElements<T extends ExtensionElement>(key: string, fallback: Array<T> = []): Array<T> {
return this.loader.getElements(this.config, key, fallback);
}
/**
* Adds one or more new rule evaluators to the existing set.
* @param values The new evaluators to add
*/
setEvaluators(values: { [key: string]: RuleEvaluator }) {
if (values) {
this.evaluators = Object.assign({}, this.evaluators, values);
}
this.ruleService.setEvaluators(values);
}
/**
@@ -153,36 +161,17 @@ export class ExtensionService {
* @returns RuleEvaluator or null if not found
*/
getEvaluator(key: string): RuleEvaluator {
if (key && key.startsWith('!')) {
const fn = this.evaluators[key.substring(1)];
return (context: RuleContext, ...args: RuleParameter[]): boolean => {
return !fn(context, ...args);
};
}
return this.evaluators[key];
return this.ruleService.getEvaluator(key);
}
/**
* Evaluates a rule.
* @param ruleId ID of the rule to evaluate
* @param context Parameter object for the evaluator with details of app state
* @param context (optional) Custom rule execution context.
* @returns True if the rule passed, false otherwise
*/
evaluateRule(ruleId: string, context: RuleContext): boolean {
const ruleRef = this.getRuleById(ruleId);
if (ruleRef) {
const evaluator = this.getEvaluator(ruleRef.type);
if (evaluator) {
return evaluator(context, ...ruleRef.parameters);
}
} else {
const evaluator = this.getEvaluator(ruleId);
if (evaluator) {
return evaluator(context);
}
}
return false;
evaluateRule(ruleId: string, context?: RuleContext): boolean {
return this.ruleService.evaluateRule(ruleId, context);
}
/**
@@ -200,7 +189,7 @@ export class ExtensionService {
* @returns The rule or null if not found
*/
getRuleById(id: string): RuleRef {
return this.rules.find((ref) => ref.id === id);
return this.ruleService.getRuleById(id);
}
/**

View File

@@ -0,0 +1,94 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { RuleRef, RuleContext, RuleEvaluator, RuleParameter } from '../config/rule.extensions';
import { ExtensionConfig } from '../config/extension.config';
import { ExtensionLoaderService } from './extension-loader.service';
@Injectable({
providedIn: 'root'
})
export class RuleService {
context: RuleContext = null;
rules: Array<RuleRef> = [];
evaluators: { [key: string]: RuleEvaluator } = {};
constructor(protected loader: ExtensionLoaderService) {}
setup(config: ExtensionConfig) {
this.rules = this.loader.getRules(config);
}
/**
* Adds one or more new rule evaluators to the existing set.
* @param values The new evaluators to add
*/
setEvaluators(values: { [key: string]: RuleEvaluator }) {
if (values) {
this.evaluators = Object.assign({}, this.evaluators, values);
}
}
/**
* Retrieves a rule using its ID value.
* @param id The ID value to look for
* @returns The rule or null if not found
*/
getRuleById(id: string): RuleRef {
return this.rules.find((ref) => ref.id === id);
}
/**
* Retrieves a RuleEvaluator function using its key name.
* @param key Key name to look for
* @returns RuleEvaluator or null if not found
*/
getEvaluator(key: string): RuleEvaluator {
if (key && key.startsWith('!')) {
const fn = this.evaluators[key.substring(1)];
return (context: RuleContext, ...args: RuleParameter[]): boolean => {
return !fn(context, ...args);
};
}
return this.evaluators[key];
}
/**
* Evaluates a rule.
* @param ruleId ID of the rule to evaluate
* @param context (optional) Custom rule execution context.
* @returns True if the rule passed, false otherwise
*/
evaluateRule(ruleId: string, context?: RuleContext): boolean {
const ruleRef = this.getRuleById(ruleId);
context = context || this.context;
if (ruleRef) {
const evaluator = this.getEvaluator(ruleRef.type);
if (evaluator) {
return evaluator(context, ...ruleRef.parameters);
}
} else {
const evaluator = this.getEvaluator(ruleId);
if (evaluator) {
return evaluator(context);
}
}
return false;
}
}

View File

@@ -18,9 +18,11 @@
export * from './lib/extensions.module';
export * from './lib/config/action.extensions';
export * from './lib/config/document-list.extensions';
export * from './lib/config/extension-element';
export * from './lib/config/extension-utils';
export * from './lib/config/extension.config';
export * from './lib/config/icon.extensions';
export * from './lib/config/navbar.extensions';
export * from './lib/config/permission.extensions';
export * from './lib/config/routing.extensions';