-
-
-
-
-
-
-
- {{ 'PERMISSION_MANAGER.PERMISSION_DISPLAY.NO_PERMISSIONS' | translate }}
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
- {{ role | adfLocalizedRole }}
-
-
-
- {{ entry.data.getValue(entry.row, entry.col) | adfLocalizedRole }}
-
-
-
-
-
-
- {{'PERMISSION_MANAGER.PERMISSION_DISPLAY.INHERITED' | translate}}
-
-
-
-
- {{'PERMISSION_MANAGER.PERMISSION_DISPLAY.LOCALLY_SET' | translate}}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
error
+
{{ 'PERMISSION_MANAGER.ERROR.NOT-FOUND'| translate }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.scss b/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.scss
index 2947029932..d6049f3827 100644
--- a/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.scss
+++ b/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.scss
@@ -1,50 +1,92 @@
@mixin adf-permission-list-theme($theme) {
- $adf-permission-list-width: 70% !default;
+ $primary: map-get($theme, primary);
+ $accent: map-get($theme, accent);
+ $warn: map-get($theme, warn);
+ $foreground: map-get($theme, foreground);
+ $background: map-get($theme, background);
.adf {
- &-permission-label {
- max-width: 130px;
- min-width: 100px;
- margin-left: 50px;
+ &-permission-card {
+ height: 100%;
+ box-sizing: border-box;
+ display: flex !important;
+ flex-direction: column;
+ overflow: hidden;
}
- &-delete-permission {
- max-width: 50px;
+ &-permission-loader {
+ margin-left: 45%;
+ overflow: hidden;
}
- &-authorityId-label {
- min-width: 100px;
- }
-
- &-key-icon {
- max-width: 50px;
- }
-
- &-ellipsis-cell {
- position: sticky;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- &-display-permission-container {
+ &-permission-container {
display: flex;
- justify-content: space-around;
- flex: 1;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 15px;
+ border: 1px solid mat-color($foreground, divider);
}
- &-datatable-permission {
+ &-inherit-container {
display: flex;
- min-width: 450px;
- width: $adf-permission-list-width;
+ flex-direction: row;
+ align-items: center;
+
+ &-header {
+ margin-bottom: 10px;
+ margin-top: 10px;
+ }
}
- &-locallyset-label {
- padding: 4px;
+ &-inherit-toggle {
+ padding-left: 30px;
}
- &-inherited-label {
- width: 92.13px;
- justify-content: center;
+ &-inherit-subtitle {
+ padding-bottom: 5px;
+ }
+
+ &-permission-content-header {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding: 5px 15px;
+ }
+
+ &-permission-role-column-header {
+ position: relative !important;
+ height: 40px;
+ .mat-form-field-infix {
+ border: none;
+ }
+ }
+
+ &-permission-header {
+ @include flex-column;
+ }
+
+ &-permission-list {
+ display: flex;
+ height: calc(100% - 63px);
+ }
+ }
+
+
+ [aria-sort='Ascending'] adf-user-role-column,
+ [aria-sort='Descending'] adf-user-role-column {
+ padding-left: 10px;
+ padding-right: 10px;
+ }
+
+
+ .adf-permission-pop-over {
+ padding-right: 15px;
+ width: 100%;
+
+ .adf-pop-over-card {
+ width: 100%;
+ @include mat-elevation(16, mat-color($foreground, divider), 0.8);
}
}
}
diff --git a/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.spec.ts b/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.spec.ts
index 3d002e3706..7e7f3dbdea 100644
--- a/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.spec.ts
+++ b/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.spec.ts
@@ -15,21 +15,24 @@
* limitations under the License.
*/
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { PermissionListComponent } from './permission-list.component';
-import { By } from '@angular/platform-browser';
import { NodesApiService, SearchService, setupTestBed } from '@alfresco/adf-core';
-import { of } from 'rxjs';
-import { NodePermissionService } from '../../services/node-permission.service';
-import { fakeNodeWithPermissions,
- fakeNodeInheritedOnly,
- fakeNodeWithOnlyLocally,
- fakeSiteNodeResponse,
- fakeSiteRoles,
- fakeNodeWithoutPermissions,
- fakeEmptyResponse } from '../../../mock/permission-list.component.mock';
-import { ContentTestingModule } from '../../../testing/content.testing.module';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
+import { of, throwError } from 'rxjs';
+import { PermissionListComponent } from './permission-list.component';
+import { NodePermissionService } from '../../services/node-permission.service';
+import {
+ fakeEmptyResponse,
+ fakeNodeInheritedOnly,
+ fakeNodeWithOnlyLocally,
+ fakeNodeWithoutPermissions,
+ fakeNodeWithPermissions,
+ fakeReadOnlyNodeInherited,
+ fakeSiteNodeResponse,
+ fakeSiteRoles
+} from '../../../mock/permission-list.component.mock';
+import { ContentTestingModule } from '../../../testing/content.testing.module';
describe('PermissionListComponent', () => {
@@ -39,6 +42,9 @@ describe('PermissionListComponent', () => {
let nodeService: NodesApiService;
let nodePermissionService: NodePermissionService;
let searchApiService: SearchService;
+ let getNodeSpy: jasmine.Spy;
+ let searchQuerySpy: jasmine.Spy;
+ const fakeLocalPermission = JSON.parse(JSON.stringify(fakeNodeWithOnlyLocally));
setupTestBed({
imports: [
@@ -54,142 +60,188 @@ describe('PermissionListComponent', () => {
nodeService = TestBed.inject(NodesApiService);
nodePermissionService = TestBed.inject(NodePermissionService);
searchApiService = TestBed.inject(SearchService);
+
+ spyOn(nodePermissionService, 'getGroupMemberByGroupName').and.returnValue(of(fakeSiteRoles));
+ getNodeSpy = spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithoutPermissions));
+ searchQuerySpy = spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeEmptyResponse));
+ component.nodeId = 'fake-node-id';
+ fixture.detectChanges();
});
afterEach(() => {
fixture.destroy();
});
- it('should be able to render the component', () => {
- spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithOnlyLocally));
- spyOn(nodePermissionService, 'getNodeRoles').and.returnValue(of([]));
- fixture.detectChanges();
- expect(element.querySelector('#adf-permission-display-container')).not.toBeNull();
+ it('should render default layout', async () => {
+ component.nodeId = 'fake-node-id';
+ getNodeSpy.and.returnValue(of(fakeNodeWithoutPermissions));
+ component.ngOnInit();
+ await fixture.detectChanges();
+ expect(element.querySelector('.adf-permission-container')).not.toBeNull();
+ expect(element.querySelector('[data-automation-id="adf-locally-set-permission"]')).not.toBeNull();
});
- it('should render default empty template when no permissions', () => {
+ it('should render error template', async () => {
component.nodeId = 'fake-node-id';
- spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithoutPermissions));
- spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeEmptyResponse));
- fixture.detectChanges();
+ getNodeSpy.and.returnValue(throwError(null));
+ component.ngOnInit();
+ await fixture.detectChanges();
- expect(element.querySelector('#adf-no-permissions-template')).not.toBeNull();
- expect(element.querySelector('#adf-permission-display-container .adf-datatable-permission')).toBeNull();
+ expect(element.querySelector('.adf-no-permission__template')).not.toBeNull();
+ expect(element.querySelector('.adf-no-permission__template p').textContent).toContain('PERMISSION_MANAGER.ERROR.NOT-FOUND');
});
it('should show the node permissions', () => {
component.nodeId = 'fake-node-id';
- spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithPermissions));
- spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeEmptyResponse));
+ getNodeSpy.and.returnValue(of(fakeNodeWithPermissions));
+ component.ngOnInit();
fixture.detectChanges();
- expect(element.querySelector('#adf-permission-display-container')).not.toBeNull();
- expect(element.querySelectorAll('.adf-datatable-row').length).toBe(4);
+
+ expect(element.querySelectorAll('[data-automation-id="adf-locally-set-permission"] .adf-datatable-row').length).toBe(2);
+
+ const showButton: HTMLButtonElement = element.querySelector('[data-automation-id="permission-info-button"]');
+ showButton.click();
+ fixture.detectChanges();
+
+ expect(document.querySelectorAll('[data-automation-id="adf-inherited-permission"] .adf-datatable-row').length).toBe(3);
});
- it('should show inherited label for inherited permissions', () => {
- component.nodeId = 'fake-node-id';
- spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeInheritedOnly));
- spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeEmptyResponse));
- fixture.detectChanges();
- expect(element.querySelector('#adf-permission-display-container')).not.toBeNull();
- expect(element.querySelector('#adf-permission-inherited-label')).toBeDefined();
- expect(element.querySelector('#adf-permission-inherited-label')).not.toBeNull();
- });
+ describe('Inherited Permission', () => {
+ it('should show inherited details', async() => {
+ getNodeSpy.and.returnValue(of(fakeNodeInheritedOnly));
+ component.ngOnInit();
+ await fixture.detectChanges();
- describe('when it is a locally set permission', () => {
-
- it('should show locally set label for locally set permissions', () => {
- component.nodeId = 'fake-node-id';
- spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithOnlyLocally));
- spyOn(nodePermissionService, 'getGroupMemberByGroupName').and.returnValue(of(fakeSiteRoles));
- spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeSiteNodeResponse));
- fixture.detectChanges();
- expect(element.querySelector('#adf-permission-display-container')).not.toBeNull();
- expect(element.querySelector('#adf-permission-locallyset-label')).toBeDefined();
- expect(element.querySelector('#adf-permission-locallyset-label')).not.toBeNull();
+ expect(element.querySelector('.adf-inherit-container .mat-checked')).toBeDefined();
+ expect(element.querySelector('.adf-inherit-container h3').textContent.trim())
+ .toBe('PERMISSION_MANAGER.LABELS.INHERITED-PERMISSIONS PERMISSION_MANAGER.LABELS.ON');
+ expect(element.querySelector('span[title="total"]').textContent.trim())
+ .toBe('PERMISSION_MANAGER.LABELS.INHERITED-SUBTITLE');
});
- it('should show a dropdown with the possible roles', async(() => {
- component.nodeId = 'fake-node-id';
- spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithOnlyLocally));
- spyOn(nodePermissionService, 'getGroupMemberByGroupName').and.returnValue(of(fakeSiteRoles));
- spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeSiteNodeResponse));
- fixture.detectChanges();
- fixture.whenStable().then(() => {
- fixture.detectChanges();
- expect(element.querySelector('#adf-select-role-permission')).toBeDefined();
- expect(element.querySelector('#adf-select-role-permission')).not.toBeNull();
- const selectBox = fixture.debugElement.query(By.css(('#adf-select-role-permission .mat-select-trigger')));
- selectBox.triggerEventHandler('click', null);
- fixture.detectChanges();
- fixture.whenStable().then(() => {
- fixture.detectChanges();
- const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
- expect(options).not.toBeNull();
- expect(options.length).toBe(4);
- expect(options[0].nativeElement.innerText).toContain('ADF.ROLES.SITECOLLABORATOR');
- expect(options[1].nativeElement.innerText).toContain('ADF.ROLES.SITECONSUMER');
- expect(options[2].nativeElement.innerText).toContain('ADF.ROLES.SITECONTRIBUTOR');
- expect(options[3].nativeElement.innerText).toContain('ADF.ROLES.SITEMANAGER');
- });
- });
- }));
+ it('should toggle the inherited button', async() => {
+ getNodeSpy.and.returnValue(of(fakeNodeInheritedOnly));
+ component.ngOnInit();
+ await fixture.detectChanges();
- it('should show the settable roles if the node is not in any site', async(() => {
- component.nodeId = 'fake-node-id';
- spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithOnlyLocally));
- spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeEmptyResponse));
- fixture.detectChanges();
- fixture.whenStable().then(() => {
- fixture.detectChanges();
- expect(element.querySelector('#adf-select-role-permission')).toBeDefined();
- expect(element.querySelector('#adf-select-role-permission')).not.toBeNull();
- const selectBox = fixture.debugElement.query(By.css(('#adf-select-role-permission .mat-select-trigger')));
- selectBox.triggerEventHandler('click', null);
- fixture.detectChanges();
- fixture.whenStable().then(() => {
- fixture.detectChanges();
- const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
- expect(options).not.toBeNull();
- expect(options.length).toBe(5);
- expect(options[0].nativeElement.innerText).toContain('ADF.ROLES.CONTRIBUTOR');
- expect(options[1].nativeElement.innerText).toContain('ADF.ROLES.COLLABORATOR');
- expect(options[2].nativeElement.innerText).toContain('ADF.ROLES.COORDINATOR');
- expect(options[3].nativeElement.innerText).toContain('ADF.ROLES.EDITOR');
- expect(options[4].nativeElement.innerText).toContain('ADF.ROLES.CONSUMER');
- });
- });
- }));
+ expect(element.querySelector('.adf-inherit-container .mat-checked')).toBeDefined();
+ expect(element.querySelector('.adf-inherit-container h3').textContent.trim())
+ .toBe('PERMISSION_MANAGER.LABELS.INHERITED-PERMISSIONS PERMISSION_MANAGER.LABELS.ON');
+ expect(element.querySelector('span[title="total"]').textContent.trim())
+ .toBe('PERMISSION_MANAGER.LABELS.INHERITED-SUBTITLE');
- it('should update the role when another value is chosen', async(() => {
- component.nodeId = 'fake-node-id';
- spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithOnlyLocally));
- spyOn(nodeService, 'updateNode').and.returnValue(of({id: 'fake-updated-node'}));
- spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeEmptyResponse));
- component.update.subscribe((updatedPermission) => {
- expect(updatedPermission).not.toBeNull();
- expect(updatedPermission.name).toBe('Editor');
- expect(updatedPermission.authorityId).toBe('GROUP_EVERYONE');
- expect(updatedPermission.accessStatus).toBe('ALLOWED');
- });
+ spyOn(nodeService, 'updateNode').and.returnValue(of(fakeLocalPermission));
+
+ const slider = fixture.debugElement.query(By.css('mat-slide-toggle'));
+ slider.triggerEventHandler('change', { source: { checked: false } });
+ await fixture.detectChanges();
+
+ expect(element.querySelector('.adf-inherit-container .mat-checked')).toBe(null);
+ expect(element.querySelector('.adf-inherit-container h3').textContent.trim())
+ .toBe('PERMISSION_MANAGER.LABELS.INHERITED-PERMISSIONS PERMISSION_MANAGER.LABELS.OFF');
+ expect(element.querySelector('span[title="total"]').textContent.trim())
+ .toBe('PERMISSION_MANAGER.LABELS.INHERITED-SUBTITLE');
+ });
+
+ it('should not toggle inherited button for read only users', async () => {
+ getNodeSpy.and.returnValue(of(fakeReadOnlyNodeInherited));
+ component.ngOnInit();
+ await fixture.detectChanges();
+
+ expect(element.querySelector('.adf-inherit-container .mat-checked')).toBeDefined();
+ expect(element.querySelector('.adf-inherit-container h3').textContent.trim())
+ .toBe('PERMISSION_MANAGER.LABELS.INHERITED-PERMISSIONS PERMISSION_MANAGER.LABELS.ON');
+ expect(element.querySelector('span[title="total"]').textContent.trim())
+ .toBe('PERMISSION_MANAGER.LABELS.INHERITED-SUBTITLE');
+
+ spyOn(nodeService, 'updateNode').and.returnValue(of(fakeLocalPermission));
+
+ const slider = fixture.debugElement.query(By.css('mat-slide-toggle'));
+ slider.triggerEventHandler('change', { source: { checked: false } });
+ await fixture.detectChanges();
+
+ expect(element.querySelector('.adf-inherit-container .mat-checked')).toBeDefined();
+ expect(element.querySelector('.adf-inherit-container h3').textContent.trim())
+ .toBe('PERMISSION_MANAGER.LABELS.INHERITED-PERMISSIONS PERMISSION_MANAGER.LABELS.ON');
+ expect(element.querySelector('span[title="total"]').textContent.trim())
+ .toBe('PERMISSION_MANAGER.LABELS.INHERITED-SUBTITLE');
+ expect(document.querySelector('simple-snack-bar').textContent)
+ .toContain('PERMISSION_MANAGER.ERROR.NOT-ALLOWED');
+ });
+
+ });
+
+ describe('locally set permission', () => {
+ beforeEach(() => {
+ getNodeSpy.and.returnValue(of(fakeLocalPermission));
+ });
+
+ it('should show locally set permissions', async() => {
+ searchQuerySpy.and.returnValue(of(fakeSiteNodeResponse));
+ component.ngOnInit();
+
+ await fixture.detectChanges();
+ expect(element.querySelector('adf-user-name-column').textContent).toContain('GROUP_EVERYONE');
+ expect(element.querySelector('#adf-select-role-permission').textContent).toContain('Contributor');
+ });
+
+ it('should see the settable roles if the node is not in any site', async() => {
+ searchQuerySpy.and.returnValue(of(fakeSiteNodeResponse));
+ component.ngOnInit();
+
+ await fixture.detectChanges();
+ expect(element.querySelector('adf-user-name-column').textContent).toContain('GROUP_EVERYONE');
+ expect(element.querySelector('#adf-select-role-permission').textContent).toContain('Contributor');
+
+ const selectBox = fixture.debugElement.query(By.css(('[id="adf-select-role-permission"] .mat-select-trigger')));
+ selectBox.triggerEventHandler('click', null);
fixture.detectChanges();
- fixture.whenStable().then(() => {
- fixture.detectChanges();
- expect(element.querySelector('#adf-select-role-permission')).toBeDefined();
- expect(element.querySelector('#adf-select-role-permission')).not.toBeNull();
- const selectBox = fixture.debugElement.query(By.css(('#adf-select-role-permission .mat-select-trigger')));
- selectBox.triggerEventHandler('click', null);
- fixture.detectChanges();
- fixture.whenStable().then(() => {
- fixture.detectChanges();
- const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
- expect(options).not.toBeNull();
- expect(options.length).toBe(5);
- options[3].triggerEventHandler('click', {});
- fixture.detectChanges();
- expect(nodeService.updateNode).toHaveBeenCalled();
- });
- });
- }));
- });
+
+ const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
+ expect(options).not.toBeNull();
+ expect(options.length).toBe(4);
+ expect(options[0].nativeElement.innerText).toContain('ADF.ROLES.SITECOLLABORATOR');
+ expect(options[1].nativeElement.innerText).toContain('ADF.ROLES.SITECONSUMER');
+ expect(options[2].nativeElement.innerText).toContain('ADF.ROLES.SITECONTRIBUTOR');
+ expect(options[3].nativeElement.innerText).toContain('ADF.ROLES.SITEMANAGER');
+ });
+
+ it('should update the role when another value is chosen', async () => {
+ spyOn(nodeService, 'updateNode').and.returnValue(of({id: 'fake-uwpdated-node'}));
+ searchQuerySpy.and.returnValue(of(fakeEmptyResponse));
+ component.ngOnInit();
+
+ await fixture.detectChanges();
+
+ expect(element.querySelector('adf-user-name-column').textContent).toContain('GROUP_EVERYONE');
+ expect(element.querySelector('#adf-select-role-permission').textContent).toContain('Contributor');
+
+ const selectBox = fixture.debugElement.query(By.css(('[id="adf-select-role-permission"] .mat-select-trigger')));
+ selectBox.triggerEventHandler('click', null);
+ fixture.detectChanges();
+ const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
+ expect(options).not.toBeNull();
+ expect(options.length).toBe(5);
+ options[3].triggerEventHandler('click', {});
+ fixture.detectChanges();
+ expect(nodeService.updateNode).toHaveBeenCalledWith('f472543f-7218-403d-917b-7a5861257244', { permissions: { locallySet: [ { accessStatus: 'ALLOWED', name: 'Editor', authorityId: 'GROUP_EVERYONE' } ] } });
+ });
+
+ it('should delete the person', async () => {
+ spyOn(nodeService, 'updateNode').and.returnValue(of({id: 'fake-uwpdated-node'}));
+ searchQuerySpy.and.returnValue(of(fakeEmptyResponse));
+ component.ngOnInit();
+ await fixture.detectChanges();
+
+ expect(element.querySelector('adf-user-name-column').textContent).toContain('GROUP_EVERYONE');
+ expect(element.querySelector('#adf-select-role-permission').textContent).toContain('Contributor');
+
+ const showButton: HTMLButtonElement = element.querySelector('[data-automation-id="adf-delete-permission-button-GROUP_EVERYONE"]');
+ showButton.click();
+ fixture.detectChanges();
+
+ expect(nodeService.updateNode).toHaveBeenCalledWith('f472543f-7218-403d-917b-7a5861257244', { permissions: { locallySet: [ ] } });
+ });
+
+ });
});
diff --git a/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.ts b/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.ts
index 0a0ac23f9d..9ca9b85703 100644
--- a/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.ts
+++ b/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.ts
@@ -15,11 +15,11 @@
* limitations under the License.
*/
-import { Component, ViewEncapsulation, Input, OnInit, EventEmitter, Output } from '@angular/core';
-import { NodesApiService } from '@alfresco/adf-core';
-import { Node, PermissionElement } from '@alfresco/js-api';
+import { ObjectDataRow } from '@alfresco/adf-core';
+import { PermissionElement } from '@alfresco/js-api';
+import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
import { PermissionDisplayModel } from '../../models/permission.model';
-import { NodePermissionService } from '../../services/node-permission.service';
+import { PermissionListService } from './permission-list.service';
@Component({
selector: 'adf-permission-list',
@@ -27,70 +27,54 @@ import { NodePermissionService } from '../../services/node-permission.service';
styleUrls: ['./permission-list.component.scss'],
encapsulation: ViewEncapsulation.None
})
-export class PermissionListComponent implements OnInit {
-
+export class PermissionListComponent {
/** ID of the node whose permissions you want to show. */
@Input()
- nodeId: string = '';
+ nodeId: string;
/** Emitted when the permission is updated. */
@Output()
- update = new EventEmitter
();
+ update: EventEmitter;
/** Emitted when an error occurs. */
@Output()
- error = new EventEmitter();
+ error: EventEmitter;
- permissionList: PermissionDisplayModel[];
- settableRoles: any[];
- actualNode: Node;
+ selectedPermissions: PermissionDisplayModel[] = [];
- constructor(private nodeService: NodesApiService,
- private nodePermissionService: NodePermissionService) {
+ constructor(public readonly permissionList: PermissionListService) {
+ this.error = this.permissionList.errored;
+ this.update = this.permissionList.updated;
}
- ngOnInit() {
- this.fetchNodePermissions();
+ ngOnInit(): void {
+ this.permissionList.fetchPermission(this.nodeId);
}
- reload() {
- this.fetchNodePermissions();
+ openAddPermissionDialog() {
+ this.permissionList.updateNodePermissionByDialog();
}
- private fetchNodePermissions() {
- this.nodeService.getNode(this.nodeId).subscribe((node: Node) => {
- this.actualNode = node;
- this.permissionList = this.nodePermissionService.getNodePermissions(node);
-
- this.nodePermissionService.getNodeRoles(node).subscribe((settableList: string[]) => {
- this.settableRoles = settableList;
- });
- });
+ onSelect(selections: ObjectDataRow[]) {
+ this.selectedPermissions = selections.map((selection) => selection['obj']);
}
- saveNewRole(event: any, permissionRow: PermissionDisplayModel) {
- const updatedPermissionRole = this.buildUpdatedPermission(event.value, permissionRow);
-
- this.nodePermissionService.updatePermissionRole(this.actualNode, updatedPermissionRole)
- .subscribe(() => {
- this.update.emit(updatedPermissionRole);
- });
+ deleteSelection() {
+ this.permissionList.deletePermissions(this.selectedPermissions);
+ this.selectedPermissions = [];
}
- private buildUpdatedPermission(newRole: string, permissionRow: PermissionDisplayModel): PermissionElement {
- return {
- accessStatus: permissionRow.accessStatus,
- name: newRole,
- authorityId: permissionRow.authorityId
- };
+ updatePermission({role, permission}) {
+ this.permissionList.updateRole(role, permission);
}
- removePermission(permissionRow: PermissionDisplayModel) {
- this.nodePermissionService
- .removePermission(this.actualNode, permissionRow)
- .subscribe(
- node => this.update.emit(node),
- error => this.error.emit(error)
- );
+ deletePermission(permission: PermissionDisplayModel) {
+ this.selectedPermissions = [];
+ this.permissionList.deletePermission(permission);
+ }
+
+ updateAllPermission(role: string) {
+ this.permissionList.bulkRoleUpdate(role);
+ this.selectedPermissions = [];
}
}
diff --git a/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.service.spec.ts b/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.service.spec.ts
new file mode 100644
index 0000000000..c02fde694c
--- /dev/null
+++ b/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.service.spec.ts
@@ -0,0 +1,149 @@
+/*!
+ * @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 { NodesApiService, NotificationService, setupTestBed } from '@alfresco/adf-core';
+import { TestBed } from '@angular/core/testing';
+import { TranslateModule } from '@ngx-translate/core';
+import { of, throwError } from 'rxjs';
+import { PermissionListService } from './permission-list.service';
+import { ContentTestingModule } from '../../../testing/content.testing.module';
+import { NodePermissionService } from '../../services/node-permission.service';
+import { fakeNodeInheritedOnly, fakeNodeWithOnlyLocally } from '../../../mock/permission-list.component.mock';
+import { PermissionDisplayModel } from '../../models/permission.model';
+
+describe('PermissionListService', () => {
+ let service: PermissionListService;
+ let nodePermissionService: NodePermissionService;
+ let notificationService: NotificationService;
+ let nodesApiService: NodesApiService;
+ const localPermission = [new PermissionDisplayModel({
+ authorityId: 'GROUP_EVERYONE',
+ name: 'Contributor',
+ accessStatus: 'ALLOWED'
+ })
+ ];
+
+ setupTestBed({
+ imports: [
+ TranslateModule.forRoot(),
+ ContentTestingModule
+ ]
+ });
+
+ beforeEach(() => {
+ service = TestBed.inject(PermissionListService);
+ nodePermissionService = TestBed.inject(NodePermissionService);
+ notificationService = TestBed.inject(NotificationService);
+ nodesApiService = TestBed.inject(NodesApiService);
+ spyOn(notificationService, 'showInfo').and.stub();
+ spyOn(notificationService, 'showWarning').and.stub();
+ spyOn(notificationService, 'showError').and.stub();
+ });
+
+ it('fetch Permission', (done) => {
+ spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({node: fakeNodeWithOnlyLocally , roles: []}));
+
+ const subscription = service.data$.subscribe(({ node, inheritedPermissions, localPermissions, roles }) => {
+ expect(node).toBe(fakeNodeWithOnlyLocally);
+ expect(inheritedPermissions).toEqual([]);
+ expect(roles).toEqual([]);
+ expect(localPermissions).toEqual(localPermission);
+ subscription.unsubscribe();
+ done();
+ });
+
+ service.fetchPermission('fake node');
+ });
+
+ describe('toggle permission', () => {
+
+ it('should show error if user doesn\'t have permission to update node', () => {
+ const node = JSON.parse(JSON.stringify(fakeNodeInheritedOnly)), event = { source: { checked: false } };
+ node.allowableOperations = [];
+ spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({node , roles: []}));
+ spyOn(nodesApiService, 'updateNode').and.stub();
+ service.fetchPermission('fetch node');
+ service.toggleInherited(event as any);
+ expect(nodesApiService.updateNode).not.toHaveBeenCalled();
+ expect(notificationService.showError).toHaveBeenCalledWith('PERMISSION_MANAGER.ERROR.NOT-ALLOWED');
+ });
+
+ it('should show message after success toggle', () => {
+ const node = JSON.parse(JSON.stringify(fakeNodeInheritedOnly)), event = { source: { checked: false } };
+ const updateNode = JSON.parse(JSON.stringify(fakeNodeInheritedOnly));
+ updateNode.permissions.isInheritanceEnabled = false;
+ spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({node , roles: []}));
+ spyOn(nodesApiService, 'updateNode').and.returnValue(of(updateNode));
+ service.fetchPermission('fetch node');
+
+ service.toggleInherited(event as any);
+ expect(nodesApiService.updateNode).toHaveBeenCalled();
+ expect(notificationService.showInfo).toHaveBeenCalledWith('PERMISSION_MANAGER.MESSAGE.INHERIT-DISABLE-SUCCESS');
+ });
+
+ it('should show message for errored toggle', () => {
+ const node = JSON.parse(JSON.stringify(fakeNodeInheritedOnly)), event = { source: { checked: false } };
+ spyOn(nodesApiService, 'updateNode').and.returnValue(throwError('Failed to update'));
+ spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({node , roles: []}));
+ service.fetchPermission('fetch node');
+
+ service.toggleInherited(event as any);
+ expect(nodesApiService.updateNode).toHaveBeenCalled();
+ expect(notificationService.showWarning).toHaveBeenCalledWith('PERMISSION_MANAGER.MESSAGE.TOGGLE-PERMISSION-FAILED');
+ });
+ });
+
+ describe('delete permission', () => {
+ const node = JSON.parse(JSON.stringify(fakeNodeWithOnlyLocally));
+ beforeEach(() => {
+ spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({node , roles: []}));
+ service.fetchPermission('fetch node');
+ });
+
+ it('should be able to delete a permission', () => {
+ spyOn(nodePermissionService, 'removePermissions').and.returnValue(of(node));
+ service.deletePermissions(localPermission);
+ expect(notificationService.showInfo).toHaveBeenCalledWith('PERMISSION_MANAGER.MESSAGE.PERMISSION-BULK-DELETE-SUCCESS', null, { user: 0, group: 1 });
+ });
+
+ it('should show error message for errored delete operation', () => {
+ spyOn(nodePermissionService, 'removePermissions').and.returnValue(throwError('Failed operation'));
+ service.deletePermissions(localPermission);
+ expect(notificationService.showError).toHaveBeenCalledWith('PERMISSION_MANAGER.MESSAGE.PERMISSION-DELETE-FAIL');
+ });
+ });
+
+ describe('Bulk Role', () => {
+ const node = JSON.parse(JSON.stringify(fakeNodeWithOnlyLocally));
+ beforeEach(() => {
+ spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({node , roles: []}));
+ service.fetchPermission('fetch node');
+ });
+
+ it('should be able to update bulk permission', () => {
+ spyOn(nodePermissionService, 'updatePermissions').and.returnValue(of(node));
+ service.bulkRoleUpdate('fake-role');
+ expect(notificationService.showInfo).toHaveBeenCalledWith('PERMISSION_MANAGER.MESSAGE.PERMISSION-BULK-UPDATE-SUCCESS', null, { user: 0, group: 1 });
+ });
+
+ it('should show error message for errored operation', () => {
+ spyOn(nodePermissionService, 'updatePermissions').and.returnValue(throwError('Error'));
+ service.bulkRoleUpdate('fake-role');
+ expect(notificationService.showError).toHaveBeenCalledWith('PERMISSION_MANAGER.MESSAGE.PERMISSION-UPDATE-FAIL');
+ });
+ });
+});
diff --git a/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.service.ts b/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.service.ts
new file mode 100644
index 0000000000..7b2ab88de7
--- /dev/null
+++ b/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.service.ts
@@ -0,0 +1,207 @@
+/*!
+ * @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 { AllowableOperationsEnum, ContentService, NodesApiService, NotificationService } from '@alfresco/adf-core';
+import { Node, PermissionElement } from '@alfresco/js-api';
+import { EventEmitter, Injectable } from '@angular/core';
+import { MatSlideToggleChange } from '@angular/material/slide-toggle';
+import { BehaviorSubject, forkJoin, Observable, of, Subject } from 'rxjs';
+import { finalize, map, switchMap } from 'rxjs/operators';
+import { RoleModel } from '../../models/role.model';
+import { PermissionDisplayModel } from '../../models/permission.model';
+import { NodePermissionsModel } from '../../models/member.model';
+import { NodePermissionService } from '../../services/node-permission.service';
+import { NodePermissionDialogService } from '../../services/node-permission-dialog.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class PermissionListService {
+ updated = new EventEmitter();
+ errored = new EventEmitter();
+
+ loading$: BehaviorSubject = new BehaviorSubject(true);
+ error$: Subject = new Subject();
+ nodeWithRoles$: Subject<{ node: Node, roles: RoleModel[] }> = new Subject();
+ data$: Observable = this.nodeWithRoles$.pipe(
+ map(({ node, roles}) => {
+ return {
+ node,
+ roles,
+ inheritedPermissions: this.nodePermissionService.getInheritedPermission(node),
+ localPermissions: this.nodePermissionService.getLocalPermissions(node),
+ allPermission: this.nodePermissionService.getNodePermissions(node)
+ };
+ })
+ );
+
+ private node: Node;
+ private roles: RoleModel[];
+
+ constructor(
+ private nodeService: NodesApiService,
+ private nodePermissionService: NodePermissionService,
+ private nodePermissionDialogService: NodePermissionDialogService,
+ private contentService: ContentService,
+ private notificationService: NotificationService
+ ) {}
+
+ fetchPermission(nodeId: string) {
+ this.loading$.next(true);
+ this.nodePermissionService.getNodeWithRoles(nodeId)
+ .pipe(finalize(() => this.loading$.next(false)))
+ .subscribe(
+ ({ node, roles }) => {
+ this.node = node;
+ this.roles = roles;
+ this.nodeWithRoles$.next({ node, roles });
+ },
+ () => this.error$.next(true)
+ );
+ }
+
+ toggleInherited(change: MatSlideToggleChange) {
+ if (this.contentService.hasAllowableOperations(this.node, AllowableOperationsEnum.UPDATEPERMISSIONS)) {
+ const nodeBody = {
+ permissions: {
+ isInheritanceEnabled: !this.node.permissions.isInheritanceEnabled
+ }
+ };
+ this.nodeService.updateNode(this.node.id, nodeBody, {include: ['permissions']})
+ .subscribe(
+ (nodeUpdated: Node) => {
+ const message = nodeUpdated.permissions.isInheritanceEnabled ? 'PERMISSION_MANAGER.MESSAGE.INHERIT-ENABLE-SUCCESS' : 'PERMISSION_MANAGER.MESSAGE.INHERIT-DISABLE-SUCCESS';
+ this.notificationService.showInfo(message);
+ nodeUpdated.permissions.inherited = nodeUpdated.permissions?.inherited ?? [];
+ this.reloadNode(nodeUpdated);
+ },
+ () => {
+ change.source.checked = this.node.permissions.isInheritanceEnabled;
+ this.notificationService.showWarning('PERMISSION_MANAGER.MESSAGE.TOGGLE-PERMISSION-FAILED');
+ }
+ );
+ } else {
+ change.source.checked = this.node.permissions.isInheritanceEnabled;
+ this.notificationService.showError('PERMISSION_MANAGER.ERROR.NOT-ALLOWED');
+ }
+ }
+
+ updateNodePermissionByDialog() {
+ this.nodePermissionDialogService
+ .openAddPermissionDialog(this.node, this.roles)
+ .pipe(
+ switchMap(selection => {
+ const total = selection.length;
+ const group = selection.filter(({authorityId}) => this.isGroup(authorityId)).length;
+ return forkJoin({
+ user: of(total - group),
+ group: of(group),
+ node: this.nodePermissionService.updateNodePermissions(this.node.id, selection)
+ });
+ })
+ )
+ .subscribe(({ user, group, node}) => {
+ this.notificationService.showInfo( 'PERMISSION_MANAGER.MESSAGE.PERMISSION-ADD-SUCCESS', null, { user, group });
+ this.reloadNode(node);
+ },
+ () => {
+ this.notificationService.showError( 'PERMISSION_MANAGER.MESSAGE.PERMISSION-ADD-FAIL');
+ this.reloadNode();
+ }
+ );
+ }
+
+ deletePermissions(permissions: PermissionElement[]) {
+ this.nodePermissionService.removePermissions(this.node, permissions)
+ .subscribe((node) => {
+ const total = permissions.length;
+ const group = permissions.filter(({authorityId}) => this.isGroup(authorityId)).length;
+ this.notificationService.showInfo('PERMISSION_MANAGER.MESSAGE.PERMISSION-BULK-DELETE-SUCCESS', null, {user: total - group, group});
+ this.reloadNode(node);
+ },
+ () => {
+ this.notificationService.showError('PERMISSION_MANAGER.MESSAGE.PERMISSION-DELETE-FAIL');
+ this.reloadNode();
+ }
+ );
+ }
+
+ updateRole(role: string, permission: PermissionDisplayModel) {
+ const updatedPermissionRole = this.buildUpdatedPermission(role, permission);
+ this.nodePermissionService.updatePermissionRole(this.node, updatedPermissionRole)
+ .subscribe((node) => {
+ this.notificationService.showInfo('PERMISSION_MANAGER.MESSAGE.PERMISSION-UPDATE-SUCCESS');
+ this.reloadNode(node);
+ this.updated.emit(permission);
+ },
+ () => {
+ this.notificationService.showError('PERMISSION_MANAGER.MESSAGE.PERMISSION-UPDATE-FAIL');
+ this.reloadNode();
+ this.errored.emit(permission);
+ }
+ );
+ }
+
+ bulkRoleUpdate(role: string) {
+ const permissions = [...this.node.permissions.locallySet] .map((permission) => this.buildUpdatedPermission(role, permission));
+ this.nodePermissionService.updatePermissions(this.node, permissions)
+ .subscribe((node) => {
+ const total = permissions.length;
+ const group = permissions.filter(({authorityId}) => this.isGroup(authorityId)).length;
+ this.notificationService.showInfo('PERMISSION_MANAGER.MESSAGE.PERMISSION-BULK-UPDATE-SUCCESS', null, {user: total - group, group});
+ this.reloadNode(node);
+ },
+ () => {
+ this.notificationService.showError('PERMISSION_MANAGER.MESSAGE.PERMISSION-UPDATE-FAIL');
+ this.reloadNode();
+ }
+ );
+ }
+
+ deletePermission(permission: PermissionDisplayModel) {
+ this.nodePermissionService
+ .removePermission(this.node, permission)
+ .subscribe((node) => {
+ this.notificationService.showInfo('PERMISSION_MANAGER.MESSAGE.PERMISSION-DELETE-SUCCESS');
+ this.reloadNode(node);
+ },
+ () => {
+ this.notificationService.showError('PERMISSION_MANAGER.MESSAGE.PERMISSION-DELETE-FAIL');
+ this.reloadNode();
+ }
+ );
+ }
+
+ private buildUpdatedPermission(role: string, permission: PermissionElement): PermissionElement {
+ return {
+ accessStatus: permission.accessStatus,
+ name: role,
+ authorityId: permission.authorityId
+ };
+ }
+
+ private reloadNode(node?: Node) {
+ if (node != null) {
+ Object.assign(this.node.permissions, node.permissions);
+ }
+ this.nodeWithRoles$.next({ node: this.node, roles: this.roles });
+ }
+
+ private isGroup(authorityId) {
+ return authorityId.startsWith('GROUP_') || authorityId.startsWith('ROLE_');
+ }
+}
diff --git a/lib/content-services/src/lib/permission-manager/components/pop-over.directive.ts b/lib/content-services/src/lib/permission-manager/components/pop-over.directive.ts
new file mode 100644
index 0000000000..7fc50b7518
--- /dev/null
+++ b/lib/content-services/src/lib/permission-manager/components/pop-over.directive.ts
@@ -0,0 +1,104 @@
+/*!
+ * @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 { AfterViewInit, Directive, ElementRef, Input, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from '@angular/core';
+import { ConnectionPositionPair, Overlay, OverlayRef } from '@angular/cdk/overlay';
+import { TemplatePortal } from '@angular/cdk/portal';
+import { takeUntil } from 'rxjs/operators';
+import { Subject } from 'rxjs';
+
+@Directive({
+ selector: '[adf-pop-over]',
+ exportAs: 'adfPopOver'
+
+})
+export class PopOverDirective implements OnInit, OnDestroy, AfterViewInit {
+ get open(): boolean {
+ return this._open;
+ }
+
+ @Input('adf-pop-over') popOver!: TemplateRef