move adf-extensions lib to the ADF repo (#3781)

* adf-extensions lib

* remove console.log calls

* integrate with angular.json

* fix output path

* update scripts

* Add extensions to scripts

* update travis setup

* Travis step II.
This commit is contained in:
Denys Vuika
2018-09-18 10:52:02 +01:00
committed by Eugenio Romano
parent 9a9c3dbfbc
commit 155131370c
40 changed files with 1936 additions and 6 deletions

View File

@@ -0,0 +1,67 @@
/*!
* @license
* Copyright 2016 - 2018 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 {
Component,
Input,
ComponentRef,
OnInit,
ComponentFactoryResolver,
ViewChild,
ViewContainerRef,
OnDestroy
} from '@angular/core';
import { ExtensionService } from '../../services/extension.service';
@Component({
selector: 'adf-dynamic-component',
template: `<div #content></div>`
})
export class DynamicExtensionComponent implements OnInit, OnDestroy {
@ViewChild('content', { read: ViewContainerRef })
content: ViewContainerRef;
@Input() id: string;
private componentRef: ComponentRef<any>;
constructor(
private extensions: ExtensionService,
private componentFactoryResolver: ComponentFactoryResolver
) {}
ngOnInit() {
const componentType = this.extensions.getComponentById(this.id);
if (componentType) {
const factory = this.componentFactoryResolver.resolveComponentFactory(
componentType
);
if (factory) {
this.content.clear();
this.componentRef = this.content.createComponent(factory, 0);
// this.setupWidget(this.componentRef);
}
}
}
ngOnDestroy() {
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
}
}

View File

@@ -0,0 +1,94 @@
/*!
* @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 {
Component,
Input,
OnInit,
OnDestroy,
ViewChild,
ViewContainerRef,
ComponentRef,
ComponentFactoryResolver,
OnChanges,
SimpleChanges
} from '@angular/core';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { ExtensionService } from '../../services/extension.service';
@Component({
selector: 'adf-dynamic-tab',
template: `<div #content></div>`
})
export class DynamicTabComponent implements OnInit, OnChanges, OnDestroy {
@ViewChild('content', { read: ViewContainerRef })
content: ViewContainerRef;
@Input()
id: string;
@Input()
node: MinimalNodeEntryEntity;
private componentRef: ComponentRef<any>;
constructor(
private extensions: ExtensionService,
private componentFactoryResolver: ComponentFactoryResolver
) {}
ngOnInit() {
const componentType = this.extensions.getComponentById(this.id);
if (componentType) {
const factory = this.componentFactoryResolver.resolveComponentFactory(
componentType
);
if (factory) {
this.content.clear();
this.componentRef = this.content.createComponent(factory, 0);
this.updateInstance();
}
}
}
ngOnChanges(changes: SimpleChanges) {
if (changes.node) {
this.updateInstance();
}
}
ngOnDestroy() {
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
}
private updateInstance() {
if (this.componentRef && this.componentRef.instance) {
this.componentRef.instance.node = this.node;
}
}
}

View File

@@ -0,0 +1,51 @@
/*!
* @license
* Copyright 2016 - 2018 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 enum ContentActionType {
default = 'default',
button = 'button',
separator = 'separator',
menu = 'menu',
custom = 'custom'
}
export interface ContentActionRef extends ExtensionElement {
type: ContentActionType;
title?: string;
description?: string;
icon?: string;
children?: Array<ContentActionRef>;
component?: string;
actions?: {
click?: string;
[key: string]: string;
};
rules?: {
enabled?: string;
visible?: string;
[key: string]: string;
};
}
export interface ActionRef {
id: string;
type: string;
payload?: string;
}

View File

@@ -0,0 +1,23 @@
/*!
* @license
* Copyright 2016 - 2018 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.
*/
export interface ExtensionElement {
id: string;
order?: number;
disabled?: boolean;
}

View File

@@ -0,0 +1,150 @@
/*!
* @license
* Copyright 2016 - 2018 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 { ContentActionRef, ContentActionType } from './action.extensions';
export function getValue(target: any, key: string): any {
if (!target) {
return undefined;
}
const keys = key.split('.');
key = '';
do {
key += keys.shift();
const value = target[key];
if (
value !== undefined &&
(typeof value === 'object' || !keys.length)
) {
target = value;
key = '';
} else if (!keys.length) {
target = undefined;
} else {
key += '.';
}
} while (keys.length);
return target;
}
export function filterEnabled(entry: { disabled?: boolean }): boolean {
return !entry.disabled;
}
export function sortByOrder(
a: { order?: number | undefined },
b: { order?: number | undefined }
) {
const left = a.order === undefined ? Number.MAX_SAFE_INTEGER : a.order;
const right = b.order === undefined ? Number.MAX_SAFE_INTEGER : b.order;
return left - right;
}
export function reduceSeparators(
acc: ContentActionRef[],
el: ContentActionRef,
i: number,
arr: ContentActionRef[]
): ContentActionRef[] {
// remove leading separator
if (i === 0) {
if (arr[i].type === ContentActionType.separator) {
return acc;
}
}
// remove duplicate separators
if (i > 0) {
const prev = arr[i - 1];
if (
prev.type === ContentActionType.separator &&
el.type === ContentActionType.separator
) {
return acc;
}
// remove trailing separator
if (i === arr.length - 1) {
if (el.type === ContentActionType.separator) {
return acc;
}
}
}
return acc.concat(el);
}
export function reduceEmptyMenus(
acc: ContentActionRef[],
el: ContentActionRef
): ContentActionRef[] {
if (el.type === ContentActionType.menu) {
if ((el.children || []).length === 0) {
return acc;
}
}
return acc.concat(el);
}
export function mergeObjects(...objects): any {
const result = {};
objects.forEach(source => {
Object.keys(source).forEach(prop => {
if (!prop.startsWith('$')) {
if (prop in result && Array.isArray(result[prop])) {
// result[prop] = result[prop].concat(source[prop]);
result[prop] = mergeArrays(result[prop], source[prop]);
} else if (prop in result && typeof result[prop] === 'object') {
result[prop] = mergeObjects(result[prop], source[prop]);
} else {
result[prop] = source[prop];
}
}
});
});
return result;
}
export function mergeArrays(left: any[], right: any[]): any[] {
const result = [];
const map = {};
(left || []).forEach(entry => {
const element = entry;
if (element && element.hasOwnProperty('id')) {
map[element.id] = element;
} else {
result.push(element);
}
});
(right || []).forEach(entry => {
const element = entry;
if (element && element.hasOwnProperty('id') && map[element.id]) {
const merged = mergeObjects(map[element.id], element);
map[element.id] = merged;
} else {
result.push(element);
}
});
return Object.values(map).concat(result);
}

View File

@@ -0,0 +1,35 @@
/*!
* @license
* Copyright 2016 - 2018 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 { RouteRef } from './routing.extensions';
import { RuleRef } from './rule.extensions';
import { ActionRef } from './action.extensions';
export interface ExtensionConfig {
$name: string;
$version: string;
$description?: string;
$references?: Array<string>;
rules?: Array<RuleRef>;
routes?: Array<RouteRef>;
actions?: Array<ActionRef>;
features?: {
[key: string]: any;
};
}

View File

@@ -0,0 +1,31 @@
/*!
* @license
* Copyright 2016 - 2018 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 NavBarGroupRef extends ExtensionElement {
items: Array<NavBarLinkRef>;
}
export interface NavBarLinkRef extends ExtensionElement {
icon: string;
title: string;
route: string;
url?: string; // evaluated at runtime based on route ref
description?: string;
}

View File

@@ -0,0 +1,20 @@
/*!
* @license
* Copyright 2016 - 2018 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.
*/
export interface NodePermissions {
check(source: any, permissions: string[], options?: any): boolean;
}

View File

@@ -0,0 +1,26 @@
/*!
* @license
* Copyright 2016 - 2018 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.
*/
export interface RouteRef {
id: string;
path: string;
component: string;
layout?: string;
auth?: string[];
data?: { [key: string]: string };
}

View File

@@ -0,0 +1,44 @@
/*!
* @license
* Copyright 2016 - 2018 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 { 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';
export type RuleEvaluator = (context: RuleContext, ...args: any[]) => boolean;
export interface RuleContext {
selection: SelectionState;
navigation: NavigationState;
profile: ProfileState;
permissions: NodePermissions;
getEvaluator(key: string): RuleEvaluator;
}
export class RuleRef {
type: string;
id?: string;
parameters?: Array<RuleParameter>;
}
export interface RuleParameter {
type: string;
value: any;
parameters?: Array<RuleParameter>;
}

View File

@@ -0,0 +1,29 @@
/*!
* @license
* Copyright 2016 - 2018 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 SidebarTabRef extends ExtensionElement {
title: string;
component: string;
icon?: string;
rules?: {
visible?: string;
[key: string]: string;
};
}

View File

@@ -0,0 +1,23 @@
/*!
* @license
* Copyright 2016 - 2018 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 ViewerExtensionRef extends ExtensionElement {
fileExtension: string;
component: string;
}

View File

@@ -0,0 +1,237 @@
/*!
* @license
* Copyright 2016 - 2018 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 { every, not, some } from './core.evaluators';
import { RuleParameter } from '../config/rule.extensions';
describe('Core Evaluators', () => {
const context: any = {
getEvaluator(key: string) {
switch (key) {
case 'positive':
return () => true;
case 'negative':
return () => false;
default:
return null;
}
}
};
describe('not', () => {
it('should evaluate a single rule to [true]', () => {
const parameter: RuleParameter = {
type: 'primitive',
value: 'negative'
};
const result = not(context, parameter);
expect(result).toBeTruthy();
});
it('should evaluate to [false] when no parameters provided', () => {
const result = not(context);
expect(result).toBeFalsy();
});
it('should evaluate to [false] when evaluator not available', () => {
const parameter: RuleParameter = {
type: 'primitive',
value: 'missing'
};
const result = not(context, parameter);
expect(result).toBeFalsy();
});
it('should evaluate a single rule to [false]', () => {
const parameter: RuleParameter = {
type: 'primitive',
value: 'positive'
};
const result = not(context, parameter);
expect(result).toBeFalsy();
});
it('should evaluate multiple rules to [true]', () => {
const parameter1: RuleParameter = {
type: 'primitive',
value: 'negative'
};
const parameter2: RuleParameter = {
type: 'primitive',
value: 'negative'
};
const parameter3: RuleParameter = {
type: 'primitive',
value: 'negative'
};
const result = not(context, parameter1, parameter2, parameter3);
expect(result).toBeTruthy();
});
it('should evaluate to [false] when one of the rules fails', () => {
const parameter1: RuleParameter = {
type: 'primitive',
value: 'negative'
};
const parameter2: RuleParameter = {
type: 'primitive',
value: 'negative'
};
const parameter3: RuleParameter = {
type: 'primitive',
value: 'positive'
};
const result = not(context, parameter1, parameter2, parameter3);
expect(result).toBeFalsy();
});
});
describe('every', () => {
it('should evaluate a single rule to [true]', () => {
const parameter: RuleParameter = {
type: 'primitive',
value: 'positive'
};
const result = every(context, parameter);
expect(result).toBeTruthy();
});
it('should evaluate to [false] when no parameters provided', () => {
const result = every(context);
expect(result).toBeFalsy();
});
it('should evaluate to [false] when evaluator not available', () => {
const parameter: RuleParameter = {
type: 'primitive',
value: 'missing'
};
const result = every(context, parameter);
expect(result).toBeFalsy();
});
it('should evaluate a single rule to [false]', () => {
const parameter: RuleParameter = {
type: 'primitive',
value: 'negative'
};
const result = every(context, parameter);
expect(result).toBeFalsy();
});
it('should evaluate multiple rules to [true]', () => {
const parameter1: RuleParameter = {
type: 'primitive',
value: 'positive'
};
const parameter2: RuleParameter = {
type: 'primitive',
value: 'positive'
};
const parameter3: RuleParameter = {
type: 'primitive',
value: 'positive'
};
const result = every(context, parameter1, parameter2, parameter3);
expect(result).toBeTruthy();
});
it('should evaluate to [false] when one of the rules fails', () => {
const parameter1: RuleParameter = {
type: 'primitive',
value: 'positive'
};
const parameter2: RuleParameter = {
type: 'primitive',
value: 'positive'
};
const parameter3: RuleParameter = {
type: 'primitive',
value: 'negative'
};
const result = every(context, parameter1, parameter2, parameter3);
expect(result).toBeFalsy();
});
});
describe('some', () => {
it('should evaluate a single rule to [true]', () => {
const parameter: RuleParameter = {
type: 'primitive',
value: 'positive'
};
const result = some(context, parameter);
expect(result).toBeTruthy();
});
it('should evaluate to [false] when no parameters provided', () => {
const result = some(context);
expect(result).toBeFalsy();
});
it('should evaluate to [false] when evaluator not available', () => {
const parameter: RuleParameter = {
type: 'primitive',
value: 'missing'
};
const result = some(context, parameter);
expect(result).toBeFalsy();
});
it('should evaluate to [true] if any rule succeeds', () => {
const parameter1: RuleParameter = {
type: 'primitive',
value: 'negative'
};
const parameter2: RuleParameter = {
type: 'primitive',
value: 'positive'
};
const parameter3: RuleParameter = {
type: 'primitive',
value: 'negative'
};
const result = some(context, parameter1, parameter2, parameter3);
expect(result).toBeTruthy();
});
});
});

View File

@@ -0,0 +1,66 @@
/*!
* @license
* Copyright 2016 - 2018 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 { RuleContext, RuleParameter } from '../config/rule.extensions';
export function not(context: RuleContext, ...args: RuleParameter[]): boolean {
if (!args || args.length === 0) {
return false;
}
return args
.every(arg => {
const evaluator = context.getEvaluator(arg.value);
if (!evaluator) {
console.warn('evaluator not found: ' + arg.value);
return false;
}
return !evaluator(context, ...(arg.parameters || []));
});
}
export function every(context: RuleContext, ...args: RuleParameter[]): boolean {
if (!args || args.length === 0) {
return false;
}
return args
.every(arg => {
const evaluator = context.getEvaluator(arg.value);
if (!evaluator) {
console.warn('evaluator not found: ' + arg.value);
return false;
}
return evaluator(context, ...(arg.parameters || []));
});
}
export function some(context: RuleContext, ...args: RuleParameter[]): boolean {
if (!args || args.length === 0) {
return false;
}
return args
.some(arg => {
const evaluator = context.getEvaluator(arg.value);
if (!evaluator) {
console.warn('evaluator not found: ' + arg.value);
return false;
}
return evaluator(context, ...(arg.parameters || []));
});
}

View File

@@ -0,0 +1,42 @@
/*!
* @license
* Copyright 2016 - 2018 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 { NgModule, ModuleWithProviders } from '@angular/core';
import { ExtensionLoaderService } from './services/extension-loader.service';
import { ExtensionService } from './services/extension.service';
import { DynamicExtensionComponent } from './components/dynamic-component/dynamic.component';
import { DynamicTabComponent } from './components/dynamic-tab/dynamic-tab.component';
@NgModule({
imports: [],
declarations: [DynamicExtensionComponent, DynamicTabComponent],
exports: [DynamicExtensionComponent, DynamicTabComponent]
})
export class ExtensionsModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: ExtensionsModule,
providers: [ExtensionLoaderService, ExtensionService]
};
}
static forChild(): ModuleWithProviders {
return {
ngModule: ExtensionsModule
};
}
}

View File

@@ -0,0 +1,138 @@
/*!
* @license
* Copyright 2016 - 2018 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 { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ActionRef, ContentActionRef, ContentActionType } from '../config/action.extensions';
import { ExtensionElement } from '../config/extension-element';
import { filterEnabled, getValue, mergeObjects, sortByOrder } from '../config/extension-utils';
import { ExtensionConfig } from '../config/extension.config';
import { RouteRef } from '../config/routing.extensions';
import { RuleRef } from '../config/rule.extensions';
@Injectable({
providedIn: 'root'
})
export class ExtensionLoaderService {
constructor(private http: HttpClient) {}
load(configPath: string, pluginsPath: string): Promise<ExtensionConfig> {
return new Promise<any>(resolve => {
this.loadConfig(configPath, 0).then(result => {
let config = result.config;
const override = sessionStorage.getItem('aca.extension.config');
if (override) {
config = JSON.parse(override);
}
const externalPlugins =
localStorage.getItem('experimental.external-plugins') ===
'true';
if (
externalPlugins &&
config.$references &&
config.$references.length > 0
) {
const plugins = config.$references.map((name, idx) =>
this.loadConfig(`${pluginsPath}/${name}`, idx)
);
Promise.all(plugins).then(results => {
const configs = results
.filter(entry => entry)
.sort(sortByOrder)
.map(entry => entry.config);
if (configs.length > 0) {
config = mergeObjects(config, ...configs);
}
resolve(config);
});
} else {
resolve(config);
}
});
});
}
protected loadConfig(
url: string,
order: number
): Promise<{ order: number; config: ExtensionConfig }> {
return new Promise(resolve => {
this.http.get<ExtensionConfig>(url).subscribe(
config => {
resolve({
order,
config
});
},
error => {
resolve(null);
}
);
});
}
getElements<T extends ExtensionElement>(
config: ExtensionConfig,
key: string,
fallback: Array<T> = []
): Array<T> {
const values = getValue(config, key) || fallback || [];
return values.filter(filterEnabled).sort(sortByOrder);
}
getContentActions(
config: ExtensionConfig,
key: string
): Array<ContentActionRef> {
return this.getElements(config, key).map(this.setActionDefaults);
}
getRules(config: ExtensionConfig): Array<RuleRef> {
if (config && config.rules) {
return config.rules;
}
return [];
}
getRoutes(config: ExtensionConfig): Array<RouteRef> {
if (config) {
return config.routes || [];
}
return [];
}
getActions(config: ExtensionConfig): Array<ActionRef> {
if (config) {
return config.actions || [];
}
return [];
}
protected setActionDefaults(action: ContentActionRef): ContentActionRef {
if (action) {
action.type = action.type || ContentActionType.default;
action.icon = action.icon || 'extension';
}
return action;
}
}

View File

@@ -0,0 +1,379 @@
/*!
* @license
* Copyright 2016 - 2018 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 { ExtensionService } from './extension.service';
import { ExtensionLoaderService } from './extension-loader.service';
import { ExtensionConfig } from '../config/extension.config';
import { RuleRef } from '../config/rule.extensions';
import { RouteRef } from '../config/routing.extensions';
import { ActionRef } from '../config/action.extensions';
describe('ExtensionService', () => {
const blankConfig: ExtensionConfig = {
$name: 'test.config',
$version: '1.0.0'
};
let loader: ExtensionLoaderService;
let service: ExtensionService;
beforeEach(() => {
loader = new ExtensionLoaderService(null);
service = new ExtensionService(loader);
});
it('should load and setup a config', async () => {
spyOn(loader, 'load').and.callFake(() => {
return Promise.resolve(blankConfig);
});
spyOn(service, 'setup').and.stub();
await service.load();
expect(loader.load).toHaveBeenCalled();
expect(service.setup).toHaveBeenCalledWith(blankConfig);
});
it('should raise warning if setting up with missing config', () => {
spyOn(console, 'warn').and.stub();
service.setup(null);
expect(console.warn).toHaveBeenCalledWith('Extension configuration not found');
});
it('should setup default evaluators', () => {
service.setup(blankConfig);
const evaluators = ['core.every', 'core.some', 'core.not'];
evaluators.forEach(key => {
expect(service.getEvaluator(key)).toBeDefined(`Evaluator ${key} is missing`);
});
});
it('should set custom evaluators', () => {
const evaluator1 = () => true;
const evaluator2 = () => false;
service.setEvaluators({
'eval1': evaluator1,
'eval2': evaluator2
});
expect(service.getEvaluator('eval1')).toBe(evaluator1);
expect(service.getEvaluator('eval2')).toBe(evaluator2);
});
it('should override existing evaluators', () => {
const evaluator1 = () => true;
const evaluator2 = () => false;
service.setup(blankConfig);
expect(service.getEvaluator('core.every')).toBeDefined();
expect(service.getEvaluator('core.every')).not.toBe(evaluator1);
service.setEvaluators({
'core.every': evaluator1,
'eval2': evaluator2
});
expect(service.getEvaluator('core.every')).toBe(evaluator1);
expect(service.getEvaluator('eval2')).toBe(evaluator2);
});
it('should negate existing evaluator', () => {
const positive = () => true;
service.setEvaluators({
'positive': positive
});
let evaluator = service.getEvaluator('positive');
expect(evaluator(null)).toBe(true);
evaluator = service.getEvaluator('!positive');
expect(evaluator(null, 'param1', 'param2')).toBe(false);
});
it('should not update evaluators with null value', () => {
service.setup(blankConfig);
service.setEvaluators(null);
expect(service.getEvaluator('core.every')).toBeDefined();
});
it('should set authentication guards', () => {
let registered = service.getAuthGuards(['guard1']);
expect(registered.length).toBe(0);
const guard1: any = {};
const guard2: any = {};
service.setAuthGuards({
'auth1': guard1,
'auth2': guard2
});
registered = service.getAuthGuards(['auth1', 'auth2']);
expect(registered.length).toBe(2);
expect(registered[0]).toBe(guard1);
expect(registered[1]).toBe(guard2);
});
it('should overwrite authentication guards', () => {
const guard1: any = {};
const guard2: any = {};
service.setAuthGuards({
'auth': guard1
});
expect(service.getAuthGuards(['auth'])).toEqual([guard1]);
service.setAuthGuards({
'auth': guard2
});
expect(service.getAuthGuards(['auth'])).toEqual([guard2]);
});
it('should not set authentication guards with null value', () => {
const guard1: any = {};
service.setAuthGuards({
'auth': guard1
});
service.setAuthGuards(null);
expect(service.getAuthGuards(['auth'])).toEqual([guard1]);
});
it('should not fetch auth guards for missing ids', () => {
const guards = service.getAuthGuards(null);
expect(guards).toEqual([]);
});
it('should set components', () => {
const component: any = {};
service.setComponents({
'component1': component
});
expect(service.getComponentById('component1')).toBe(component);
});
it('should overwrite components', () => {
const component1: any = {};
const component2: any = {};
service.setComponents({
'component': component1
});
expect(service.getComponentById('component')).toBe(component1);
service.setComponents({
'component': component2
});
expect(service.getComponentById('component')).toBe(component2);
});
it('should not set components with null value', () => {
const component: any = {};
service.setComponents({
'component1': component
});
expect(service.getComponentById('component1')).toBe(component);
service.setComponents(null);
expect(service.getComponentById('component1')).toBe(component);
});
it('should fetch route by id', () => {
const route: RouteRef = {
id: 'test.route',
component: 'component',
path: '/ext/route1'
};
spyOn(loader, 'getRoutes').and.returnValue([route]);
service.setup(blankConfig);
expect(service.getRouteById('test.route')).toBe(route);
});
it('should fetch action by id', () => {
const action: ActionRef = {
id: 'test.action',
type: 'action'
};
spyOn(loader, 'getActions').and.returnValue([action]);
service.setup(blankConfig);
expect(service.getActionById('test.action')).toBe(action);
});
it('should fetch rule by id', () => {
const rule: RuleRef = {
id: 'test.rule',
type: 'core.every'
};
spyOn(loader, 'getRules').and.returnValue([rule]);
service.setup(blankConfig);
expect(service.getRuleById('test.rule')).toBe(rule);
});
it('should evaluate condition', () => {
const condition = () => true;
service.setEvaluators({
'test.condition': condition
});
const context: any = {
getEvaluator(key: string) {
return service.getEvaluator(key);
}
};
const result = service.evaluateRule('test.condition', context);
expect(result).toBe(true);
});
it('should evaluate missing condition as [false]', () => {
const context: any = {
getEvaluator(key: string) {
return service.getEvaluator(key);
}
};
const result = service.evaluateRule('missing.condition', context);
expect(result).toBe(false);
});
it('should evaluate rule by reference', () => {
const ruleRef: RuleRef = {
id: 'test.rule',
type: 'core.every',
parameters: [
{
type: 'rule',
value: 'test.condition'
}
]
};
spyOn(loader, 'getRules').and.returnValue([ruleRef]);
service.setup(blankConfig);
const condition = () => true;
service.setEvaluators({
'test.condition': condition
});
const context: any = {
getEvaluator(key: string) {
return service.getEvaluator(key);
}
};
const result = service.evaluateRule('test.rule', context);
expect(result).toBe(true);
});
it('should evaluate rule ref with missing condition as [false]', () => {
const ruleRef: RuleRef = {
id: 'test.rule',
type: 'missing.evaluator'
};
spyOn(loader, 'getRules').and.returnValue([ruleRef]);
service.setup(blankConfig);
const context: any = {
getEvaluator(key: string) {
return service.getEvaluator(key);
}
};
const result = service.evaluateRule('test.rule', context);
expect(result).toBe(false);
});
it('should evaluate rule ref with missing evaluator as [false]', () => {
const ruleRef: RuleRef = {
id: 'test.rule',
type: 'core.every',
parameters: [
{
type: 'rule',
value: 'missing.condition'
}
]
};
spyOn(loader, 'getRules').and.returnValue([ruleRef]);
service.setup(blankConfig);
const context: any = {
getEvaluator(key: string) {
return service.getEvaluator(key);
}
};
const result = service.evaluateRule('test.rule', context);
expect(result).toBe(false);
});
describe('expressions', () => {
it('should eval static value', () => {
const value = service.runExpression('hello world');
expect(value).toBe('hello world');
});
it('should eval string as an expression', () => {
const value = service.runExpression('$( "hello world" )');
expect(value).toBe('hello world');
});
it('should eval expression with no context', () => {
const value = service.runExpression('$( 1 + 1 )');
expect(value).toBe(2);
});
it('should eval expression with context', () => {
const context = {
a: 'hey',
b: 'there'
};
const expression = '$( context.a + " " + context.b + "!" )';
const value = service.runExpression(expression, context);
expect(value).toBe('hey there!');
});
});
});

View File

@@ -0,0 +1,148 @@
/*!
* @license
* Copyright 2016 - 2018 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, Type } from '@angular/core';
import { RuleEvaluator, RuleRef, RuleContext, RuleParameter } 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';
@Injectable()
export class ExtensionService {
configPath = 'assets/app.extensions.json';
pluginsPath = 'assets/plugins';
rules: Array<RuleRef> = [];
routes: Array<RouteRef> = [];
actions: Array<ActionRef> = [];
authGuards: { [key: string]: Type<{}> } = {};
components: { [key: string]: Type<{}> } = {};
evaluators: { [key: string]: RuleEvaluator } = {};
constructor(private loader: ExtensionLoaderService) {}
async load(): Promise<ExtensionConfig> {
const config = await this.loader.load(
this.configPath,
this.pluginsPath
);
this.setup(config);
return config;
}
setup(config: ExtensionConfig) {
if (!config) {
console.warn('Extension configuration not found');
return;
}
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);
}
setEvaluators(values: { [key: string]: RuleEvaluator }) {
if (values) {
this.evaluators = Object.assign({}, this.evaluators, values);
}
}
setAuthGuards(values: { [key: string]: Type<{}> }) {
if (values) {
this.authGuards = Object.assign({}, this.authGuards, values);
}
}
setComponents(values: { [key: string]: Type<{}> }) {
if (values) {
this.components = Object.assign({}, this.components, values);
}
}
getRouteById(id: string): RouteRef {
return this.routes.find(route => route.id === id);
}
getAuthGuards(ids: string[]): Array<Type<{}>> {
return (ids || [])
.map(id => this.authGuards[id])
.filter(guard => guard);
}
getActionById(id: string): ActionRef {
return this.actions.find(action => action.id === id);
}
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];
}
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;
}
getComponentById(id: string): Type<{}> {
return this.components[id];
}
getRuleById(id: string): RuleRef {
return this.rules.find(ref => ref.id === id);
}
runExpression(value: string, context?: any) {
const pattern = new RegExp(/\$\((.*\)?)\)/g);
const matches = pattern.exec(value);
if (matches && matches.length > 1) {
const expression = matches[1];
const fn = new Function('context', `return ${expression}`);
const result = fn(context);
return result;
}
return value;
}
}

View File

@@ -0,0 +1,23 @@
/*!
* @license
* Copyright 2016 - 2018 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 { Node } from 'alfresco-js-api';
export interface NavigationState {
currentFolder?: Node;
url?: string;
}

View File

@@ -0,0 +1,25 @@
/*!
* @license
* Copyright 2016 - 2018 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.
*/
export interface ProfileState {
id: string;
isAdmin: boolean;
firstName: string;
lastName: string;
userName?: string;
initials?: string;
}

View File

@@ -0,0 +1,30 @@
/*!
* @license
* Copyright 2016 - 2018 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 { MinimalNodeEntity, SiteEntry } from 'alfresco-js-api';
export interface SelectionState {
count: number;
nodes: MinimalNodeEntity[];
libraries: SiteEntry[];
isEmpty: boolean;
first?: MinimalNodeEntity;
last?: MinimalNodeEntity;
folder?: MinimalNodeEntity;
file?: MinimalNodeEntity;
library?: SiteEntry;
}

View File

@@ -0,0 +1,36 @@
/*!
* @license
* Copyright 2016 - 2018 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.
*/
export * from './lib/config/action.extensions';
export * from './lib/config/extension-element';
export * from './lib/config/extension-utils';
export * from './lib/config/extension.config';
export * from './lib/config/navbar.extensions';
export * from './lib/config/permission.extensions';
export * from './lib/config/routing.extensions';
export * from './lib/config/rule.extensions';
export * from './lib/config/sidebar.extensions';
export * from './lib/config/viewer.extensions';
export * from './lib/services/extension-loader.service';
export * from './lib/services/extension.service';
export * from './lib/store/states/navigation.state';
export * from './lib/store/states/profile.state';
export * from './lib/store/states/selection.state';
export * from './lib/extensions.module';

View File

@@ -0,0 +1,22 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);