[AAE-5874] Re-evaluate extension rules when context changes (#2296)

* [AAE-5874] Re-evaluate extension rules when context changes

* [AAE-5874] Add disabled attribute to toolbar actions

* [AAE-5874] Fix unit tests
This commit is contained in:
Pablo Martinez Garcia
2021-09-16 15:48:43 +02:00
committed by Denys Vuika
parent 46a179c75a
commit c4e4b50f8a
17 changed files with 408 additions and 238 deletions

View File

@@ -47,14 +47,15 @@ describe('InfoDrawerComponent', () => {
};
const extensionServiceMock = {
getSidebarTabs: () => {},
getAllowedSidebarActions: () => [
{
id: 'app.sidebar.close',
order: 100,
title: 'close',
icon: 'highlight_off'
}
]
getAllowedSidebarActions: () =>
of([
{
id: 'app.sidebar.close',
order: 100,
title: 'close',
icon: 'highlight_off'
}
])
};
beforeEach(() => {

View File

@@ -27,7 +27,7 @@ import { Component, HostListener, Input, OnChanges, OnDestroy, OnInit } from '@a
import { MinimalNodeEntity, MinimalNodeEntryEntity, SiteEntry } from '@alfresco/js-api';
import { ContentActionRef, SidebarTabRef } from '@alfresco/adf-extensions';
import { Store } from '@ngrx/store';
import { getAppSelection, SetInfoDrawerStateAction, ToggleInfoDrawerAction, infoDrawerPreview } from '@alfresco/aca-shared/store';
import { SetInfoDrawerStateAction, ToggleInfoDrawerAction, infoDrawerPreview } from '@alfresco/aca-shared/store';
import { AppExtensionService } from '../../services/app.extension.service';
import { ContentApiService } from '../../services/content-api.service';
import { takeUntil } from 'rxjs/operators';
@@ -60,11 +60,11 @@ export class InfoDrawerComponent implements OnChanges, OnInit, OnDestroy {
ngOnInit() {
this.tabs = this.extensions.getSidebarTabs();
this.store
.select(getAppSelection)
this.extensions
.getAllowedSidebarActions()
.pipe(takeUntil(this.onDestroy$))
.subscribe(() => {
this.actions = this.extensions.getAllowedSidebarActions();
.subscribe((actions) => {
this.actions = actions;
});
this.store

View File

@@ -6,6 +6,7 @@
[color]="color"
[attr.aria-label]="actionRef.description || actionRef.title | translate"
[attr.title]="actionRef.description || actionRef.title | translate"
[disabled]="actionRef.disabled"
(click)="runAction()"
>
<adf-icon [value]="actionRef.icon"></adf-icon>

View File

@@ -5,6 +5,7 @@
[attr.aria-label]="actionRef.description || actionRef.title | translate"
[attr.title]="actionRef.description || actionRef.title | translate"
[matMenuTriggerFor]="menu"
[disabled]="actionRef.disabled"
#matTrigger="matMenuTrigger"
>
<adf-icon [value]="actionRef.icon"></adf-icon>

View File

@@ -60,9 +60,17 @@ describe('AppExtensionService', () => {
extensions = TestBed.inject(ExtensionService);
});
const applyConfig = (config: ExtensionConfig) => {
const applyConfig = (config: ExtensionConfig, selection?: boolean) => {
extensions.setup(config);
service.setup(config);
if (selection) {
service.selection = {
isEmpty: false,
count: 1,
libraries: null,
nodes: null
};
}
};
describe('configs', () => {
@@ -284,7 +292,7 @@ describe('AppExtensionService', () => {
});
describe('content actions', () => {
it('should load content actions from the config', () => {
it('should load content actions from the config', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -297,23 +305,26 @@ describe('AppExtensionService', () => {
{
id: 'aca:toolbar/separator-1',
order: 1,
type: ContentActionType.separator,
type: ContentActionType.default,
title: 'action1'
},
{
id: 'aca:toolbar/separator-2',
order: 2,
type: ContentActionType.separator,
type: ContentActionType.default,
title: 'action2'
}
]
}
});
expect(service.toolbarActions.length).toBe(2);
service.getAllowedToolbarActions().subscribe((actions) => {
expect(actions.length).toBe(2);
done();
});
});
it('should sort content actions by order', () => {
it('should sort content actions by order', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -326,27 +337,30 @@ describe('AppExtensionService', () => {
{
id: 'aca:toolbar/separator-2',
order: 2,
type: ContentActionType.separator,
type: ContentActionType.default,
title: 'action2'
},
{
id: 'aca:toolbar/separator-1',
order: 1,
type: ContentActionType.separator,
type: ContentActionType.default,
title: 'action1'
}
]
}
});
expect(service.toolbarActions.length).toBe(2);
expect(service.toolbarActions[0].id).toBe('aca:toolbar/separator-1');
expect(service.toolbarActions[1].id).toBe('aca:toolbar/separator-2');
service.getAllowedToolbarActions().subscribe((actions) => {
expect(actions.length).toBe(2);
expect(actions[0].id).toBe('aca:toolbar/separator-1');
expect(actions[1].id).toBe('aca:toolbar/separator-2');
done();
});
});
});
describe('open with', () => {
it('should load [open with] actions for the viewer', () => {
it('should load [open with] actions for the viewer', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -373,10 +387,13 @@ describe('AppExtensionService', () => {
}
});
expect(service.openWithActions.length).toBe(1);
service.getOpenWithActions().subscribe((actions) => {
expect(actions.length).toBe(1);
done();
});
});
it('should load only enabled [open with] actions for the viewer', () => {
it('should load only enabled [open with] actions for the viewer', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -413,11 +430,14 @@ describe('AppExtensionService', () => {
}
});
expect(service.openWithActions.length).toBe(1);
expect(service.openWithActions[0].id).toBe('aca:viewer/action2');
service.getOpenWithActions().subscribe((actions) => {
expect(actions.length).toBe(1);
expect(actions[0].id).toBe('aca:viewer/action2');
done();
});
});
it('should sort [open with] actions by order', () => {
it('should sort [open with] actions by order', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -453,14 +473,17 @@ describe('AppExtensionService', () => {
}
});
expect(service.openWithActions.length).toBe(2);
expect(service.openWithActions[0].id).toBe('aca:viewer/action1');
expect(service.openWithActions[1].id).toBe('aca:viewer/action2');
service.getOpenWithActions().subscribe((actions) => {
expect(actions.length).toBe(2);
expect(actions[0].id).toBe('aca:viewer/action1');
expect(actions[1].id).toBe('aca:viewer/action2');
done();
});
});
});
describe('create', () => {
it('should load [create] actions from config', () => {
it('should load [create] actions from config', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -481,10 +504,13 @@ describe('AppExtensionService', () => {
}
});
expect(service.createActions.length).toBe(1);
service.getCreateActions().subscribe((actions) => {
expect(actions.length).toBe(1);
done();
});
});
it('should sort [create] actions by order', () => {
it('should sort [create] actions by order', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -512,9 +538,12 @@ describe('AppExtensionService', () => {
}
});
expect(service.createActions.length).toBe(2);
expect(service.createActions[0].id).toBe('aca:create/folder-2');
expect(service.createActions[1].id).toBe('aca:create/folder');
service.getCreateActions().subscribe((actions) => {
expect(actions.length).toBe(2);
expect(actions[0].id).toBe('aca:create/folder-2');
expect(actions[1].id).toBe('aca:create/folder');
done();
});
});
});
@@ -700,7 +729,7 @@ describe('AppExtensionService', () => {
});
describe('getSharedLinkViewerToolbarActions', () => {
it('should get shared link viewer actions', () => {
it('should get shared link viewer actions', (done) => {
const actions = [
{
id: 'id',
@@ -724,23 +753,29 @@ describe('AppExtensionService', () => {
}
];
applyConfig({
$id: 'test',
$name: 'test',
$version: '1.0.0',
$license: 'MIT',
$vendor: 'Good company',
$runtime: '1.5.0',
features: {
viewer: {
shared: {
toolbarActions: actions
applyConfig(
{
$id: 'test',
$name: 'test',
$version: '1.0.0',
$license: 'MIT',
$vendor: 'Good company',
$runtime: '1.5.0',
features: {
viewer: {
shared: {
toolbarActions: actions
}
}
}
}
});
},
true
);
expect(service.getSharedLinkViewerToolbarActions()).toEqual(expectedActions);
service.getSharedLinkViewerToolbarActions().subscribe((sharedLinkViewerToolbarActions) => {
expect(sharedLinkViewerToolbarActions).toEqual(expectedActions);
done();
});
});
});
@@ -793,7 +828,7 @@ describe('AppExtensionService', () => {
});
describe('getHeaderActions', () => {
it('should load user actions from the config', () => {
it('should load user actions from the config', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -806,21 +841,24 @@ describe('AppExtensionService', () => {
{
id: 'header.action.separator.1',
order: 1,
type: ContentActionType.separator
type: ContentActionType.default
},
{
id: 'header.action.separator.2',
order: 2,
type: ContentActionType.separator
type: ContentActionType.default
}
]
}
});
expect(service.headerActions.length).toBe(2);
service.getHeaderActions().subscribe((headerActions) => {
expect(headerActions.length).toBe(2);
done();
});
});
it('should sort header actions by order', () => {
it('should sort header actions by order', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -844,12 +882,15 @@ describe('AppExtensionService', () => {
}
});
const actions = service.getHeaderActions();
expect(actions[0].id).toBe('header.action.2');
expect(actions[1].id).toBe('header.action.1');
service.getHeaderActions().subscribe((headerActions) => {
expect(headerActions.length).toBe(2);
expect(headerActions[0].id).toBe('header.action.2');
expect(headerActions[1].id).toBe('header.action.1');
done();
});
});
it('should sort header menu children actions by order', () => {
it('should sort header menu children actions by order', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -880,9 +921,12 @@ describe('AppExtensionService', () => {
}
});
const actions = service.getHeaderActions()[0];
expect(actions.children[0].id).toBe('header.action.2');
expect(actions.children[1].id).toBe('header.action.1');
service.getHeaderActions().subscribe((headerActions) => {
expect(headerActions.length).toBe(1);
expect(headerActions[0].children[0].id).toBe('header.action.2');
expect(headerActions[0].children[1].id).toBe('header.action.1');
done();
});
});
});
@@ -1050,7 +1094,7 @@ describe('AppExtensionService', () => {
}
];
it('should set the action disabled for create actions', () => {
it('should set the action disabled for create actions', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -1063,10 +1107,13 @@ describe('AppExtensionService', () => {
}
});
expect(service.getCreateActions()).toEqual(expectedActionsWithChildren);
service.getCreateActions().subscribe((createActions) => {
expect(createActions).toEqual(expectedActionsWithChildren);
done();
});
});
it('should set the action disabled for sidebar actions', () => {
it('should set the action disabled for sidebar actions', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -1081,10 +1128,13 @@ describe('AppExtensionService', () => {
}
});
expect(service.getAllowedSidebarActions()).toEqual(expectedActionsWithoutChildren);
service.getAllowedSidebarActions().subscribe((serviceActions) => {
expect(serviceActions).toEqual(expectedActionsWithoutChildren);
done();
});
});
it('should set the action disabled for toolbar actions', () => {
it('should set the action disabled for toolbar actions', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -1097,10 +1147,13 @@ describe('AppExtensionService', () => {
}
});
expect(service.getAllowedToolbarActions()).toEqual(expectedActionsWithoutChildren);
service.getAllowedToolbarActions().subscribe((serviceActions) => {
expect(serviceActions).toEqual(expectedActionsWithoutChildren);
done();
});
});
it('should set the action disabled for viewer toolbar actions', () => {
it('should set the action disabled for viewer toolbar actions', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -1113,30 +1166,39 @@ describe('AppExtensionService', () => {
}
});
expect(service.getViewerToolbarActions()).toEqual(expectedActionsWithoutChildren);
service.getViewerToolbarActions().subscribe((serviceActions) => {
expect(serviceActions).toEqual(expectedActionsWithoutChildren);
done();
});
});
it('should set the action disabled for shared link viewer toolbar actions', () => {
applyConfig({
$id: 'test',
$name: 'test',
$version: '1.0.0',
$license: 'MIT',
$vendor: 'Good company',
$runtime: '1.5.0',
features: {
viewer: {
shared: {
toolbarActions: actions
it('should set the action disabled for shared link viewer toolbar actions', (done) => {
applyConfig(
{
$id: 'test',
$name: 'test',
$version: '1.0.0',
$license: 'MIT',
$vendor: 'Good company',
$runtime: '1.5.0',
features: {
viewer: {
shared: {
toolbarActions: actions
}
}
}
}
});
},
true
);
expect(service.getSharedLinkViewerToolbarActions()).toEqual(expectedActionsWithoutChildren);
service.getSharedLinkViewerToolbarActions().subscribe((serviceActions) => {
expect(serviceActions).toEqual(expectedActionsWithoutChildren);
done();
});
});
it('should set the action disabled for header actions', () => {
it('should set the action disabled for header actions', (done) => {
applyConfig({
$id: 'test',
$name: 'test',
@@ -1149,23 +1211,32 @@ describe('AppExtensionService', () => {
}
});
expect(service.getHeaderActions()).toEqual(expectedActionsWithoutChildren);
service.getHeaderActions().subscribe((serviceActions) => {
expect(serviceActions).toEqual(expectedActionsWithoutChildren);
done();
});
});
it('should set the action disabled for context menu actions', () => {
applyConfig({
$id: 'test',
$name: 'test',
$version: '1.0.0',
$license: 'MIT',
$vendor: 'Good company',
$runtime: '1.5.0',
features: {
contextMenu: actions
}
});
it('should set the action disabled for context menu actions', (done) => {
applyConfig(
{
$id: 'test',
$name: 'test',
$version: '1.0.0',
$license: 'MIT',
$vendor: 'Good company',
$runtime: '1.5.0',
features: {
contextMenu: actions
}
},
true
);
expect(service.getAllowedContextMenuActions()).toEqual(expectedActionsWithoutChildren);
service.getAllowedContextMenuActions().subscribe((serviceActions) => {
expect(serviceActions).toEqual(expectedActionsWithoutChildren);
done();
});
});
});
});

View File

@@ -56,6 +56,7 @@ import { RepositoryInfo, NodeEntry } from '@alfresco/js-api';
import { ViewerRules } from '../models/viewer.rules';
import { SettingsGroupRef } from '../models/types';
import { NodePermissionService } from '../services/node-permission.service';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
@@ -63,21 +64,22 @@ import { NodePermissionService } from '../services/node-permission.service';
export class AppExtensionService implements RuleContext {
private _references = new BehaviorSubject<ExtensionRef[]>([]);
headerActions: Array<ContentActionRef> = [];
toolbarActions: Array<ContentActionRef> = [];
viewerToolbarActions: Array<ContentActionRef> = [];
sharedLinkViewerToolbarActions: Array<ContentActionRef> = [];
contextMenuActions: Array<ContentActionRef> = [];
openWithActions: Array<ContentActionRef> = [];
createActions: Array<ContentActionRef> = [];
navbar: Array<NavBarGroupRef> = [];
sidebarTabs: Array<SidebarTabRef> = [];
sidebarActions: Array<ContentActionRef> = [];
contentMetadata: any;
search: any;
viewerRules: ViewerRules = {};
settingGroups: Array<SettingsGroupRef> = [];
private _headerActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private _toolbarActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private _viewerToolbarActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private _sharedLinkViewerToolbarActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private _contextMenuActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private _openWithActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private _createActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private _sidebarActions = new BehaviorSubject<Array<ContentActionRef>>([]);
documentListPresets: {
files: Array<DocumentListPresetRef>;
libraries: Array<DocumentListPresetRef>;
@@ -106,6 +108,8 @@ export class AppExtensionService implements RuleContext {
references$: Observable<ExtensionRef[]>;
config: ExtensionConfig;
constructor(
public auth: AuthenticationService,
protected store: Store<AppStore>,
@@ -124,12 +128,16 @@ export class AppExtensionService implements RuleContext {
this.navigation = result.navigation;
this.profile = result.profile;
this.repository = result.repository;
if (this.config) {
this.setup(this.config);
}
});
}
async load() {
const config = await this.extensions.load();
this.setup(config);
this.config = await this.extensions.load();
this.setup(this.config);
}
setup(config: ExtensionConfig) {
@@ -140,15 +148,15 @@ export class AppExtensionService implements RuleContext {
this.settingGroups = this.loader.getElements<SettingsGroupRef>(config, 'settings');
this.headerActions = this.loader.getContentActions(config, 'features.header');
this.sidebarActions = this.loader.getContentActions(config, 'features.sidebar.toolbar');
this.toolbarActions = this.loader.getContentActions(config, 'features.toolbar');
this.viewerToolbarActions = this.loader.getContentActions(config, 'features.viewer.toolbarActions');
this.sharedLinkViewerToolbarActions = this.loader.getContentActions(config, 'features.viewer.shared.toolbarActions');
this._headerActions.next(this.loader.getContentActions(config, 'features.header'));
this._sidebarActions.next(this.loader.getContentActions(config, 'features.sidebar.toolbar'));
this._toolbarActions.next(this.loader.getContentActions(config, 'features.toolbar'));
this._viewerToolbarActions.next(this.loader.getContentActions(config, 'features.viewer.toolbarActions'));
this._sharedLinkViewerToolbarActions.next(this.loader.getContentActions(config, 'features.viewer.shared.toolbarActions'));
this._contextMenuActions.next(this.loader.getContentActions(config, 'features.contextMenu'));
this._openWithActions.next(this.loader.getContentActions(config, 'features.viewer.openWith'));
this._createActions.next(this.loader.getElements<ContentActionRef>(config, 'features.create'));
this.contextMenuActions = this.loader.getContentActions(config, 'features.contextMenu');
this.openWithActions = this.loader.getContentActions(config, 'features.viewer.openWith');
this.createActions = this.loader.getElements<ContentActionRef>(config, 'features.create');
this.navbar = this.loadNavBar(config);
this.sidebarTabs = this.loader.getElements<SidebarTabRef>(config, 'features.sidebar.tabs');
this.contentMetadata = this.loadContentMetadata(config);
@@ -344,12 +352,16 @@ export class AppExtensionService implements RuleContext {
};
}
getCreateActions(): Array<ContentActionRef> {
return this.createActions
.filter((action) => this.filterVisible(action))
.map((action) => this.copyAction(action))
.map((action) => this.buildMenu(action))
.map((action) => this.setActionDisabledFromRule(action));
getCreateActions(): Observable<Array<ContentActionRef>> {
return this._createActions.pipe(
map((createActions) =>
createActions
.filter((action) => this.filterVisible(action))
.map((action) => this.copyAction(action))
.map((action) => this.buildMenu(action))
.map((action) => this.setActionDisabledFromRule(action))
)
);
}
private buildMenu(actionRef: ContentActionRef): ContentActionRef {
@@ -388,48 +400,58 @@ export class AppExtensionService implements RuleContext {
.reduce(reduceSeparators, []);
}
getAllowedSidebarActions(): Array<ContentActionRef> {
return this.getAllowedActions(this.sidebarActions);
getAllowedSidebarActions(): Observable<Array<ContentActionRef>> {
return this._sidebarActions.pipe(map((sidebarActions) => this.getAllowedActions(sidebarActions)));
}
getAllowedToolbarActions(): Array<ContentActionRef> {
return this.getAllowedActions(this.toolbarActions);
getAllowedToolbarActions(): Observable<Array<ContentActionRef>> {
return this._toolbarActions.pipe(map((toolbarActions) => this.getAllowedActions(toolbarActions)));
}
getViewerToolbarActions(): Array<ContentActionRef> {
return this.getAllowedActions(this.viewerToolbarActions);
getViewerToolbarActions(): Observable<Array<ContentActionRef>> {
return this._viewerToolbarActions.pipe(map((viewerToolbarActions) => this.getAllowedActions(viewerToolbarActions)));
}
getSharedLinkViewerToolbarActions(): Array<ContentActionRef> {
return this.getAllowedActions(this.sharedLinkViewerToolbarActions);
getOpenWithActions(): Observable<Array<ContentActionRef>> {
return this._openWithActions.pipe(map((openWithActions) => this.getAllowedActions(openWithActions)));
}
getHeaderActions(): Array<ContentActionRef> {
return this.headerActions
.filter((action) => this.filterVisible(action))
.map((action) => {
if (action.type === ContentActionType.menu) {
const copy = this.copyAction(action);
if (copy.children && copy.children.length > 0) {
copy.children = copy.children
.filter((childAction) => this.filterVisible(childAction))
.sort(sortByOrder)
.reduce(reduceEmptyMenus, [])
.reduce(reduceSeparators, []);
}
return copy;
}
return action;
})
.map((action) => this.setActionDisabledFromRule(action))
.sort(sortByOrder)
.reduce(reduceEmptyMenus, [])
.reduce(reduceSeparators, []);
getSharedLinkViewerToolbarActions(): Observable<Array<ContentActionRef>> {
return this._sharedLinkViewerToolbarActions.pipe(
map((sharedLinkViewerToolbarActions) => (!this.selection.isEmpty ? this.getAllowedActions(sharedLinkViewerToolbarActions) : []))
);
}
getAllowedContextMenuActions(): Array<ContentActionRef> {
return this.getAllowedActions(this.contextMenuActions);
getHeaderActions(): Observable<Array<ContentActionRef>> {
return this._headerActions.pipe(
map((headerActions) =>
headerActions
.filter((action) => this.filterVisible(action))
.map((action) => {
if (action.type === ContentActionType.menu) {
const copy = this.copyAction(action);
if (copy.children && copy.children.length > 0) {
copy.children = copy.children
.filter((childAction) => this.filterVisible(childAction))
.sort(sortByOrder)
.reduce(reduceEmptyMenus, [])
.reduce(reduceSeparators, []);
}
return copy;
}
return action;
})
.map((action) => this.setActionDisabledFromRule(action))
.sort(sortByOrder)
.reduce(reduceEmptyMenus, [])
.reduce(reduceSeparators, [])
)
);
}
getAllowedContextMenuActions(): Observable<Array<ContentActionRef>> {
return this._contextMenuActions.pipe(map((contextMenuActions) => (!this.selection.isEmpty ? this.getAllowedActions(contextMenuActions) : [])));
}
getSettingsGroups(): Array<SettingsGroupRef> {