[ACA-1597] allow extensions overwrite existing settings (#523)

* merge objects within the arrays

* metadata properties

* code cleanup
This commit is contained in:
Denys Vuika 2018-07-20 14:46:43 +01:00 committed by GitHub
parent 8c9ffc1160
commit 3ab9cee163
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 136 additions and 38 deletions

View File

@ -214,20 +214,20 @@
}, },
"type": "object", "type": "object",
"required": ["name", "version"], "required": ["$name", "$version"],
"properties": { "properties": {
"name": { "$name": {
"description": "Extension name", "description": "Extension name",
"type": "string" "type": "string"
}, },
"version": { "$version": {
"description": "Extension version", "description": "Extension version",
"type": "string" "type": "string"
}, },
"description": { "$description": {
"description": "Brief description on what the extension does" "description": "Brief description on what the extension does"
}, },
"references": { "$references": {
"description": "References to external files", "description": "References to external files",
"type": "array", "type": "array",
"items": { "items": {

View File

@ -29,8 +29,10 @@ import { RuleRef } from './rule.extensions';
import { ActionRef, ContentActionRef } from './action.extensions'; import { ActionRef, ContentActionRef } from './action.extensions';
export interface ExtensionConfig { export interface ExtensionConfig {
version: string; $name: string;
references?: Array<string>; $version: string;
$description?: string;
$references?: Array<string>;
rules?: Array<RuleRef>; rules?: Array<RuleRef>;
routes?: Array<RouteRef>; routes?: Array<RouteRef>;
actions?: Array<ActionRef>; actions?: Array<ActionRef>;

View File

@ -43,10 +43,59 @@ describe('ExtensionService', () => {
extensions = TestBed.get(ExtensionService); extensions = TestBed.get(ExtensionService);
}); });
describe('configs', () => {
it('should merge two arrays based on [id] keys', () => {
const left = [
{
name: 'item0'
},
{
id: '#1',
name: 'item1'
},
{
id: '#2',
name: 'item2'
}
];
const right = [
{
name: 'extra-1'
},
{
id: '#2',
name: 'custom2',
tag: 'extra tag'
}
];
const result = extensions.mergeArrays(left, right);
expect(result).toEqual([
{
id: '#1',
name: 'item1'
},
{
id: '#2',
name: 'custom2',
tag: 'extra tag'
},
{
name: 'item0'
},
{
name: 'extra-1'
},
]);
});
});
describe('actions', () => { describe('actions', () => {
beforeEach(() => { beforeEach(() => {
extensions.setup({ extensions.setup({
version: '1.0.0', $name: 'test',
$version: '1.0.0',
actions: [ actions: [
{ {
id: 'aca:actions/create-folder', id: 'aca:actions/create-folder',
@ -179,7 +228,8 @@ describe('ExtensionService', () => {
beforeEach(() => { beforeEach(() => {
extensions.setup({ extensions.setup({
version: '1.0.0', $name: 'test',
$version: '1.0.0',
routes: [ routes: [
{ {
id: 'aca:routes/about', id: 'aca:routes/about',
@ -237,7 +287,8 @@ describe('ExtensionService', () => {
describe('content actions', () => { describe('content actions', () => {
it('should load content actions from the config', () => { it('should load content actions from the config', () => {
extensions.setup({ extensions.setup({
version: '1.0.0', $name: 'test',
$version: '1.0.0',
features: { features: {
content: { content: {
actions: [ actions: [
@ -263,7 +314,8 @@ describe('ExtensionService', () => {
it('should sort content actions by order', () => { it('should sort content actions by order', () => {
extensions.setup({ extensions.setup({
version: '1.0.0', $name: 'test',
$version: '1.0.0',
features: { features: {
content: { content: {
actions: [ actions: [
@ -297,7 +349,8 @@ describe('ExtensionService', () => {
describe('open with', () => { describe('open with', () => {
it('should load [open with] actions for the viewer', () => { it('should load [open with] actions for the viewer', () => {
extensions.setup({ extensions.setup({
version: '1.0.0', $name: 'test',
$version: '1.0.0',
features: { features: {
viewer: { viewer: {
openWith: [ openWith: [
@ -322,7 +375,8 @@ describe('ExtensionService', () => {
it('should load only enabled [open with] actions for the viewer', () => { it('should load only enabled [open with] actions for the viewer', () => {
extensions.setup({ extensions.setup({
version: '1.0.0', $name: 'test',
$version: '1.0.0',
features: { features: {
viewer: { viewer: {
openWith: [ openWith: [
@ -358,7 +412,8 @@ describe('ExtensionService', () => {
it('should sort [open with] actions by order', () => { it('should sort [open with] actions by order', () => {
extensions.setup({ extensions.setup({
version: '1.0.0', $name: 'test',
$version: '1.0.0',
features: { features: {
viewer: { viewer: {
openWith: [ openWith: [
@ -396,7 +451,8 @@ describe('ExtensionService', () => {
describe('create', () => { describe('create', () => {
it('should load [create] actions from config', () => { it('should load [create] actions from config', () => {
extensions.setup({ extensions.setup({
version: '1.0.0', $name: 'test',
$version: '1.0.0',
features: { features: {
create: [ create: [
{ {
@ -415,7 +471,8 @@ describe('ExtensionService', () => {
it('should sort [create] actions by order', () => { it('should sort [create] actions by order', () => {
extensions.setup({ extensions.setup({
version: '1.0.0', $name: 'test',
$version: '1.0.0',
features: { features: {
create: [ create: [
{ {

View File

@ -74,8 +74,8 @@ export class ExtensionService implements RuleContext {
this.loadConfig(this.configPath, 0).then(result => { this.loadConfig(this.configPath, 0).then(result => {
let config = result.config; let config = result.config;
if (config.references && config.references.length > 0) { if (config.$references && config.$references.length > 0) {
const plugins = config.references.map( const plugins = config.$references.map(
(name, idx) => this.loadConfig(`${this.pluginsPath}/${name}`, idx) (name, idx) => this.loadConfig(`${this.pluginsPath}/${name}`, idx)
); );
@ -86,7 +86,7 @@ export class ExtensionService implements RuleContext {
.map(entry => entry.config); .map(entry => entry.config);
if (configs.length > 0) { if (configs.length > 0) {
config = this.mergeConfigs(config, ...configs); config = this.mergeObjects(config, ...configs);
} }
this.setup(config); this.setup(config);
@ -411,23 +411,50 @@ export class ExtensionService implements RuleContext {
return false; return false;
} }
// todo: requires overwrite support for array entries mergeObjects(...objects): any {
// todo: overwrite only particular areas, don't touch version or other top-level props
protected mergeConfigs(...objects): any {
const result = {}; const result = {};
objects.forEach(source => { objects.forEach(source => {
Object.keys(source).forEach(prop => { Object.keys(source).forEach(prop => {
if (prop in result && Array.isArray(result[prop])) { if (!prop.startsWith('$')) {
result[prop] = result[prop].concat(source[prop]); if (prop in result && Array.isArray(result[prop])) {
} else if (prop in result && typeof result[prop] === 'object') { // result[prop] = result[prop].concat(source[prop]);
result[prop] = this.mergeConfigs(result[prop], source[prop]); result[prop] = this.mergeArrays(result[prop], source[prop]);
} else { } else if (prop in result && typeof result[prop] === 'object') {
result[prop] = source[prop]; result[prop] = this.mergeObjects(result[prop], source[prop]);
} else {
result[prop] = source[prop];
}
} }
}); });
}); });
return result; return result;
} }
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 = this.mergeObjects(map[element.id], element);
map[element.id] = merged;
} else {
result.push(element);
}
});
return Object.values(map).concat(result);
}
} }

View File

@ -1,9 +1,8 @@
{ {
"$schema": "../../extension.schema.json", "$schema": "../../extension.schema.json",
"name": "app", "$name": "app",
"version": "1.0.0", "$version": "1.0.0",
"$references": [
"references": [
"plugin1.json", "plugin1.json",
"plugin2.json" "plugin2.json"
], ],

View File

@ -1,8 +1,8 @@
{ {
"$schema": "../../../extension.schema.json", "$schema": "../../../extension.schema.json",
"version": "1.0.0", "$version": "1.0.0",
"name": "plugin1", "$name": "plugin1",
"description": "demo plugin", "$description": "demo plugin",
"actions": [ "actions": [
{ {
@ -36,6 +36,19 @@
} }
] ]
}, },
"navbar": [
{
"id": "app.navbar.primary",
"items": [
{
"id": "app.navbar.personalFiles",
"icon": "extension",
"title": "APP.BROWSE.PERSONAL.SIDENAV_LINK.LABEL",
"route": "personal-files"
}
]
}
],
"content": { "content": {
"actions": [ "actions": [
{ {

View File

@ -1,8 +1,8 @@
{ {
"$schema": "../../../extension.schema.json", "$schema": "../../../extension.schema.json",
"version": "1.0.0", "$version": "1.0.0",
"name": "plugin2", "$name": "plugin2",
"description": "demo plugin", "$description": "demo plugin",
"routes": [ "routes": [
{ {