[ADF-2556] Created component for add group or user to permission (#3242)

* [ADF-2556] first step to create add people or group to permissions

* [ADF-2556] creating a dialog with user results

* [ADF-2556]
integrated service for add and remove permission from node

* [ADF-2556] fixed behaviour and style for add user group

* [ADF-2556] added some refactoring for dialog service

* [ADF-2556] refactoring the dependencies of the components

* [ADF-2556] added some fix and a new key for dialog

* [ADF-2556] start adding test for node permission service

* [ADF-2556] added test for add permission panel component

* [ADf-2556] adding tests for new add permission component

* [ADF-2556] fixed tests and added documentation

* [ADF-2556] fixed documentation for add-node components

* [ADF-2556] added peer review changes
This commit is contained in:
Vito
2018-05-03 15:14:15 +01:00
committed by Eugenio Romano
parent 61a4173ad3
commit 513915b3d9
37 changed files with 1576 additions and 25 deletions

View File

@@ -2,11 +2,17 @@
<button mat-raised-button
[color]="toggleStatus?'accent':'primary'"
adf-inherit-permission [nodeId]="nodeId"
(error)="showErrorMessage($event)"
(updated)="onUpdatedPermissions($event)">
{{ (toggleStatus?'DEMO_PERMISSION.INHERITED_PERMISSIONS_BUTTON':'DEMO_PERMISSION.INHERIT_PERMISSION_BUTTON') | translate}}</button>
<button mat-button color="primary" (click)="openAddPermissionDialog($event)">Add User or Group</button>
</div>
<div>
<adf-permission-list #permissionList [nodeId]="nodeId">
<adf-permission-list #permissionList
[nodeId]="nodeId"
(update)="reloadList()"
(error)="showErrorMessage($event)">
</adf-permission-list>
</div>

View File

@@ -1,6 +1,6 @@
.inherit_permission_button {
padding-top: 20px;
display: flex;
justify-content: space-evenly;
justify-content: center;
padding-bottom: 20px;
}

View File

@@ -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
);
}
}

View File

@@ -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<MinimalNodeEntryEntity>` | |
| error | `EventEmitter<any>` | |
## 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);
});
```

View File

@@ -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
<adf-add-permission [nodeId]="nodeId"
(success)="onSuccess($event)" (error)="onError($event)">
</adf-add-permission>
```
## Class members
### Properties
| Name | Type | Default value | Description |
| -- | -- | -- | -- |
### Events
| Name | Type | Description |
| -- | -- | -- |
| select | `EventEmitter<MinimalNodeEntryEntity>` | |
## 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.

View File

@@ -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
<adf-add-permission [nodeId]="nodeId"
(success)="onSuccess($event)" (error)="onError($event)">
</adf-add-permission>
```
## Class members
### Properties
| Name | Type | Default value | Description |
| -- | -- | -- | -- |
| nodeId | `string` | "" | |
### Events
| Name | Type | Description |
| -- | -- | -- |
| success | `EventEmitter<MinimalNodeEntryEntity>` | |
| error | `EventEmitter<any>` | |
## 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -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..."
}
}
}

View File

@@ -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
}
};

View File

@@ -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',

View File

@@ -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<MinimalNodeEntity[]>;
}

View File

@@ -0,0 +1,16 @@
<h2 mat-dialog-title id="add-permission-dialog-title">
{{(data?.title ? data?.title : 'PERMISSION_MANAGER.ADD-PERMISSION.BASE-DIALOG-TITLE') | translate}}
</h2>
<mat-dialog-content>
<adf-add-permission-panel
(select)="onSelect($event)">
</adf-add-permission-panel>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close id="add-permission-dialog-close-button">{{'PERMISSION_MANAGER.ADD-PERMISSION.CLOSE-ACTION' | translate}}</button>
<button mat-button id="add-permission-dialog-confirm-button" [mat-dialog-close]="true"
class="choose-action"
[disabled]="currentSelection?.length === 0"
(click)="onAddClicked()">{{'PERMISSION_MANAGER.ADD-PERMISSION.ADD-ACTION' | translate}}</button>
</mat-dialog-actions>

View File

@@ -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);
}
}
}
}
}

View File

@@ -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<AddPermissionDialogComponent>;
let element: HTMLElement;
let data: AddPermissionDialogData = {
title: 'dead or alive you are coming with me',
nodeId: 'fake-node-id',
confirm: new Subject<MinimalNodeEntity[]> ()
};
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 = <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 = <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 = <HTMLButtonElement> element.querySelector('#add-permission-dialog-confirm-button');
expect(confirmButton.disabled).toBeTruthy();
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
confirmButton = <HTMLButtonElement> 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 = <HTMLButtonElement> element.querySelector('#add-permission-dialog-confirm-button');
confirmButton.click();
});
}));
});

View File

@@ -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();
}
}

View File

@@ -0,0 +1,54 @@
<mat-form-field floatPlaceholder="never" class="adf-permission-search-input">
<input matInput
id="searchInput"
[formControl]="searchInput"
type="text"
placeholder="{{'PERMISSION_MANAGER.ADD-PERMISSION.SEARCH' | translate}}"
[value]="searchedWord">
<mat-icon *ngIf="searchedWord?.length > 0"
class="adf-permission-search-icon"
data-automation-id="adf-permission-clear-input"
id="adf-permission-clear-input"
matSuffix (click)="clearSearch()">clear
</mat-icon>
<mat-icon *ngIf="searchedWord?.length === 0"
class="adf-permission-search-icon"
data-automation-id="adf-permission-search-icon"
matSuffix>search
</mat-icon>
</mat-form-field>
<div *ngIf="searchedWord?.length === 0" id="adf-add-permission-type-search">
<span class="adf-permission-start-message">{{'PERMISSION_MANAGER.ADD-PERMISSION.TYPE-MESSAGE' | translate}}</span>
</div>
<adf-search #search [searchTerm]="searchedWord"
id="adf-add-permission-authority-results"
class="adf-permission-result-list"
*ngIf="searchedWord.length !== 0">
<ng-template let-data>
<mat-selection-list [class.adf-permission-result-list-elements]="data?.list?.entries.length !== 0">
<mat-list-option *ngFor="let item of data?.list?.entries; let idx = index"
(click)="elementClicked(item)"
class="adf-list-option-item"
id="result_option_{{idx}}">
<mat-icon mat-list-icon id="add-group-icon"
*ngIf="item?.entry?.nodeType === 'cm:authorityContainer' else show_person_icon">
group_add
</mat-icon>
<ng-template #show_person_icon>
<mat-icon id="add-person-icon" mat-list-icon>person_add</mat-icon>
</ng-template>
<p>
{{item.entry?.properties['cm:authorityName']?
item.entry?.properties['cm:authorityName'] :
item.entry?.properties['cm:firstName']}}</p>
</mat-list-option>
</mat-selection-list>
<div *ngIf="data?.list?.entries.length === 0" class="adf-permission-no-result" id="adf-add-permission-no-results">
<span>{{'PERMISSION_MANAGER.ADD-PERMISSION.NO-RESULT' | translate}}</span>
</div>
</ng-template>
</adf-search>

View File

@@ -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);
}
}
}
}

View File

@@ -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<AddPermissionPanelComponent>;
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', {});
});
}));
});

View File

@@ -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<any> = 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();
}
}

View File

@@ -0,0 +1,14 @@
<adf-add-permission-panel
(select)="onSelect($event)">
</adf-add-permission-panel>
<div id="adf-add-permission-actions">
<button mat-button
id="adf-add-permission-action-button"
class="adf-permission-action"
[disabled]="selectedItems?.length === 0"
(click)="applySelection()">
{{'PERMISSION_MANAGER.ADD-PERMISSION.ADD-ACTION' | translate}}
</button>
</div>

View File

@@ -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);
}
}
}
}

View File

@@ -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<AddPermissionComponent>;
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 = <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 = <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 = <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 = <HTMLButtonElement> element.querySelector('#adf-add-permission-action-button');
addButton.click();
});
}));
});

View File

@@ -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<MinimalNodeEntryEntity> = new EventEmitter();
@Output()
error: EventEmitter<any> = 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);
});
}
}

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -35,15 +35,18 @@ export class InheritPermissionDirective {
@Output()
updated: EventEmitter<MinimalNodeEntryEntity> = new EventEmitter<MinimalNodeEntryEntity>();
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
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));
});
}

View File

@@ -48,6 +48,13 @@
</ng-template>
</ng-template>
</data-column>
<data-column key="delete">
<ng-template let-entry="$implicit">
<button *ngIf="!entry.row.getValue('isInherited')" mat-icon-button color="primary" (click)="removePermission(entry.row.obj)">
<mat-icon>highlight_off</mat-icon>
</button>
</ng-template>
</data-column>
</data-columns>
</adf-datatable>
</div>

View File

@@ -35,6 +35,9 @@ export class PermissionListComponent implements OnInit {
@Output()
update: EventEmitter<PermissionElement> = new EventEmitter();
@Output()
error: EventEmitter<any> = 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));
}
}

View File

@@ -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';
}
}
}

View File

@@ -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 {}

View File

@@ -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';

View File

@@ -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<any>;
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<any>();
nodePermissionService = TestBed.get(NodePermissionService);
spyOnDialogOpen = spyOn(materialDialog, 'open').and.returnValue({
afterOpen: () => afterOpenObservable,
afterClosed: () => Observable.of({}),
componentInstance: {
error: new Subject<any>()
}
});
});
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');
});
}));
});

View File

@@ -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<MinimalNodeEntity[]> {
const confirm = new Subject<MinimalNodeEntity[]>();
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<MinimalNodeEntryEntity> {
return this.openAddPermissionDialog(nodeId, title).switchMap((selection) => {
return this.nodePermissionService.updateNodePermissions(nodeId, selection);
});
}
}

View File

@@ -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 = <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']);
});
}));
});

View File

@@ -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<MinimalNodeEntryEntity> {
updatePermissionRole(node: MinimalNodeEntryEntity, updatedPermissionRole: PermissionElement): Observable<MinimalNodeEntryEntity> {
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<MinimalNodeEntryEntity> {
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<MinimalNodeEntryEntity> {
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 = <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<MinimalNodeEntryEntity> {
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<string[]> {
const groupName = 'GROUP_site_' + siteName;
return this.getGroupMemeberByGroupName(groupName)

View File

@@ -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();

View File

@@ -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);
}