diff --git a/demo-shell/src/app/components/permissions/demo-permissions.component.html b/demo-shell/src/app/components/permissions/demo-permissions.component.html index 2adeb22178..6cb86d301d 100644 --- a/demo-shell/src/app/components/permissions/demo-permissions.component.html +++ b/demo-shell/src/app/components/permissions/demo-permissions.component.html @@ -2,11 +2,17 @@ + +
- +
diff --git a/demo-shell/src/app/components/permissions/demo-permissions.component.scss b/demo-shell/src/app/components/permissions/demo-permissions.component.scss index a6da03dab0..18a2c1fa49 100644 --- a/demo-shell/src/app/components/permissions/demo-permissions.component.scss +++ b/demo-shell/src/app/components/permissions/demo-permissions.component.scss @@ -1,6 +1,6 @@ .inherit_permission_button { padding-top: 20px; display: flex; - justify-content: space-evenly; + justify-content: center; padding-bottom: 20px; } diff --git a/demo-shell/src/app/components/permissions/demo-permissions.component.ts b/demo-shell/src/app/components/permissions/demo-permissions.component.ts index cc4ad25a3e..49455b8c51 100644 --- a/demo-shell/src/app/components/permissions/demo-permissions.component.ts +++ b/demo-shell/src/app/components/permissions/demo-permissions.component.ts @@ -17,9 +17,9 @@ import { Component, Optional, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Params} from '@angular/router'; -import { PermissionListComponent } from '@alfresco/adf-content-services'; +import { PermissionListComponent, NodePermissionDialogService } from '@alfresco/adf-content-services'; import { MinimalNodeEntryEntity } from 'alfresco-js-api'; -import { NodesApiService } from '@alfresco/adf-core'; +import { NodesApiService, NotificationService } from '@alfresco/adf-core'; @Component({ selector: 'app-permissions', @@ -35,7 +35,9 @@ export class DemoPermissionComponent implements OnInit { toggleStatus = false; constructor(@Optional() private route: ActivatedRoute, - private nodeService: NodesApiService) { + private nodeService: NodesApiService, + private nodePermissionDialogService: NodePermissionDialogService, + private notificationService: NotificationService) { } ngOnInit() { @@ -56,4 +58,24 @@ export class DemoPermissionComponent implements OnInit { this.displayPermissionComponent.reload(); } + reloadList() { + this.displayPermissionComponent.reload(); + } + + openAddPermissionDialog(event: Event) { + this.nodePermissionDialogService.updateNodePermissionByDialog(this.nodeId).subscribe(() => { + this.displayPermissionComponent.reload(); + }, + (error) => { + this.showErrorMessage(error); + }); + } + + showErrorMessage(error) { + this.notificationService.openSnackMessage( + JSON.parse(error.response.text).error.errorKey, + 4000 + ); + } + } diff --git a/docs/content-services/add-permission-dialog.component.md b/docs/content-services/add-permission-dialog.component.md new file mode 100644 index 0000000000..1a5daaa4f1 --- /dev/null +++ b/docs/content-services/add-permission-dialog.component.md @@ -0,0 +1,63 @@ +--- +Added: v2.4.0 +Status: Active +Last reviewed: 2018-05-03 +--- + +# Add Permission Dialog Component + +Allow user to search people or group that could be added to the current node permissions. + +![Add Permission Component](../docassets/images/add-permission-component.png) + +## Basic Usage + +```ts +import { NodePermissionDialogService } from '@alfresco/adf-content-services'; + + constructor(private nodePermissionDialogService: nodePermissionDialogService) { + } + + this.nodePermissionDialogService.openAddPermissionDialog(this.nodeId).subscribe((selectedNodes) => { + //action for selected nodes + }, + (error) => { + this.showErrorMessage(error); + }); +``` + +## Class members + +### Properties + +| Name | Type | Default value | Description | +| -- | -- | -- | -- | +| nodeId | `string` | "" | | + +### Events + +| Name | Type | Description | +| -- | -- | -- | +| success | `EventEmitter` | | +| error | `EventEmitter` | | + +## Details + +This component extends the [Add permission panel component](../add-permission-panel.component.md) +and apply the action confirm when the selection made is accepted. +The dialog will be opened via the nodePermissionDialogService which will provide an Observable to subscribe to for getting the node selected. +In case you want the dialog service to take care of update the current node you can call `updateNodePermissionByDialog` in this way : + +```ts +import { NodePermissionDialogService } from '@alfresco/adf-content-services'; + + constructor(private nodePermissionDialogService: nodePermissionDialogService) { + } + + this.nodePermissionDialogService.updateNodePermissionByDialog(this.nodeId).subscribe((node) => { + //updated node + }, + (error) => { + this.showErrorMessage(error); + }); +``` \ No newline at end of file diff --git a/docs/content-services/add-permission-panel.component.md b/docs/content-services/add-permission-panel.component.md new file mode 100644 index 0000000000..479e7bd0bb --- /dev/null +++ b/docs/content-services/add-permission-panel.component.md @@ -0,0 +1,37 @@ +--- +Added: v2.4.0 +Status: Active +Last reviewed: 2018-05-03 +--- + +# Add Permission Component + +Allow user to search people or group that could be added to the current node permissions. + +![Add Permission Component](../docassets/images/add-permission-component.png) + +## Basic Usage + +```html + + +``` + +## Class members + +### Properties + +| Name | Type | Default value | Description | +| -- | -- | -- | -- | + +### Events + +| Name | Type | Description | +| -- | -- | -- | +| select | `EventEmitter` | | + +## Details +This component uses a [Search component](../search.component.md) to retrieve the +groups and people that could be added to the permission list of the current node. +The `select` event will be emitted when a result is clicked from the list. \ No newline at end of file diff --git a/docs/content-services/add-permission.component.md b/docs/content-services/add-permission.component.md new file mode 100644 index 0000000000..f86e89bd2a --- /dev/null +++ b/docs/content-services/add-permission.component.md @@ -0,0 +1,41 @@ +--- +Added: v2.4.0 +Status: Active +Last reviewed: 2018-05-03 +--- + +# Add Permission Component + +Allow user to search people or group that could be added to the current node permissions. + +![Add Permission Component](../docassets/images/add-permission-component.png) + +## Basic Usage + +```html + + +``` + +## Class members + +### Properties + +| Name | Type | Default value | Description | +| -- | -- | -- | -- | +| nodeId | `string` | "" | | + +### Events + +| Name | Type | Description | +| -- | -- | -- | +| success | `EventEmitter` | | +| error | `EventEmitter` | | + +## Details + +This component extends the [Add permission panel component](../add-permission-panel.component.md) +and apply the action confirm when the selection made is accepted. +The `success` event will be emitted when the node is correctly updated. +The `error` event will be thrown whenever the node update permission will fail. diff --git a/docs/docassets/images/add-permission-component.png b/docs/docassets/images/add-permission-component.png new file mode 100644 index 0000000000..b3a9628f85 Binary files /dev/null and b/docs/docassets/images/add-permission-component.png differ diff --git a/lib/content-services/i18n/en.json b/lib/content-services/i18n/en.json index 4cd29e1d0c..b8f7b14292 100644 --- a/lib/content-services/i18n/en.json +++ b/lib/content-services/i18n/en.json @@ -261,6 +261,14 @@ "ROLE": "Role", "LOCALLY_SET": "Locally set", "NO_PERMISSIONS": "No permissions" - } + }, + "ADD-PERMISSION": { + "SEARCH" : "Search", + "TYPE-MESSAGE" : "Type something to start searching groups or people", + "NO-RESULT": "No result found for this search", + "ADD-ACTION" : "ADD", + "CLOSE-ACTION" : "CLOSE", + "BASE-DIALOG-TITLE" : "Search a group or people to add..." + } } } diff --git a/lib/content-services/mock/add-permission.component.mock.ts b/lib/content-services/mock/add-permission.component.mock.ts new file mode 100644 index 0000000000..e63efafe46 --- /dev/null +++ b/lib/content-services/mock/add-permission.component.mock.ts @@ -0,0 +1,153 @@ +/*! + * @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. + */ + +export const fakeAuthorityResults: any[] = [{ + 'entry': { + 'aspectNames': [ + 'cm:personDisabled', + 'cm:ownable', + 'cm:preferences' + ], + 'isFolder': false, + 'search': { + 'score': 4.014668 + }, + 'isFile': false, + 'name': 'dc103838-645f-43c1-8a2a-bc187e13c343', + 'location': 'nodes', + 'id': 'dc103838-645f-43c1-8a2a-bc187e13c343', + 'nodeType': 'cm:person', + 'properties': { + 'cm:location': 'Tilbury, UK', + 'cm:persondescription': { + 'contentUrl': 'store://2018/4/18/9/30/514bb261-bc61-4502-ad2f-dfafec9ae4eb.bin', + 'mimetype': 'application/octet-stream', + 'size': 55, + 'encoding': 'UTF-8', + 'locale': 'en_US', + 'id': 148, + 'infoUrl': 'contentUrl=store://2018/4/18/9/30/514bb261-bc61-4502-ad2f-dfafec9ae4eb.bin|mimetype=application/octet-stream|size=55|encoding=UTF-8|locale=en_US_' + }, + 'cm:owner': { + 'id': 'abeecher', + 'displayName': 'Alice Beecher' + }, + 'cm:companyaddress2': 'Tilbury', + 'cm:userStatus': 'Helping to design the look and feel of the new web site', + 'cm:companyaddress1': '200 Butterwick Street', + 'cm:telephone': '0112211001100', + 'cm:preferenceValues': { + 'contentUrl': 'store://2018/4/18/9/30/afc39bc9-6bac-4f24-8730-9d9f617a322e.bin', + 'mimetype': 'text/plain', + 'size': 709, + 'encoding': 'UTF-8', + 'locale': 'en_US', + 'id': 147, + 'infoUrl': 'contentUrl=store://2018/4/18/9/30/afc39bc9-6bac-4f24-8730-9d9f617a322e.bin|mimetype=text/plain|size=709|encoding=UTF-8|locale=en_US_' + }, + 'cm:userName': 'abeecher', + 'cm:companyaddress3': 'UK', + 'cm:userStatusTime': '2011-02-15T20:20:13.432+0000', + 'cm:email': 'abeecher@example.com', + 'cm:skype': 'abeecher', + 'cm:jobtitle': 'Graphic Designer', + 'cm:homeFolderProvider': 'userHomesHomeFolderProvider', + 'cm:homeFolder': '242533d8-68e6-4811-bc3d-61ec63c614aa', + 'cm:lastName': 'Beecher', + 'cm:sizeCurrent': 8382006, + 'cm:sizeQuota': -1, + 'cm:firstName': 'Alice', + 'cm:emailFeedId': 440, + 'cm:authorizationStatus': 'NEVER_AUTHORIZED', + 'cm:mobile': '0112211001100', + 'cm:organization': 'Moresby, Garland and Wedge', + 'cm:companypostcode': 'ALF1 SAM1' + }, + 'parentId': '063f5d48-a0b3-4cbf-826c-88a4fbfa3336' + } +}, +{ + 'entry': { + 'aspectNames': [ + 'cm:ownable', + 'cm:preferences' + ], + 'isFolder': false, + 'search': { + 'score': 4.014668 + }, + 'isFile': false, + 'name': 'e320c16b-a763-4a4e-9f22-286ff5d8dca2', + 'location': 'nodes', + 'id': 'e320c16b-a763-4a4e-9f22-286ff5d8dca2', + 'nodeType': 'cm:person', + 'properties': { + 'cm:homeFolderProvider': 'bootstrapHomeFolderProvider', + 'cm:preferenceValues': { + 'contentUrl': 'store://2018/4/23/14/42/92bb4aa9-db27-41a4-9804-ddab3cc83d3e.bin', + 'mimetype': 'text/plain', + 'size': 102, + 'encoding': 'UTF-8', + 'locale': 'en', + 'id': 313, + 'infoUrl': 'contentUrl=store://2018/4/23/14/42/92bb4aa9-db27-41a4-9804-ddab3cc83d3e.bin|mimetype=text/plain|size=102|encoding=UTF-8|locale=en_' + }, + 'cm:authorizationStatus': 'AUTHORIZED', + 'cm:homeFolder': 'a20cd541-4ada-4525-9807-9fa0a047d9f4', + 'cm:userName': 'admin', + 'cm:sizeCurrent': 0, + 'cm:email': 'admin@alfresco.com', + 'cm:firstName': 'Administrator', + 'cm:owner': { + 'id': 'admin', + 'displayName': 'Administrator' + } + }, + 'parentId': '063f5d48-a0b3-4cbf-826c-88a4fbfa3336' + } +}, +{ + 'entry': { + 'isFolder': false, + 'search': { + 'score': 0.3541112 + }, + 'isFile': false, + 'name': 'GROUP_ALFRESCO_ADMINISTRATORS', + 'location': 'nodes', + 'id': 'GROUP_ALFRESCO_ADMINISTRATORS', + 'nodeType': 'cm:authorityContainer', + 'properties': { + 'cm:authorityName': 'GROUP_ALFRESCO_ADMINISTRATORS' + }, + 'parentId': '030d833e-da8e-4f5c-8ef9-d809638bd04b' + } +}]; + +export const fakeAuthorityListResult: any = { +'list': { +'pagination': { + 'count': 0, + 'hasMoreItems': false, + 'totalItems': 0, + 'skipCount': 0, + 'maxItems': 100 +}, +'context': {}, +'entries': fakeAuthorityResults +} +}; diff --git a/lib/content-services/mock/permission-list.component.mock.ts b/lib/content-services/mock/permission-list.component.mock.ts index 903a8d7173..9dacbb68dc 100644 --- a/lib/content-services/mock/permission-list.component.mock.ts +++ b/lib/content-services/mock/permission-list.component.mock.ts @@ -251,6 +251,89 @@ export const fakeNodeWithOnlyLocally: any = { } }; +export const fakeNodeToRemovePermission: any = { + 'aspectNames': [ + 'cm:auditable', + 'cm:taggable', + 'cm:author', + 'cm:titled', + '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': { + 'id': 'System', + 'displayName': 'System' + }, + 'modifiedAt': '2018-03-21T03:17:58.783+0000', + 'permissions': { + 'locallySet': [ + { + 'authorityId': 'GROUP_EVERYONE', + 'name': 'Contributor', + 'accessStatus': 'ALLOWED' + }, + { + 'authorityId': 'GROUP_FAKE_1', + 'name': 'Contributor', + 'accessStatus': 'ALLOWED' + }, + { + 'authorityId': 'FAKE_PERSON_1', + 'name': 'Contributor', + 'accessStatus': 'ALLOWED' + } + ], + 'settable': [ + 'Contributor', + 'Collaborator', + 'Coordinator', + 'Editor', + 'Consumer' + ], + 'isInheritanceEnabled': true + }, + 'modifiedByUser': { + 'id': 'admin', + 'displayName': 'PedroH Hernandez' + }, + 'name': 'test', + 'id': 'f472543f-7218-403d-917b-7a5861257244', + 'nodeType': 'cm:folder', + 'properties': { + 'cm:title': 'test', + 'cm:author': 'yagud', + 'cm:taggable': [ + 'e8c8fbba-03ba-4fa6-86b1-f7ad7c296409' + ], + 'cm:description': 'sleepery', + 'app:icon': 'space-icon-default' + } +}; + export const fakeNodeWithoutPermissions: any = { 'aspectNames': [ 'cm:auditable', diff --git a/lib/content-services/permission-manager/components/add-permission/add-permission-dialog-data.interface.ts b/lib/content-services/permission-manager/components/add-permission/add-permission-dialog-data.interface.ts new file mode 100644 index 0000000000..37282feb84 --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/add-permission-dialog-data.interface.ts @@ -0,0 +1,25 @@ +/*! + * @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 { MinimalNodeEntity } from 'alfresco-js-api'; +import { Subject } from 'rxjs/Subject'; + +export interface AddPermissionDialogData { + title?: string; + nodeId: string; + confirm: Subject; +} diff --git a/lib/content-services/permission-manager/components/add-permission/add-permission-dialog.component.html b/lib/content-services/permission-manager/components/add-permission/add-permission-dialog.component.html new file mode 100644 index 0000000000..0a6891632d --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/add-permission-dialog.component.html @@ -0,0 +1,16 @@ +

+ {{(data?.title ? data?.title : 'PERMISSION_MANAGER.ADD-PERMISSION.BASE-DIALOG-TITLE') | translate}} +

+ + + + + + + + + diff --git a/lib/content-services/permission-manager/components/add-permission/add-permission-dialog.component.scss b/lib/content-services/permission-manager/components/add-permission/add-permission-dialog.component.scss new file mode 100644 index 0000000000..457536fef1 --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/add-permission-dialog.component.scss @@ -0,0 +1,55 @@ +@mixin adf-add-permission-dialog-theme($theme) { + + $primary: map-get($theme, primary); + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .adf-add-permission-dialog { + + .mat-dialog-title { + margin-left: 24px; + margin-right: 24px; + font-size: 20px; + font-weight: 600; + font-style: normal; + font-stretch: normal; + line-height: 1.6; + letter-spacing: -0.5px; + color: mat-color($foreground, text, 0.87); + } + + .mat-dialog-container { + padding-left: 0; + padding-right: 0; + } + + .mat-dialog-content { + margin: 0; + overflow: hidden; + } + + .mat-dialog-actions { + padding: 8px; + background-color: mat-color($background, background); + display: flex; + justify-content: flex-end; + color: mat-color($foreground, secondary-text); + + button { + text-transform: uppercase; + font-weight: normal; + } + + .choose-action { + + &[disabled] { + opacity: 0.6; + } + + &:enabled { + color: mat-color($primary); + } + } + } + } +} diff --git a/lib/content-services/permission-manager/components/add-permission/add-permission-dialog.component.spec.ts b/lib/content-services/permission-manager/components/add-permission/add-permission-dialog.component.spec.ts new file mode 100644 index 0000000000..b523e751d4 --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/add-permission-dialog.component.spec.ts @@ -0,0 +1,111 @@ +/*! + * @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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { ContentTestingModule } from '../../../testing/content.testing.module'; +import { By } from '@angular/platform-browser'; +import { setupTestBed } from '@alfresco/adf-core'; +import { AddPermissionDialogComponent } from './add-permission-dialog.component'; +import { MinimalNodeEntity } from 'alfresco-js-api'; +import { Subject } from 'rxjs/Subject'; +import { AddPermissionDialogData } from './add-permission-dialog-data.interface'; +import { fakeAuthorityResults } from '../../../mock/add-permission.component.mock'; +import { AddPermissionPanelComponent } from './add-permission-panel.component'; + +describe('AddPermissionDialog', () => { + + let fixture: ComponentFixture; + let element: HTMLElement; + let data: AddPermissionDialogData = { + title: 'dead or alive you are coming with me', + nodeId: 'fake-node-id', + confirm: new Subject () + }; + const dialogRef = { + close: jasmine.createSpy('close') + }; + + setupTestBed({ + imports: [ContentTestingModule], + providers: [ + { provide: MatDialogRef, useValue: dialogRef }, + { provide: MAT_DIALOG_DATA, useValue: data } + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }); + + beforeEach(() => { + + fixture = TestBed.createComponent(AddPermissionDialogComponent); + element = fixture.nativeElement; + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.destroy(); + }); + + it('should show the INJECTED title', () => { + const titleElement = fixture.debugElement.query(By.css('#add-permission-dialog-title')); + expect(titleElement).not.toBeNull(); + expect(titleElement.nativeElement.innerText).toBe('dead or alive you are coming with me'); + }); + + it('should close the dialog when close button is clicked', () => { + const closeButton: HTMLButtonElement = element.querySelector('#add-permission-dialog-close-button'); + expect(closeButton).not.toBeNull(); + closeButton.click(); + expect(dialogRef.close).toHaveBeenCalled(); + }); + + it('should disable the confirm button when no selection is applied', () => { + const confirmButton: HTMLButtonElement = element.querySelector('#add-permission-dialog-confirm-button'); + expect(confirmButton.disabled).toBeTruthy(); + }); + + it('should enable the button when a selection is done', async(() => { + const addPermissionPanelComponent: AddPermissionPanelComponent = fixture.debugElement.query(By.directive(AddPermissionPanelComponent)).componentInstance; + addPermissionPanelComponent.select.emit(fakeAuthorityResults); + let confirmButton: HTMLButtonElement = element.querySelector('#add-permission-dialog-confirm-button'); + expect(confirmButton.disabled).toBeTruthy(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + confirmButton = element.querySelector('#add-permission-dialog-confirm-button'); + expect(confirmButton.disabled).toBeFalsy(); + }); + })); + + it('should stream the confirmed selection on the confirm subject', async(() => { + const addPermissionPanelComponent: AddPermissionPanelComponent = fixture.debugElement.query(By.directive(AddPermissionPanelComponent)).componentInstance; + addPermissionPanelComponent.select.emit(fakeAuthorityResults); + data.confirm.subscribe((selection) => { + expect(selection[0]).not.toBeNull(); + expect(selection[0].entry.id).not.toBeNull(); + expect(fakeAuthorityResults[0].entry.id).toBe(selection[0].entry.id); + }); + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + const confirmButton = element.querySelector('#add-permission-dialog-confirm-button'); + confirmButton.click(); + }); + })); +}); diff --git a/lib/content-services/permission-manager/components/add-permission/add-permission-dialog.component.ts b/lib/content-services/permission-manager/components/add-permission/add-permission-dialog.component.ts new file mode 100644 index 0000000000..c41d208557 --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/add-permission-dialog.component.ts @@ -0,0 +1,48 @@ +/*! + * @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 { Component, ViewEncapsulation, Inject, ViewChild } from '@angular/core'; +import { MAT_DIALOG_DATA } from '@angular/material'; +import { MinimalNodeEntity } from 'alfresco-js-api'; +import { AddPermissionDialogData } from './add-permission-dialog-data.interface'; +import { AddPermissionComponent } from '../add-permission/add-permission.component'; + +@Component({ + selector: 'adf-add-permission-dialog', + templateUrl: './add-permission-dialog.component.html', + styleUrls: ['./add-permission-dialog.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class AddPermissionDialogComponent { + + @ViewChild('addPermission') + addPermissionComponent: AddPermissionComponent; + + private currentSelection: MinimalNodeEntity[] = []; + + constructor(@Inject(MAT_DIALOG_DATA) public data: AddPermissionDialogData) { + } + + onSelect(items: MinimalNodeEntity[]) { + this.currentSelection = items; + } + + onAddClicked() { + this.data.confirm.next(this.currentSelection); + this.data.confirm.complete(); + } +} diff --git a/lib/content-services/permission-manager/components/add-permission/add-permission-panel.component.html b/lib/content-services/permission-manager/components/add-permission/add-permission-panel.component.html new file mode 100644 index 0000000000..801c16b3de --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/add-permission-panel.component.html @@ -0,0 +1,54 @@ + + + + clear + + + search + + + + + + + + + + + group_add + + + person_add + +

+ {{item.entry?.properties['cm:authorityName']? + item.entry?.properties['cm:authorityName'] : + item.entry?.properties['cm:firstName']}}

+
+
+
+ {{'PERMISSION_MANAGER.ADD-PERMISSION.NO-RESULT' | translate}} +
+
+
diff --git a/lib/content-services/permission-manager/components/add-permission/add-permission-panel.component.scss b/lib/content-services/permission-manager/components/add-permission/add-permission-panel.component.scss new file mode 100644 index 0000000000..10e293d7ac --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/add-permission-panel.component.scss @@ -0,0 +1,68 @@ +@mixin adf-add-permission-panel-theme($theme) { + + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $mat-menu-border-radius: 2px !default; + + .adf { + + &-permission-result-list { + display: flex; + height: 300px; + overflow: auto; + border: 2px solid mat-color($foreground, base, 0.07); + + &-elements { + width: 100%; + } + } + + &-permission-start-message { + display: flex; + align-items: center; + justify-content: space-around; + height: 300px; + overflow: auto; + border: 2px solid mat-color($foreground, base, 0.07); + } + + &-permission-no-result{ + display: flex; + align-items: center; + justify-content: space-around; + width: 100%; + } + + &-permission-search { + &-input { + width: 100%; + + &-icon { + color: mat-color($foreground, disabled-button); + cursor: pointer; + + &:hover { + color: mat-color($foreground, base); + } + } + } + } + + &-list-option-item .mat-list-text { + display: flex; + flex-direction: row !important; + align-items: center; + } + + &-permission-action { + &[disabled] { + opacity: 0.6; + } + &:enabled { + color: mat-color($primary); + } + } + } +} diff --git a/lib/content-services/permission-manager/components/add-permission/add-permission-panel.component.spec.ts b/lib/content-services/permission-manager/components/add-permission/add-permission-panel.component.spec.ts new file mode 100644 index 0000000000..d6e869e282 --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/add-permission-panel.component.spec.ts @@ -0,0 +1,156 @@ +/*! + * @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, ComponentFixture, TestBed } from '@angular/core/testing'; +import { AddPermissionPanelComponent } from './add-permission-panel.component'; +import { By } from '@angular/platform-browser'; +import { SearchService, setupTestBed, SearchConfigurationService } from '@alfresco/adf-core'; +import { Observable } from 'rxjs/Observable'; +import { fakeAuthorityListResult } from '../../../mock/add-permission.component.mock'; +import { ContentTestingModule } from '../../../testing/content.testing.module'; +import { DebugElement } from '@angular/core'; + +describe('AddPermissionPanelComponent', () => { + + let fixture: ComponentFixture; + let component: AddPermissionPanelComponent; + let element: HTMLElement; + let searchApiService: SearchService; + let debugElement: DebugElement; + + setupTestBed({ + imports: [ContentTestingModule], + providers: [SearchService, SearchConfigurationService] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AddPermissionPanelComponent); + debugElement = fixture.debugElement; + element = fixture.nativeElement; + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.destroy(); + }); + + function typeWordIntoSearchInput(word: string): void { + let inputDebugElement = debugElement.query(By.css('#searchInput')); + inputDebugElement.nativeElement.value = word; + inputDebugElement.nativeElement.focus(); + inputDebugElement.nativeElement.dispatchEvent(new Event('input')); + } + + it('should be able to render the component', () => { + expect(element.querySelector('#adf-add-permission-type-search')).not.toBeNull(); + expect(element.querySelector('#searchInput')).not.toBeNull(); + }); + + it('should show search results when user types something', async(() => { + searchApiService = fixture.componentRef.injector.get(SearchService); + spyOn(searchApiService, 'search').and.returnValue(Observable.of(fakeAuthorityListResult)); + expect(element.querySelector('#adf-add-permission-type-search')).not.toBeNull(); + expect(element.querySelector('#searchInput')).not.toBeNull(); + typeWordIntoSearchInput('a'); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(element.querySelector('#adf-add-permission-authority-results')).not.toBeNull(); + expect(element.querySelector('#result_option_0')).not.toBeNull(); + }); + })); + + it('should emit a select event with the selected items when an item is clicked', async(() => { + searchApiService = fixture.componentRef.injector.get(SearchService); + spyOn(searchApiService, 'search').and.returnValue(Observable.of(fakeAuthorityListResult)); + component.select.subscribe((items) => { + expect(items).not.toBeNull(); + expect(items[0].entry.id).toBeDefined(); + expect(items[0].entry.id).not.toBeNull(); + expect(items[0].entry.id).toBe(fakeAuthorityListResult.list.entries[0].entry.id); + }); + + typeWordIntoSearchInput('a'); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + const listElement: DebugElement = fixture.debugElement.query(By.css('#result_option_0')); + expect(listElement).not.toBeNull(); + listElement.triggerEventHandler('click', {}); + }); + })); + + it('should show the icon related on the nodeType', async(() => { + searchApiService = fixture.componentRef.injector.get(SearchService); + spyOn(searchApiService, 'search').and.returnValue(Observable.of(fakeAuthorityListResult)); + expect(element.querySelector('#adf-add-permission-type-search')).not.toBeNull(); + expect(element.querySelector('#searchInput')).not.toBeNull(); + typeWordIntoSearchInput('a'); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(element.querySelector('#adf-add-permission-authority-results')).not.toBeNull(); + expect(element.querySelector('#result_option_0 #add-person-icon')).toBeDefined(); + expect(element.querySelector('#result_option_0 #add-person-icon')).not.toBeNull(); + expect(element.querySelector('#result_option_2 #add-group-icon')).toBeDefined(); + expect(element.querySelector('#result_option_2 #add-group-icon')).not.toBeNull(); + }); + })); + + it('should clear the search when user delete the search input field', async(() => { + searchApiService = fixture.componentRef.injector.get(SearchService); + spyOn(searchApiService, 'search').and.returnValue(Observable.of(fakeAuthorityListResult)); + expect(element.querySelector('#adf-add-permission-type-search')).not.toBeNull(); + expect(element.querySelector('#searchInput')).not.toBeNull(); + typeWordIntoSearchInput('a'); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(element.querySelector('#adf-add-permission-authority-results')).not.toBeNull(); + expect(element.querySelector('#result_option_0')).not.toBeNull(); + const clearButton = fixture.debugElement.query(By.css('#adf-permission-clear-input')); + expect(clearButton).not.toBeNull(); + clearButton.triggerEventHandler('click', {}); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(element.querySelector('#adf-add-permission-authority-results')).toBeNull(); + }); + }); + })); + + it('should remove element from selection when is clicked and already selected', async(() => { + searchApiService = fixture.componentRef.injector.get(SearchService); + spyOn(searchApiService, 'search').and.returnValue(Observable.of(fakeAuthorityListResult)); + component.selectedItems.push(fakeAuthorityListResult.list.entries[0]); + component.select.subscribe((items) => { + expect(items).not.toBeNull(); + expect(items[0]).toBeUndefined(); + expect(component.selectedItems.length).toBe(0); + }); + + typeWordIntoSearchInput('a'); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + const listElement: DebugElement = fixture.debugElement.query(By.css('#result_option_0')); + expect(listElement).not.toBeNull(); + listElement.triggerEventHandler('click', {}); + }); + })); + +}); diff --git a/lib/content-services/permission-manager/components/add-permission/add-permission-panel.component.ts b/lib/content-services/permission-manager/components/add-permission/add-permission-panel.component.ts new file mode 100644 index 0000000000..df35119d07 --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/add-permission-panel.component.ts @@ -0,0 +1,82 @@ +/*! + * @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 { Component, ViewEncapsulation, EventEmitter, Output, ViewChild } from '@angular/core'; +import { SearchPermissionConfigurationService } from './search-config-permission.service'; +import { SearchService, SearchConfigurationService } from '@alfresco/adf-core'; +import { SearchComponent } from '../../../search/components/search.component'; +import { FormControl } from '@angular/forms'; +import { debounceTime } from 'rxjs/operators'; +import { MinimalNodeEntity } from 'alfresco-js-api'; + +@Component({ + selector: 'adf-add-permission-panel', + templateUrl: './add-permission-panel.component.html', + styleUrls: ['./add-permission-panel.component.scss'], + encapsulation: ViewEncapsulation.None, + providers: [ + { provide: SearchConfigurationService, useClass: SearchPermissionConfigurationService }, + SearchService + ] +}) +export class AddPermissionPanelComponent { + + @ViewChild('search') + search: SearchComponent; + + @Output() + select: EventEmitter = new EventEmitter(); + + searchInput: FormControl = new FormControl(); + searchedWord = ''; + debounceSearch: number = 200; + + selectedItems: MinimalNodeEntity[] = []; + + constructor() { + this.searchInput.valueChanges + .pipe( + debounceTime(this.debounceSearch) + ) + .subscribe((searchValue) => { + this.searchedWord = searchValue; + if (!searchValue) { + this.search.resetResults(); + } + }); + } + + elementClicked(item: MinimalNodeEntity) { + if (this.isAlreadySelected(item)) { + this.selectedItems.splice(this.selectedItems.indexOf(item), 1); + } else { + this.selectedItems.push(item); + } + this.select.emit(this.selectedItems); + } + + private isAlreadySelected(item: MinimalNodeEntity): boolean { + return this.selectedItems.indexOf(item) >= 0; + } + + clearSearch() { + this.searchedWord = ''; + this.selectedItems.splice(0, this.selectedItems.length); + this.search.resetResults(); + } + +} diff --git a/lib/content-services/permission-manager/components/add-permission/add-permission.component.html b/lib/content-services/permission-manager/components/add-permission/add-permission.component.html new file mode 100644 index 0000000000..99d3792e9d --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/add-permission.component.html @@ -0,0 +1,14 @@ + + +
+ +
+ + diff --git a/lib/content-services/permission-manager/components/add-permission/add-permission.component.scss b/lib/content-services/permission-manager/components/add-permission/add-permission.component.scss new file mode 100644 index 0000000000..c3324845cd --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/add-permission.component.scss @@ -0,0 +1,16 @@ +@mixin adf-add-permission-theme($theme) { + + $primary: map-get($theme, primary); + + .adf { + + &-permission-action { + &[disabled] { + opacity: 0.6; + } + &:enabled { + color: mat-color($primary); + } + } + } +} diff --git a/lib/content-services/permission-manager/components/add-permission/add-permission.component.spec.ts b/lib/content-services/permission-manager/components/add-permission/add-permission.component.spec.ts new file mode 100644 index 0000000000..71c3f28321 --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/add-permission.component.spec.ts @@ -0,0 +1,102 @@ +/*! + * @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, ComponentFixture, TestBed } from '@angular/core/testing'; +import { AddPermissionComponent } from './add-permission.component'; +import { AddPermissionPanelComponent } from './add-permission-panel.component'; +import { By } from '@angular/platform-browser'; +import { setupTestBed } from '@alfresco/adf-core'; +import { Observable } from 'rxjs/Observable'; +import { fakeAuthorityResults } from '../../../mock/add-permission.component.mock'; +import { ContentTestingModule } from '../../../testing/content.testing.module'; +import { NodePermissionService } from '../../services/node-permission.service'; + +describe('AddPermissionComponent', () => { + + let fixture: ComponentFixture; + let element: HTMLElement; + let nodePermissionService: NodePermissionService; + + setupTestBed({ + imports: [ + ContentTestingModule + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AddPermissionComponent); + element = fixture.nativeElement; + nodePermissionService = TestBed.get(NodePermissionService); + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.destroy(); + }); + + it('should be able to render the component', () => { + expect(element.querySelector('#adf-add-permission-type-search')).not.toBeNull(); + expect(element.querySelector('#searchInput')).not.toBeNull(); + expect(element.querySelector('#adf-add-permission-actions')).not.toBeNull(); + const addButton: HTMLButtonElement = element.querySelector('#adf-add-permission-action-button'); + expect(addButton.disabled).toBeTruthy(); + }); + + it('should enable the ADD button when a selection is sent', async(() => { + const addPermissionPanelComponent: AddPermissionPanelComponent = fixture.debugElement.query(By.directive(AddPermissionPanelComponent)).componentInstance; + addPermissionPanelComponent.select.emit(fakeAuthorityResults); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + const addButton: HTMLButtonElement = element.querySelector('#adf-add-permission-action-button'); + expect(addButton.disabled).toBeFalsy(); + }); + })); + + it('should emit a success event when the node is updated', async(() => { + fixture.componentInstance.selectedItems = fakeAuthorityResults; + spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(Observable.of({ id: 'fake-node-id'})); + + fixture.componentInstance.success.subscribe((node) => { + expect(node.id).toBe('fake-node-id'); + }); + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + const addButton: HTMLButtonElement = element.querySelector('#adf-add-permission-action-button'); + addButton.click(); + }); + })); + + it('should emit an error event when the node update fail', async(() => { + fixture.componentInstance.selectedItems = fakeAuthorityResults; + spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(Observable.throw({ error: 'errored'})); + + fixture.componentInstance.error.subscribe((error) => { + expect(error.error).toBe('errored'); + }); + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + const addButton: HTMLButtonElement = element.querySelector('#adf-add-permission-action-button'); + addButton.click(); + }); + })); + +}); diff --git a/lib/content-services/permission-manager/components/add-permission/add-permission.component.ts b/lib/content-services/permission-manager/components/add-permission/add-permission.component.ts new file mode 100644 index 0000000000..c6d4b1734d --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/add-permission.component.ts @@ -0,0 +1,61 @@ +/*! + * @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 { Component, ViewEncapsulation, EventEmitter, Input, Output } from '@angular/core'; +import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { NodePermissionService } from '../../services/node-permission.service'; + +@Component({ + selector: 'adf-add-permission', + templateUrl: './add-permission.component.html', + styleUrls: ['./add-permission.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class AddPermissionComponent { + + @Input() + nodeId: string; + + @Output() + success: EventEmitter = new EventEmitter(); + + @Output() + error: EventEmitter = new EventEmitter(); + + selectedItems: MinimalNodeEntity[] = []; + currentNode: MinimalNodeEntryEntity; + currentNodeRoles: string[]; + + constructor(private nodePermissionService: NodePermissionService) { + } + + onSelect(selection: MinimalNodeEntity[]) { + this.selectedItems = selection; + } + + applySelection() { + this.nodePermissionService.updateNodePermissions(this.nodeId, this.selectedItems) + .subscribe( + (node) => { + this.success.emit(node); + }, + (error) => { + this.error.emit(error); + }); + } + +} diff --git a/lib/content-services/permission-manager/components/add-permission/search-config-permission.service.ts b/lib/content-services/permission-manager/components/add-permission/search-config-permission.service.ts new file mode 100644 index 0000000000..7b58ce3783 --- /dev/null +++ b/lib/content-services/permission-manager/components/add-permission/search-config-permission.service.ts @@ -0,0 +1,43 @@ +/*! + * @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 { QueryBody } from 'alfresco-js-api'; +import { SearchConfigurationInterface } from '@alfresco/adf-core'; + +export class SearchPermissionConfigurationService implements SearchConfigurationInterface { + + constructor() { + } + + public generateQueryBody(searchTerm: string, maxResults: number, skipCount: number): QueryBody { + const defaultQueryBody: QueryBody = { + query: { + query: searchTerm ? `authorityName:${searchTerm}* OR userName:${searchTerm}*` : searchTerm + }, + include: ['properties', 'aspectNames'], + paging: { + maxItems: maxResults, + skipCount: skipCount + }, + filterQueries: [ + /*tslint:disable-next-line */ + { query: "TYPE:'cm:authority'" }] + }; + + return defaultQueryBody; + } +} diff --git a/lib/content-services/permission-manager/components/inherited-button.directive.spec.ts b/lib/content-services/permission-manager/components/inherited-button.directive.spec.ts index 97202ec06b..4828b39779 100644 --- a/lib/content-services/permission-manager/components/inherited-button.directive.spec.ts +++ b/lib/content-services/permission-manager/components/inherited-button.directive.spec.ts @@ -17,7 +17,7 @@ import { SimpleInheritedPermissionTestComponent } from '../../mock/inherited-permission.component.mock'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { PermissionManagerModule } from '../../index'; +import { InheritPermissionDirective } from './inherited-button.directive'; import { NodesApiService, setupTestBed, CoreModule } from '@alfresco/adf-core'; import { Observable } from 'rxjs/Observable'; @@ -33,20 +33,20 @@ describe('InheritPermissionDirective', () => { setupTestBed({ imports: [ - CoreModule.forRoot(), - PermissionManagerModule + CoreModule.forRoot() ], declarations: [ + InheritPermissionDirective, SimpleInheritedPermissionTestComponent ] }); - beforeEach(() => { + beforeEach(async(() => { fixture = TestBed.createComponent(SimpleInheritedPermissionTestComponent); component = fixture.componentInstance; element = fixture.nativeElement; nodeService = TestBed.get(NodesApiService); - }); + })); it('should be able to render the simple component', async(() => { fixture.detectChanges(); diff --git a/lib/content-services/permission-manager/components/inherited-button.directive.ts b/lib/content-services/permission-manager/components/inherited-button.directive.ts index 01e5e21468..9bc1c3faa9 100644 --- a/lib/content-services/permission-manager/components/inherited-button.directive.ts +++ b/lib/content-services/permission-manager/components/inherited-button.directive.ts @@ -35,15 +35,18 @@ export class InheritPermissionDirective { @Output() updated: EventEmitter = new EventEmitter(); + @Output() + error: EventEmitter = new EventEmitter(); + constructor(private nodeService: NodesApiService) { } onInheritPermissionClicked() { this.nodeService.getNode(this.nodeId).subscribe((node: MinimalNodeEntryEntity) => { - const nodeBody = { permissions : {isInheritanceEnabled : !node.permissions.isInheritanceEnabled} }; - this.nodeService.updateNode(this.nodeId, nodeBody, {include: ['permissions'] }).subscribe((nodeUpdated: MinimalNodeEntryEntity) => { + const nodeBody = { permissions: { isInheritanceEnabled: !node.permissions.isInheritanceEnabled } }; + this.nodeService.updateNode(this.nodeId, nodeBody, { include: ['permissions'] }).subscribe((nodeUpdated: MinimalNodeEntryEntity) => { this.updated.emit(nodeUpdated); - }); + }, (error) => this.error.emit(error)); }); } 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 2a1cce741f..4c9f58c330 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 @@ -48,6 +48,13 @@ + + + + + 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 e741ebce98..0a49a03841 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 @@ -35,6 +35,9 @@ export class PermissionListComponent implements OnInit { @Output() update: EventEmitter = new EventEmitter(); + @Output() + error: EventEmitter = new EventEmitter(); + permissionList: PermissionDisplayModel[]; settableRoles: any[]; actualNode: MinimalNodeEntryEntity; @@ -82,7 +85,7 @@ export class PermissionListComponent implements OnInit { saveNewRole(event: any, permissionRow: PermissionDisplayModel) { let updatedPermissionRole: PermissionElement = this.buildUpdatedPermission(event.value, permissionRow); - this.nodePermissionService.updatePermissionRoles(this.actualNode, updatedPermissionRole) + this.nodePermissionService.updatePermissionRole(this.actualNode, updatedPermissionRole) .subscribe((node: MinimalNodeEntryEntity) => { this.update.emit(updatedPermissionRole); }); @@ -96,4 +99,10 @@ export class PermissionListComponent implements OnInit { return permissionRole; } + removePermission(permissionRow: PermissionDisplayModel) { + this.nodePermissionService.removePermission(this.actualNode, permissionRow).subscribe((node) => { + this.update.emit(node); + }, (error) => this.error.emit(error)); + } + } diff --git a/lib/content-services/permission-manager/models/permission.model.ts b/lib/content-services/permission-manager/models/permission.model.ts index 7e9600e31c..3500496d58 100644 --- a/lib/content-services/permission-manager/models/permission.model.ts +++ b/lib/content-services/permission-manager/models/permission.model.ts @@ -31,7 +31,7 @@ export class PermissionDisplayModel implements PermissionElement { this.name = obj.name; this.accessStatus = obj.accessStatus; this.isInherited = obj.isInherited !== null && obj.isInherited !== undefined ? obj.isInherited : false; - this.icon = obj.icon ? obj.icon : 'lock_open'; + this.icon = obj.icon ? obj.icon : 'vpn_key'; } } } diff --git a/lib/content-services/permission-manager/permission-manager.module.ts b/lib/content-services/permission-manager/permission-manager.module.ts index ff918e5d2c..9a4385d5f7 100644 --- a/lib/content-services/permission-manager/permission-manager.module.ts +++ b/lib/content-services/permission-manager/permission-manager.module.ts @@ -21,10 +21,15 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; import { MaterialModule } from '../material.module'; import { PermissionListComponent } from './components/permission-list/permission-list.component'; +import { AddPermissionComponent } from './components/add-permission/add-permission.component'; +import { AddPermissionDialogComponent } from './components/add-permission/add-permission-dialog.component'; import { DataTableModule, DataColumnModule } from '@alfresco/adf-core'; import { InheritPermissionDirective } from './components/inherited-button.directive'; import { NodePermissionService } from './services/node-permission.service'; import { NoPermissionTemplateComponent } from './components/permission-list/no-permission.component'; +import { SearchModule } from '..'; +import { NodePermissionDialogService } from './services/node-permission-dialog.service'; +import { AddPermissionPanelComponent } from './components/add-permission/add-permission-panel.component'; @NgModule({ imports: [ @@ -34,20 +39,29 @@ import { NoPermissionTemplateComponent } from './components/permission-list/no-p MaterialModule, TranslateModule, DataTableModule, - DataColumnModule + DataColumnModule, + SearchModule ], declarations: [ PermissionListComponent, NoPermissionTemplateComponent, - InheritPermissionDirective + AddPermissionPanelComponent, + InheritPermissionDirective, + AddPermissionComponent, + AddPermissionDialogComponent ], providers: [ + NodePermissionDialogService, NodePermissionService ], + entryComponents: [ AddPermissionPanelComponent, AddPermissionComponent, AddPermissionDialogComponent ], exports: [ PermissionListComponent, NoPermissionTemplateComponent, - InheritPermissionDirective + AddPermissionPanelComponent, + InheritPermissionDirective, + AddPermissionComponent, + AddPermissionDialogComponent ] }) export class PermissionManagerModule {} diff --git a/lib/content-services/permission-manager/public-api.ts b/lib/content-services/permission-manager/public-api.ts index 3e76f09728..d49beb12bd 100644 --- a/lib/content-services/permission-manager/public-api.ts +++ b/lib/content-services/permission-manager/public-api.ts @@ -18,7 +18,12 @@ export * from './components/permission-list/permission-list.component'; export * from './components/permission-list/no-permission.component'; export * from './components/inherited-button.directive'; -export * from './services/node-permission.service'; export * from './models/permission.model'; +export * from './services/node-permission-dialog.service'; +export * from './services/node-permission.service'; +export * from './components/add-permission/add-permission-dialog-data.interface'; +export * from './components/add-permission/add-permission-panel.component'; +export * from './components/add-permission/add-permission.component'; +export * from './components/add-permission/add-permission-dialog.component'; export * from './permission-manager.module'; diff --git a/lib/content-services/permission-manager/services/node-permission-dialog.service.spec.ts b/lib/content-services/permission-manager/services/node-permission-dialog.service.spec.ts new file mode 100644 index 0000000000..afcd3b7c17 --- /dev/null +++ b/lib/content-services/permission-manager/services/node-permission-dialog.service.spec.ts @@ -0,0 +1,83 @@ +/*! + * @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 { TestBed, async } from '@angular/core/testing'; +import { AppConfigService, setupTestBed } from '@alfresco/adf-core'; +import { NodePermissionDialogService } from './node-permission-dialog.service'; +import { MatDialog } from '@angular/material'; +import { Observable } from 'rxjs/Observable'; +import { Subject } from 'rxjs/Subject'; +import { ContentTestingModule } from '../../testing/content.testing.module'; +import { NodePermissionService } from './node-permission.service'; + +describe('NodePermissionDialogService', () => { + + let service: NodePermissionDialogService; + let materialDialog: MatDialog; + let spyOnDialogOpen: jasmine.Spy; + let afterOpenObservable: Subject; + let nodePermissionService: NodePermissionService; + + setupTestBed({ + imports: [ContentTestingModule], + providers: [NodePermissionService] + }); + + beforeEach(() => { + let appConfig: AppConfigService = TestBed.get(AppConfigService); + appConfig.config.ecmHost = 'http://localhost:9876/ecm'; + service = TestBed.get(NodePermissionDialogService); + materialDialog = TestBed.get(MatDialog); + afterOpenObservable = new Subject(); + nodePermissionService = TestBed.get(NodePermissionService); + spyOnDialogOpen = spyOn(materialDialog, 'open').and.returnValue({ + afterOpen: () => afterOpenObservable, + afterClosed: () => Observable.of({}), + componentInstance: { + error: new Subject() + } + }); + }); + + it('should be able to create the service', () => { + expect(service).not.toBeNull(); + }); + + it('should be able to open the dialog showing node permissions', () => { + service.openAddPermissionDialog('fake-node-id', 'fake-title'); + expect(spyOnDialogOpen).toHaveBeenCalled(); + }); + + it('should throw an error if the update of the node fails', async(() => { + spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(Observable.throw({error : 'error'})); + spyOn(service, 'openAddPermissionDialog').and.returnValue(Observable.of({})); + service.updateNodePermissionByDialog('fake-node-id', 'fake-title').subscribe(() => { + Observable.throw('This call should fail'); + }, (error) => { + expect(error.error).toBe('error'); + }); + })); + + it('should return the updated node', async(() => { + spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(Observable.of({id : 'fake-node'})); + spyOn(service, 'openAddPermissionDialog').and.returnValue(Observable.of({})); + service.updateNodePermissionByDialog('fake-node-id', 'fake-title').subscribe((node) => { + expect(node.id).toBe('fake-node'); + }); + })); + +}); diff --git a/lib/content-services/permission-manager/services/node-permission-dialog.service.ts b/lib/content-services/permission-manager/services/node-permission-dialog.service.ts new file mode 100644 index 0000000000..b0c433021b --- /dev/null +++ b/lib/content-services/permission-manager/services/node-permission-dialog.service.ts @@ -0,0 +1,64 @@ +/*! + * @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 { MatDialog } from '@angular/material'; +import { Injectable } from '@angular/core'; +import { Subject } from 'rxjs/Subject'; +import { Observable } from 'rxjs/Observable'; +import { AddPermissionDialogComponent } from '../components/add-permission/add-permission-dialog.component'; +import { AddPermissionDialogData } from '../components/add-permission/add-permission-dialog-data.interface'; +import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { NodePermissionService } from './node-permission.service'; + +@Injectable() +export class NodePermissionDialogService { + + constructor(private dialog: MatDialog, + private nodePermissionService: NodePermissionService) { + } + + openAddPermissionDialog(nodeId: string, title?: string): Observable { + const confirm = new Subject(); + + confirm.subscribe({ + complete: this.close.bind(this) + }); + + const data: AddPermissionDialogData = { + nodeId: nodeId, + title: title, + confirm : confirm + }; + + this.openDialog(data, 'adf-add-permission-dialog', '630px'); + return confirm; + } + + private openDialog(data: any, currentPanelClass: string, chosenWidth: string) { + this.dialog.open(AddPermissionDialogComponent, { data, panelClass: currentPanelClass, width: chosenWidth }); + } + + close() { + this.dialog.closeAll(); + } + + updateNodePermissionByDialog(nodeId?: string, title?: string): Observable { + return this.openAddPermissionDialog(nodeId, title).switchMap((selection) => { + return this.nodePermissionService.updateNodePermissions(nodeId, selection); + }); + } +} 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 index b45554d6b7..588f80426a 100644 --- a/lib/content-services/permission-manager/services/node-permission.service.spec.ts +++ b/lib/content-services/permission-manager/services/node-permission.service.spec.ts @@ -20,7 +20,10 @@ import { NodePermissionService } from './node-permission.service'; import { SearchService, NodesApiService, setupTestBed, CoreModule } 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'; +import { fakeEmptyResponse, fakeNodeWithOnlyLocally, fakeSiteRoles, fakeSiteNodeResponse, + fakeNodeToRemovePermission } from '../../mock/permission-list.component.mock'; +import { fakeAuthorityResults } from '../../mock/add-permission.component.mock'; +import { NodePermissionDialogService } from './node-permission-dialog.service'; describe('NodePermissionService', () => { @@ -33,6 +36,7 @@ describe('NodePermissionService', () => { CoreModule.forRoot() ], providers: [ + NodePermissionDialogService, NodePermissionService ] }); @@ -85,7 +89,7 @@ describe('NodePermissionService', () => { spyOn(nodeService, 'updateNode').and.callFake((nodeId, permissionBody) => returnUpdatedNode(nodeId, permissionBody)); - service.updatePermissionRoles(fakeNodeWithOnlyLocally, fakePermission).subscribe((node: MinimalNodeEntryEntity) => { + service.updatePermissionRole(fakeNodeWithOnlyLocally, fakePermission).subscribe((node: MinimalNodeEntryEntity) => { expect(node).not.toBeNull(); expect(node.id).toBe('fake-updated-node'); expect(node.permissions.locallySet.length).toBe(1); @@ -95,4 +99,53 @@ describe('NodePermissionService', () => { }); })); + it('should be able to remove a locally set permission', async(() => { + const fakePermission: PermissionElement = { + 'authorityId': 'FAKE_PERSON_1', + 'name': 'Contributor', + 'accessStatus' : 'ALLOWED' + }; + spyOn(nodeService, 'updateNode').and.callFake((nodeId, permissionBody) => returnUpdatedNode(nodeId, permissionBody)); + const fakeNodeCopy = Object.assign(fakeNodeToRemovePermission); + + service.removePermission(fakeNodeCopy, fakePermission).subscribe((node: MinimalNodeEntryEntity) => { + expect(node).not.toBeNull(); + expect(node.id).toBe('fake-updated-node'); + expect(node.permissions.locallySet.length).toBe(2); + expect(node.permissions.locallySet[0].authorityId).not.toBe(fakePermission.authorityId); + expect(node.permissions.locallySet[1].authorityId).not.toBe(fakePermission.authorityId); + }); + })); + + it('should be able to update locally set permissions on the node by node id', async(() => { + const fakeNodeCopy = Object.assign(fakeNodeWithOnlyLocally); + spyOn(nodeService, 'getNode').and.returnValue(Observable.of(fakeNodeCopy)); + spyOn(nodeService, 'updateNode').and.callFake((nodeId, permissionBody) => returnUpdatedNode(nodeId, permissionBody)); + spyOn(searchApiService, 'searchByQueryBody').and.returnValue(Observable.of(fakeSiteNodeResponse)); + spyOn(service, 'getGroupMemeberByGroupName').and.returnValue(Observable.of(fakeSiteRoles)); + + service.updateNodePermissions('fake-node-id', fakeAuthorityResults).subscribe((node: MinimalNodeEntryEntity) => { + expect(node).not.toBeNull(); + expect(node.id).toBe('fake-updated-node'); + expect(node.permissions.locallySet.length).toBe(4); + expect(node.permissions.locallySet[3].authorityId).not.toBe(fakeAuthorityResults[0].entry['cm:userName']); + expect(node.permissions.locallySet[2].authorityId).not.toBe(fakeAuthorityResults[1].entry['cm:userName']); + expect(node.permissions.locallySet[1].authorityId).not.toBe(fakeAuthorityResults[2].entry['cm:userName']); + }); + })); + + it('should be able to update locally permissions on the node', async(() => { + const fakeNodeCopy = Object.assign(fakeNodeWithOnlyLocally); + spyOn(nodeService, 'updateNode').and.callFake((nodeId, permissionBody) => returnUpdatedNode(nodeId, permissionBody)); + + service.updateLocallySetPermissions(fakeNodeCopy, fakeAuthorityResults, fakeSiteRoles).subscribe((node: MinimalNodeEntryEntity) => { + expect(node).not.toBeNull(); + expect(node.id).toBe('fake-updated-node'); + expect(node.permissions.locallySet.length).toBe(4); + expect(node.permissions.locallySet[3].authorityId).not.toBe(fakeAuthorityResults[0].entry['cm:userName']); + expect(node.permissions.locallySet[2].authorityId).not.toBe(fakeAuthorityResults[1].entry['cm:userName']); + expect(node.permissions.locallySet[1].authorityId).not.toBe(fakeAuthorityResults[2].entry['cm:userName']); + }); + })); + }); diff --git a/lib/content-services/permission-manager/services/node-permission.service.ts b/lib/content-services/permission-manager/services/node-permission.service.ts index 6c3bb34f39..63d74e73fa 100644 --- a/lib/content-services/permission-manager/services/node-permission.service.ts +++ b/lib/content-services/permission-manager/services/node-permission.service.ts @@ -18,8 +18,10 @@ 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'; +import { QueryBody, MinimalNodeEntryEntity, MinimalNodeEntity, PathElement, GroupMemberEntry, GroupsPaging, GroupMemberPaging, PermissionElement } from 'alfresco-js-api'; import 'rxjs/add/operator/switchMap'; +import { of } from 'rxjs/observable/of'; +import { switchMap } from 'rxjs/operators'; @Injectable() export class NodePermissionService { @@ -42,7 +44,7 @@ export class NodePermissionService { }); } - updatePermissionRoles(node: MinimalNodeEntryEntity, updatedPermissionRole: PermissionElement): Observable { + updatePermissionRole(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); @@ -54,6 +56,47 @@ export class NodePermissionService { return this.nodeService.updateNode(node.id, permissionBody); } + updateNodePermissions(nodeId: string, permissionList: MinimalNodeEntity[]): Observable { + return this.nodeService.getNode(nodeId).pipe( + switchMap(node => { + return this.getNodeRoles(node).pipe( + switchMap((nodeRoles) => of({node, nodeRoles}) ) + ); + }), + switchMap(({node, nodeRoles}) => this.updateLocallySetPermissions(node, permissionList, nodeRoles)) + ); + } + + updateLocallySetPermissions(node: MinimalNodeEntryEntity, nodes: MinimalNodeEntity[], nodeRole: string[]): Observable { + let permissionBody = { permissions: { locallySet: []} }; + const permissionList = this.transformNodeToPermissionElement(nodes, nodeRole[0]); + permissionBody.permissions.locallySet = node.permissions.locallySet ? node.permissions.locallySet.concat(permissionList) : permissionList; + return this.nodeService.updateNode(node.id, permissionBody); + } + + private transformNodeToPermissionElement(nodes: MinimalNodeEntity[], nodeRole: any): PermissionElement[] { + return nodes.map((node) => { + let newPermissionElement: PermissionElement = { + 'authorityId': node.entry.properties['cm:authorityName'] ? + node.entry.properties['cm:authorityName'] : + node.entry.properties['cm:userName'], + 'name': nodeRole, + 'accessStatus': 'ALLOWED' + }; + return newPermissionElement; + }); + } + + removePermission(node: MinimalNodeEntryEntity, permissionToRemove: PermissionElement): Observable { + let permissionBody = { permissions: { locallySet: [] } }; + const index = node.permissions.locallySet.map((permission) => permission.authorityId).indexOf(permissionToRemove.authorityId); + if (index !== -1) { + node.permissions.locallySet.splice(index, 1); + permissionBody.permissions.locallySet = node.permissions.locallySet; + return this.nodeService.updateNode(node.id, permissionBody); + } + } + private getGroupMembersBySiteName(siteName: string): Observable { const groupName = 'GROUP_site_' + siteName; return this.getGroupMemeberByGroupName(groupName) diff --git a/lib/content-services/search/components/search-trigger.directive.ts b/lib/content-services/search/components/search-trigger.directive.ts index 4c9b16eb68..f27f3cb892 100644 --- a/lib/content-services/search/components/search-trigger.directive.ts +++ b/lib/content-services/search/components/search-trigger.directive.ts @@ -201,7 +201,7 @@ export class SearchTriggerDirective implements ControlValueAccessor, OnDestroy { } private setValueAndClose(event: any | null): void { - if (this.isPanelOptionClicked(event)) { + if (this.isPanelOptionClicked(event) && !event.defaultPrevented) { this.setTriggerValue(event.target.textContent.trim()); this.onChange(event.target.textContent.trim()); this.element.nativeElement.focus(); diff --git a/lib/content-services/styles/_index.scss b/lib/content-services/styles/_index.scss index bf8be12a5d..547bbbbf41 100644 --- a/lib/content-services/styles/_index.scss +++ b/lib/content-services/styles/_index.scss @@ -15,6 +15,9 @@ @import '../content-node-selector/content-node-selector.component'; @import '../content-metadata/content-metadata.module'; @import '../permission-manager/components/permission-list/permission-list.component'; +@import '../permission-manager/components/add-permission/add-permission.component'; +@import '../permission-manager/components/add-permission/add-permission-dialog.component'; +@import '../permission-manager/components/add-permission/add-permission-panel.component'; @mixin adf-content-services-theme($theme) { @include adf-breadcrumb-theme($theme); @@ -30,4 +33,7 @@ @include adf-content-node-selector-dialog-theme($theme) ; @include adf-content-metadata-module-theme($theme); @include adf-permission-list-theme($theme); + @include adf-add-permission-theme($theme); + @include adf-add-permission-dialog-theme($theme); + @include adf-add-permission-panel-theme($theme); }