From 79789cb070414d77b334347d677023b43c2662c6 Mon Sep 17 00:00:00 2001 From: Vito Date: Sun, 8 Apr 2018 16:25:51 +0100 Subject: [PATCH] [ADF-2553] added select box and updating role (#3148) * [ADF-2553] start adding dropdown * [ADF-2553] added select box and updating role * [ADF-2553] added some fixes * [ADF-2554] added test for component phase 1 * [ADF-2553] fixed error and added tests * [ADF-2553] added documentation and improved api call --- .../node-permission.service.md | 38 ++++ .../permission-list.component.md | 10 +- lib/content-services/i18n/en.json | 2 +- .../mock/permission-list.component.mock.ts | 173 ++++++++++++++++++ .../permission-list.component.html | 21 ++- .../permission-list.component.spec.ts | 128 +++++++++++-- .../permission-list.component.ts | 35 +++- .../models/permission.model.ts | 15 +- .../permission-manager.module.ts | 4 + .../permission-manager/public-api.ts | 2 +- .../services/node-permission.service.spec.ts | 98 ++++++++++ .../services/node-permission.service.ts | 97 ++++++++++ lib/core/services/alfresco-api.service.ts | 6 +- 13 files changed, 599 insertions(+), 30 deletions(-) create mode 100644 docs/content-services/node-permission.service.md create mode 100644 lib/content-services/permission-manager/services/node-permission.service.spec.ts create mode 100644 lib/content-services/permission-manager/services/node-permission.service.ts diff --git a/docs/content-services/node-permission.service.md b/docs/content-services/node-permission.service.md new file mode 100644 index 0000000000..2b6534ed1d --- /dev/null +++ b/docs/content-services/node-permission.service.md @@ -0,0 +1,38 @@ +--- +Added: v2.0.0 +Status: Active +--- +# Node Permission service + +Manages the role permissions for the content nodes + +## Methods + +- `getNodeRoles(node: MinimalNodeEntryEntity): Observable` + Gets a list of roles for the current node. + - `node` - the target node +- `updatePermissionRoles(node: MinimalNodeEntryEntity, updatedPermissionRole: PermissionElement): Observable` + Update the given permission for the related node + - `node` - the target node + - `updatedPermissionRole` the permission role to update/add + +- `getGroupMemeberByGroupName(groupName: string, opts?: any): Observable` + Perform a call to the groups api to retrieve all the members related to that group name. + - `groupName` the members group name + - `opts` additional parameters to perform the call + + +## Details + +Content Services supports +This service needs the support for the groups api fo content services that is available from version 5.2.1 + +See the +[Groups API](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/GroupssApi.md) +in the Alfresco JS API for more information about the types returned by Tag +service methods and for the implementation of the REST API the service is +based on. + +## See also + +- [Permission list component](permission-list.component.md) diff --git a/docs/content-services/permission-list.component.md b/docs/content-services/permission-list.component.md index 1d1784ee24..0ec5c73a78 100644 --- a/docs/content-services/permission-list.component.md +++ b/docs/content-services/permission-list.component.md @@ -23,7 +23,15 @@ Shows node permissions as a table. | ---- | ---- | ------------- | ----------- | | nodeId | `string` | `null` | ID of the node whose permissions you want to show. | +### Events + +| Name | Type | Description | +| ---- | ---- | ----------- | +| update | `PermissionElement` | Emitted when the permission is updated. | + ## Details This component uses a [Datatable component](../core/datatable.component.md) to show the -permissions retrieved from the [Node service](../core/node.service.md). \ No newline at end of file +permissions retrieved from the [Node service](../core/node.service.md). +For the locallyset permissions a role dropdown will be showed allowing the user to change it. +When user select a new value, the permission role is automatically updated and the `update` event is thrown. \ No newline at end of file diff --git a/lib/content-services/i18n/en.json b/lib/content-services/i18n/en.json index d579903150..267f7a3d51 100644 --- a/lib/content-services/i18n/en.json +++ b/lib/content-services/i18n/en.json @@ -234,7 +234,7 @@ "PERMISSION_DISPLAY": { "INHERITED" : "Inherited", "AUTHORITY_ID" : "Authority ID", - "NAME": "Name", + "ROLE": "Role", "LOCALLY_SET" : "Locally set" } } diff --git a/lib/content-services/mock/permission-list.component.mock.ts b/lib/content-services/mock/permission-list.component.mock.ts index ab05d6fb9a..4da39ffd29 100644 --- a/lib/content-services/mock/permission-list.component.mock.ts +++ b/lib/content-services/mock/permission-list.component.mock.ts @@ -24,6 +24,28 @@ export const fakeNodeWithPermissions: any = { 'app:uifacets' ], 'createdAt': '2017-11-16T16:29:38.638+0000', + 'path': { + 'name': '/Company Home/Sites/testsite/documentLibrary', + 'isComplete': true, + 'elements': [ + { + 'id': '2be275a1-b00d-4e45-83d8-66af43ac2252', + 'name': 'Company Home' + }, + { + 'id': '1be10a97-6eb9-4b60-b6c6-1673900e9631', + 'name': 'Sites' + }, + { + 'id': 'e002c740-b8f9-482a-a554-8fff4e4c9dc0', + 'name': 'testsite' + }, + { + 'id': '71626fae-0c04-4d0c-a129-20fa4c178716', + 'name': 'documentLibrary' + } + ] + }, 'isFolder': true, 'isFile': false, 'createdByUser': { @@ -87,6 +109,28 @@ export const fakeNodeInheritedOnly: any = { 'app:uifacets' ], 'createdAt': '2017-11-16T16:29:38.638+0000', + 'path': { + 'name': '/Company Home/Sites/testsite/documentLibrary', + 'isComplete': true, + 'elements': [ + { + 'id': '2be275a1-b00d-4e45-83d8-66af43ac2252', + 'name': 'Company Home' + }, + { + 'id': '1be10a97-6eb9-4b60-b6c6-1673900e9631', + 'name': 'Sites' + }, + { + 'id': 'e002c740-b8f9-482a-a554-8fff4e4c9dc0', + 'name': 'testsite' + }, + { + 'id': '71626fae-0c04-4d0c-a129-20fa4c178716', + 'name': 'documentLibrary' + } + ] + }, 'isFolder': true, 'isFile': false, 'createdByUser': { @@ -133,6 +177,7 @@ export const fakeNodeInheritedOnly: any = { 'app:icon': 'space-icon-default' } }; + export const fakeNodeWithOnlyLocally: any = { 'aspectNames': [ 'cm:auditable', @@ -142,6 +187,28 @@ export const fakeNodeWithOnlyLocally: any = { 'app:uifacets' ], 'createdAt': '2017-11-16T16:29:38.638+0000', + 'path': { + 'name': '/Company Home/Sites/testsite/documentLibrary', + 'isComplete': true, + 'elements': [ + { + 'id': '2be275a1-b00d-4e45-83d8-66af43ac2252', + 'name': 'Company Home' + }, + { + 'id': '1be10a97-6eb9-4b60-b6c6-1673900e9631', + 'name': 'Sites' + }, + { + 'id': 'e002c740-b8f9-482a-a554-8fff4e4c9dc0', + 'name': 'testsite' + }, + { + 'id': '71626fae-0c04-4d0c-a129-20fa4c178716', + 'name': 'documentLibrary' + } + ] + }, 'isFolder': true, 'isFile': false, 'createdByUser': { @@ -183,3 +250,109 @@ export const fakeNodeWithOnlyLocally: any = { 'app:icon': 'space-icon-default' } }; + +export const fakeSiteNodeResponse: any = { + 'list': { + 'pagination': { + 'count': 1, + 'hasMoreItems': false, + 'totalItems': 1, + 'skipCount': 0, + 'maxItems': 100 + }, + 'context': {}, + 'entries': [ + { + 'entry': { + 'isLink': false, + 'isFile': false, + 'createdByUser': { + 'id': 'admin', + 'displayName': 'Administrator' + }, + 'modifiedAt': '2018-03-22T15:40:10.093+0000', + 'nodeType': 'st:site', + 'parentId': '1be10a97-6eb9-4b60-b6c6-1673900e9631', + 'aspectNames': [ + 'cm:tagscope', + 'cm:titled', + 'cm:auditable' + ], + 'createdAt': '2018-03-22T15:39:50.821+0000', + 'isFolder': true, + 'search': { + 'score': 10.292057 + }, + 'modifiedByUser': { + 'id': 'admin', + 'displayName': 'Administrator' + }, + 'name': 'testsite', + 'location': 'nodes', + 'id': 'e002c740-b8f9-482a-a554-8fff4e4c9dc0', + 'properties': { + 'st:siteVisibility': 'PUBLIC', + 'cm:title': 'TEST_SITE', + 'st:sitePreset': 'site-dashboard' + } + } + } + ] + } +}; + +export const fakeSiteRoles: any = { + 'list': { + 'pagination': { + 'count': 4, + 'hasMoreItems': false, + 'totalItems': 4, + 'skipCount': 0, + 'maxItems': 100 + }, + 'entries': [ + { + 'entry': { + 'displayName': 'site_testsite_SiteCollaborator', + 'id': 'GROUP_site_testsite_SiteCollaborator', + 'memberType': 'GROUP' + } + }, + { + 'entry': { + 'displayName': 'site_testsite_SiteConsumer', + 'id': 'GROUP_site_testsite_SiteConsumer', + 'memberType': 'GROUP' + } + }, + { + 'entry': { + 'displayName': 'site_testsite_SiteContributor', + 'id': 'GROUP_site_testsite_SiteContributor', + 'memberType': 'GROUP' + } + }, + { + 'entry': { + 'displayName': 'site_testsite_SiteManager', + 'id': 'GROUP_site_testsite_SiteManager', + 'memberType': 'GROUP' + } + } + ] + } +}; + +export const fakeEmptyResponse: any = { + 'list': { + 'pagination': { + 'count': 0, + 'hasMoreItems': false, + 'totalItems': 0, + 'skipCount': 0, + 'maxItems': 100 + }, + 'context': {}, + 'entries': [] + } +}; diff --git a/lib/content-services/permission-manager/components/permission-list/permission-list.component.html b/lib/content-services/permission-manager/components/permission-list/permission-list.component.html index ed87390d6c..8f11da7c2e 100644 --- a/lib/content-services/permission-manager/components/permission-list/permission-list.component.html +++ b/lib/content-services/permission-manager/components/permission-list/permission-list.component.html @@ -4,12 +4,27 @@ - + + + + + + {{ role }} + + + + + {{entry.data.getValue(entry.row, entry.col)}} + + + - {{'PERMISSION_MANAGER.PERMISSION_DISPLAY.INHERITED' | translate}} diff --git a/lib/content-services/permission-manager/components/permission-list/permission-list.component.spec.ts b/lib/content-services/permission-manager/components/permission-list/permission-list.component.spec.ts index a853706bd2..3fe8b4f1a3 100644 --- a/lib/content-services/permission-manager/components/permission-list/permission-list.component.spec.ts +++ b/lib/content-services/permission-manager/components/permission-list/permission-list.component.spec.ts @@ -17,9 +17,16 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { PermissionListComponent } from './permission-list.component'; -import { NodesApiService } from '@alfresco/adf-core'; +import { By } from '@angular/platform-browser'; +import { NodesApiService, SearchService } from '@alfresco/adf-core'; import { Observable } from 'rxjs/Observable'; -import { fakeNodeWithPermissions, fakeNodeInheritedOnly, fakeNodeWithOnlyLocally } from '../../../mock/permission-list.component.mock'; +import { NodePermissionService } from '../../services/node-permission.service'; +import { fakeNodeWithPermissions, + fakeNodeInheritedOnly, + fakeNodeWithOnlyLocally, + fakeSiteNodeResponse, + fakeSiteRoles, + fakeEmptyResponse } from '../../../mock/permission-list.component.mock'; describe('PermissionDisplayComponent', () => { @@ -27,18 +34,22 @@ describe('PermissionDisplayComponent', () => { let component: PermissionListComponent; let element: HTMLElement; let nodeService: NodesApiService; + let nodePermissionService: NodePermissionService; + let searchApiService: SearchService; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ PermissionListComponent ], - providers: [NodesApiService] + providers: [NodesApiService, NodePermissionService] }).compileComponents().then(() => { fixture = TestBed.createComponent(PermissionListComponent); component = fixture.componentInstance; element = fixture.nativeElement; nodeService = TestBed.get(NodesApiService); + nodePermissionService = TestBed.get(NodePermissionService); + searchApiService = TestBed.get(SearchService); }); })); @@ -47,35 +58,126 @@ describe('PermissionDisplayComponent', () => { TestBed.resetTestingModule(); })); - it('should be able to render the component', async() => { + it('should be able to render the component', () => { fixture.detectChanges(); expect(element.querySelector('#adf-permission-display-container')).not.toBeNull(); }); - it('should show the node permissions', async() => { + it('should show the node permissions', () => { component.nodeId = 'fake-node-id'; spyOn(nodeService, 'getNode').and.returnValue(Observable.of(fakeNodeWithPermissions)); + spyOn(searchApiService, 'searchByQueryBody').and.returnValue(Promise.resolve(fakeEmptyResponse)); fixture.detectChanges(); expect(element.querySelector('#adf-permission-display-container')).not.toBeNull(); expect(element.querySelectorAll('.adf-datatable-row').length).toBe(4); }); - it('should show inherited label for inherited permissions', async() => { + it('should show inherited label for inherited permissions', () => { component.nodeId = 'fake-node-id'; spyOn(nodeService, 'getNode').and.returnValue(Observable.of(fakeNodeInheritedOnly)); + spyOn(searchApiService, 'searchByQueryBody').and.returnValue(Promise.resolve(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(); }); - it('should show locally set label for locally set permissions', async() => { - component.nodeId = 'fake-node-id'; - spyOn(nodeService, 'getNode').and.returnValue(Observable.of(fakeNodeWithOnlyLocally)); - 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(); + 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(Observable.of(fakeNodeWithOnlyLocally)); + spyOn(nodePermissionService, 'getGroupMemeberByGroupName').and.returnValue(Observable.of(fakeSiteRoles)); + spyOn(searchApiService, 'searchByQueryBody').and.returnValue(Promise.resolve(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(); + }); + + it('should show a dropdown with the possible roles', async(() => { + component.nodeId = 'fake-node-id'; + spyOn(nodeService, 'getNode').and.returnValue(Observable.of(fakeNodeWithOnlyLocally)); + spyOn(nodePermissionService, 'getGroupMemeberByGroupName').and.returnValue(Observable.of(fakeSiteRoles)); + spyOn(searchApiService, 'searchByQueryBody').and.returnValue(Promise.resolve(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(); + let options: any = fixture.debugElement.queryAll(By.css('mat-option')); + expect(options).not.toBeNull(); + expect(options.length).toBe(4); + expect(options[0].nativeElement.innerText).toContain('SiteCollaborator'); + expect(options[1].nativeElement.innerText).toContain('SiteConsumer'); + expect(options[2].nativeElement.innerText).toContain('SiteContributor'); + expect(options[3].nativeElement.innerText).toContain('SiteManager'); + }); + }); + })); + + 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(Observable.of(fakeNodeWithOnlyLocally)); + spyOn(searchApiService, 'searchByQueryBody').and.returnValue(Promise.resolve(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(); + let options: any = fixture.debugElement.queryAll(By.css('mat-option')); + expect(options).not.toBeNull(); + expect(options.length).toBe(5); + expect(options[0].nativeElement.innerText).toContain('Contributor'); + expect(options[1].nativeElement.innerText).toContain('Collaborator'); + expect(options[2].nativeElement.innerText).toContain('Coordinator'); + expect(options[3].nativeElement.innerText).toContain('Editor'); + expect(options[4].nativeElement.innerText).toContain('Consumer'); + }); + }); + })); + + it('should update the role when another value is chosen', async(() => { + component.nodeId = 'fake-node-id'; + spyOn(nodeService, 'getNode').and.returnValue(Observable.of(fakeNodeWithOnlyLocally)); + spyOn(nodeService, 'updateNode').and.returnValue(Observable.of({id: 'fake-updated-node'})); + spyOn(searchApiService, 'searchByQueryBody').and.returnValue(Promise.resolve(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'); + }); + 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(); + let 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(); + }); + }); + })); + }); }); diff --git a/lib/content-services/permission-manager/components/permission-list/permission-list.component.ts b/lib/content-services/permission-manager/components/permission-list/permission-list.component.ts index f7e8327153..e741ebce98 100644 --- a/lib/content-services/permission-manager/components/permission-list/permission-list.component.ts +++ b/lib/content-services/permission-manager/components/permission-list/permission-list.component.ts @@ -15,10 +15,11 @@ * limitations under the License. */ -import { Component, ViewEncapsulation, Input, OnInit } from '@angular/core'; +import { Component, ViewEncapsulation, Input, OnInit, EventEmitter, Output } from '@angular/core'; import { NodesApiService } from '@alfresco/adf-core'; -import { MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { MinimalNodeEntryEntity, PermissionElement } from 'alfresco-js-api'; import { PermissionDisplayModel } from '../../models/permission.model'; +import { NodePermissionService } from '../../services/node-permission.service'; @Component({ selector: 'adf-permission-list', @@ -31,9 +32,15 @@ export class PermissionListComponent implements OnInit { @Input() nodeId: string = ''; - permissionList: PermissionDisplayModel[]; + @Output() + update: EventEmitter = new EventEmitter(); - constructor(private nodeService: NodesApiService) { + permissionList: PermissionDisplayModel[]; + settableRoles: any[]; + actualNode: MinimalNodeEntryEntity; + + constructor(private nodeService: NodesApiService, + private nodePermissionService: NodePermissionService) { } @@ -47,7 +54,11 @@ export class PermissionListComponent implements OnInit { private fetchNodePermissions() { this.nodeService.getNode(this.nodeId).subscribe((node: MinimalNodeEntryEntity) => { + this.actualNode = node; this.permissionList = this.getPermissionList(node); + this.nodePermissionService.getNodeRoles(node).subscribe((settableList: string[]) => { + this.settableRoles = settableList; + }); }); } @@ -69,4 +80,20 @@ export class PermissionListComponent implements OnInit { return allPermissions; } + saveNewRole(event: any, permissionRow: PermissionDisplayModel) { + let updatedPermissionRole: PermissionElement = this.buildUpdatedPermission(event.value, permissionRow); + this.nodePermissionService.updatePermissionRoles(this.actualNode, updatedPermissionRole) + .subscribe((node: MinimalNodeEntryEntity) => { + this.update.emit(updatedPermissionRole); + }); + } + + private buildUpdatedPermission(newRole: string, permissionRow: PermissionDisplayModel): PermissionElement { + let permissionRole: PermissionElement = {}; + permissionRole.accessStatus = permissionRow.accessStatus; + permissionRole.name = newRole; + permissionRole.authorityId = permissionRow.authorityId; + return permissionRole; + } + } diff --git a/lib/content-services/permission-manager/models/permission.model.ts b/lib/content-services/permission-manager/models/permission.model.ts index 41767a7d4a..7e9600e31c 100644 --- a/lib/content-services/permission-manager/models/permission.model.ts +++ b/lib/content-services/permission-manager/models/permission.model.ts @@ -15,19 +15,22 @@ * limitations under the License. */ -export class PermissionDisplayModel { - accessStatus: string; - authorityId: string; - name: string; +import { PermissionElement } from 'alfresco-js-api'; + +export class PermissionDisplayModel implements PermissionElement { + + authorityId?: string; + name?: string; + accessStatus?: PermissionElement.AccessStatusEnum; isInherited: boolean = false; icon: string; constructor(obj?: any) { if (obj) { - this.accessStatus = obj.accessStatus; this.authorityId = obj.authorityId; this.name = obj.name; - this.isInherited = obj.isInherited; + this.accessStatus = obj.accessStatus; + this.isInherited = obj.isInherited !== null && obj.isInherited !== undefined ? obj.isInherited : false; this.icon = obj.icon ? obj.icon : 'lock_open'; } } diff --git a/lib/content-services/permission-manager/permission-manager.module.ts b/lib/content-services/permission-manager/permission-manager.module.ts index 370edef4ce..487d29fcc3 100644 --- a/lib/content-services/permission-manager/permission-manager.module.ts +++ b/lib/content-services/permission-manager/permission-manager.module.ts @@ -23,6 +23,7 @@ import { MaterialModule } from '../material.module'; import { PermissionListComponent } from './components/permission-list/permission-list.component'; import { DataTableModule, DataColumnModule } from '@alfresco/adf-core'; import { InheritPermissionDirective } from './components/inherited-button.directive'; +import { NodePermissionService } from './services/node-permission.service'; @NgModule({ imports: [ @@ -38,6 +39,9 @@ import { InheritPermissionDirective } from './components/inherited-button.direct PermissionListComponent, InheritPermissionDirective ], + providers: [ + NodePermissionService + ], exports: [ PermissionListComponent, InheritPermissionDirective diff --git a/lib/content-services/permission-manager/public-api.ts b/lib/content-services/permission-manager/public-api.ts index 7ad71379e1..4b1f46f576 100644 --- a/lib/content-services/permission-manager/public-api.ts +++ b/lib/content-services/permission-manager/public-api.ts @@ -17,5 +17,5 @@ export * from './components/permission-list/permission-list.component'; export * from './components/inherited-button.directive'; - + export * from './services/node-permission.service'; export * from './models/permission.model'; diff --git a/lib/content-services/permission-manager/services/node-permission.service.spec.ts b/lib/content-services/permission-manager/services/node-permission.service.spec.ts new file mode 100644 index 0000000000..212fa0720d --- /dev/null +++ b/lib/content-services/permission-manager/services/node-permission.service.spec.ts @@ -0,0 +1,98 @@ +/*! + * @license + * Copyright 2016 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 { async, TestBed } from '@angular/core/testing'; +import { NodePermissionService } from './node-permission.service'; +import { AlfrescoApiService, SearchService, NodesApiService } from '@alfresco/adf-core'; +import { MinimalNodeEntryEntity, PermissionElement } from 'alfresco-js-api'; +import { Observable } from 'rxjs/Observable'; +import { fakeEmptyResponse, fakeNodeWithOnlyLocally, fakeSiteRoles, fakeSiteNodeResponse } from '../../mock/permission-list.component.mock'; + +describe('NodePermissionService', () => { + + let service: NodePermissionService, + nodeService: NodesApiService, + searchApiService: SearchService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + providers: [ + AlfrescoApiService, + NodePermissionService, SearchService, NodesApiService + ] + }).compileComponents(); + })); + + beforeEach(() => { + service = TestBed.get(NodePermissionService); + searchApiService = TestBed.get(SearchService); + nodeService = TestBed.get(NodesApiService); + }); + + afterEach(() => { + TestBed.resetTestingModule(); + }); + + function returnUpdatedNode(nodeId, nodeBody) { + let fakeNode: MinimalNodeEntryEntity = {}; + fakeNode.id = 'fake-updated-node'; + fakeNode.permissions = nodeBody.permissions; + return Observable.of(fakeNode); + } + + it('should return a list of roles taken from the site groups', async(() => { + spyOn(searchApiService, 'searchByQueryBody').and.returnValue(Promise.resolve(fakeSiteNodeResponse)); + spyOn(service, 'getGroupMemeberByGroupName').and.returnValue(Observable.of(fakeSiteRoles)); + + service.getNodeRoles(fakeNodeWithOnlyLocally).subscribe((roleArray: string[]) => { + expect(roleArray).not.toBeNull(); + expect(roleArray.length).toBe(4); + expect(roleArray[0]).toBe('SiteCollaborator'); + }); + })); + + it('should return a list of settable if node has no site', async(() => { + spyOn(searchApiService, 'searchByQueryBody').and.returnValue(Promise.resolve(fakeEmptyResponse)); + + service.getNodeRoles(fakeNodeWithOnlyLocally).subscribe((roleArray: string[]) => { + expect(roleArray).not.toBeNull(); + expect(roleArray.length).toBe(5); + expect(roleArray[0]).toBe('Contributor'); + }); + })); + + it('should be able to update a locally set permission role', async(() => { + const fakeAccessStatus: any = 'DENIED'; + const fakePermission: PermissionElement = { + 'authorityId': 'GROUP_EVERYONE', + 'name': 'Contributor', + 'accessStatus' : fakeAccessStatus + }; + + spyOn(nodeService, 'updateNode').and.callFake((nodeId, permissionBody) => returnUpdatedNode(nodeId, permissionBody)); + + service.updatePermissionRoles(fakeNodeWithOnlyLocally, fakePermission).subscribe((node: MinimalNodeEntryEntity) => { + expect(node).not.toBeNull(); + expect(node.id).toBe('fake-updated-node'); + expect(node.permissions.locallySet.length).toBe(1); + expect(node.permissions.locallySet[0].authorityId).toBe(fakePermission.authorityId); + expect(node.permissions.locallySet[0].name).toBe(fakePermission.name); + expect(node.permissions.locallySet[0].accessStatus).toBe(fakePermission.accessStatus); + }); + })); + +}); diff --git a/lib/content-services/permission-manager/services/node-permission.service.ts b/lib/content-services/permission-manager/services/node-permission.service.ts new file mode 100644 index 0000000000..3f764513df --- /dev/null +++ b/lib/content-services/permission-manager/services/node-permission.service.ts @@ -0,0 +1,97 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { AlfrescoApiService, SearchService, NodesApiService } from '@alfresco/adf-core'; +import { QueryBody, MinimalNodeEntryEntity, PathElement, GroupMemberEntry, GroupsPaging, GroupMemberPaging, PermissionElement } from 'alfresco-js-api'; + +@Injectable() +export class NodePermissionService { + + constructor(private apiService: AlfrescoApiService, + private searchApiService: SearchService, + private nodeService: NodesApiService) { + } + + getNodeRoles(node: MinimalNodeEntryEntity): Observable { + const retrieveSiteQueryBody: QueryBody = this.buildRetrieveSiteQueryBody(node.path.elements); + return Observable.fromPromise(this.searchApiService.searchByQueryBody(retrieveSiteQueryBody)) + .switchMap((siteNodeList: any) => { + if ( siteNodeList.list.entries.length > 0 ) { + let siteName = siteNodeList.list.entries[0].entry.name; + return this.getGroupMembersBySiteName(siteName); + } else { + return Observable.of(node.permissions.settable); + } + }); + } + + updatePermissionRoles(node: MinimalNodeEntryEntity, updatedPermissionRole: PermissionElement): Observable { + let permissionBody = { permissions: { locallySet: []} }; + const index = node.permissions.locallySet.map((permission) => permission.authorityId).indexOf(updatedPermissionRole.authorityId); + permissionBody.permissions.locallySet = permissionBody.permissions.locallySet.concat(node.permissions.locallySet); + if (index !== -1) { + permissionBody.permissions.locallySet[index] = updatedPermissionRole; + } else { + permissionBody.permissions.locallySet.push(updatedPermissionRole); + } + return this.nodeService.updateNode(node.id, permissionBody); + } + + private getGroupMembersBySiteName(siteName: string): Observable { + const groupName = 'GROUP_site_' + siteName; + return this.getGroupMemeberByGroupName(groupName) + .map((res: GroupsPaging) => { + let displayResult: string[] = []; + res.list.entries.forEach((member: GroupMemberEntry) => { + displayResult.push(this.formattedRoleName(member.entry.displayName, 'site_' + siteName)); + }); + return displayResult; + }); + } + + getGroupMemeberByGroupName(groupName: string, opts?: any): Observable { + return Observable.fromPromise(this.apiService.groupsApi.getGroupMembers(groupName, opts)); + } + + private formattedRoleName(displayName, siteName): string { + return displayName.replace(siteName + '_', ''); + } + + private buildRetrieveSiteQueryBody(nodePath: PathElement[]): QueryBody { + const pathNames = nodePath.map((node: PathElement) => 'name: "' + node.name + '"'); + const buildedPathNames = pathNames.join(' OR '); + return { + 'query': { + 'query': buildedPathNames + }, + 'paging': { + 'maxItems': 100, + 'skipCount': 0 + }, + 'include': ['aspectNames', 'properties'], + 'filterQueries': [ + { + 'query': + "TYPE:'st:site'" + } + ] + }; + } + +} diff --git a/lib/core/services/alfresco-api.service.ts b/lib/core/services/alfresco-api.service.ts index c113a1f6e8..524a53774c 100644 --- a/lib/core/services/alfresco-api.service.ts +++ b/lib/core/services/alfresco-api.service.ts @@ -19,7 +19,7 @@ import { Injectable } from '@angular/core'; import { AlfrescoApi, ContentApi, FavoritesApi, NodesApi, PeopleApi, RenditionsApi, SharedlinksApi, SitesApi, - VersionsApi, ClassesApi, SearchApi + VersionsApi, ClassesApi, SearchApi, GroupsApi } from 'alfresco-js-api'; import * as alfrescoApi from 'alfresco-js-api'; import { AppConfigService } from '../app-config/app-config.service'; @@ -74,6 +74,10 @@ export class AlfrescoApiService { return this.getInstance().core.classesApi; } + get groupsApi(): GroupsApi { + return this.getInstance().core.groupsApi; + } + constructor(protected appConfig: AppConfigService, protected storage: StorageService) { }