Disable upload button with no permission (#1820)

* Add the permission check on the activiti button

* Fix code after unit test

* Add basic documentation
This commit is contained in:
Maurizio Vitale
2017-04-20 12:06:10 +01:00
committed by Mario Romano
parent 721e96c01b
commit 0e4dab8b66
11 changed files with 301 additions and 29 deletions

View File

@@ -178,6 +178,43 @@ Attribute | Options | Default | Description | Mandatory
`currentFolderPath` | *string* | '/Sites/swsdp/documentLibrary' | define the path where the files are uploaded |
`versioning` | *boolean* | false | Versioning false is the default uploader behaviour and it rename using an integer suffix if there is a name clash. Versioning true to indicate that a major version should be created |
`staticTitle` | *string* | 'FILE_UPLOAD.BUTTON.UPLOAD_FILE' or 'FILE_UPLOAD.BUTTON.UPLOAD_FOLDER' string in the JSON text file | define the text of the upload button|
`disableWithNoPermission` | *boolean* | false | If the value is true and the user doesn't have the permission to delete the node the button will be disabled |
### How to show notification message with no permission
You can show a notification error when the user doesn't have the right permission to perform the action.
The UploadButtonComponent provides the event permissionEvent that is raised when the delete permission is missing
You can subscribe to this event from your component and use the NotificationService to show a message.
```html
<alfresco-upload-button
[rootFolderId]="currentFolderId"
(permissionEvent)="onUploadPermissionFailed($event)">
</alfresco-upload-button>
export class MyComponent {
onUploadPermissionFailed(event: any) {
this.notificationService.openSnackMessage(`you don't have the ${event.permission} permission to ${event.action} the ${event.type} `, 4000);
}
}
```
![Upload notification message](docs/assets/upload-notification-message.png)
#### How to disable the button when the delete permission is missing
You can easily disable the button when the user doesn't own the permission to perform the action.
The UploadButtonComponent provides the property disableWithNoPermission that can be true. In this way the button should be disabled if the delete permission is missing for the node.
```html
<alfresco-upload-button
[rootFolderId]="currentFolderId"
[disableWithNoPermission]="true">
</alfresco-upload-button>
```
![Upload disable button](docs/assets/upload-disable-button.png)
### Drag and drop

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

View File

@@ -9,6 +9,27 @@
z-index: 4;
}
.mdl-button--file--disabled {
background: rgba(0,0,0,.12);
color: rgba(0,0,0,.26);
}
.mdl-button--file--disabled:hover {
background: rgba(0,0,0,.12);
color: rgba(0,0,0,.26);
}
.mdl-button--file--disabled:focus:not(:active) {
box-shadow: 0 0 8px rgba(0,0,0,.18), 0 8px 16px rgba(0,0,0,.36);
background-color: rgba(158,158,158,.4);
}
.mdl-button--file--disabled input[type="file"]:disabled {
cursor: default;
background-color: transparent;
}
.mdl-textfield--file .mdl-textfield__input {
box-sizing: border-box;
width: calc(100% - 32px);

View File

@@ -1,6 +1,6 @@
<form>
<!--Files Upload-->
<div *ngIf="!uploadFolders" class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored mdl-button--file">
<div *ngIf="!uploadFolders" [class.mdl-button--file--disabled]="disableButton" class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored mdl-button--file">
<i class="material-icons">file_upload</i>
<!--Multiple Files Upload-->
@@ -11,6 +11,7 @@
(change)="onFilesAdded($event)"
multiple="multiple"
accept="{{acceptedFilesType}}"
[attr.disabled]="disableButton"
#uploadFiles>
</span>
@@ -21,6 +22,7 @@
<input id="upload-single-file" data-automation-id="upload-single-file" type="file" name="uploadFiles"
(change)="onFilesAdded($event)"
accept="{{acceptedFilesType}}"
[attr.disabled]="disableButton"
#uploadFiles>
</span>
</div>
@@ -34,6 +36,7 @@
(change)="onDirectoryAdded($event)"
multiple="multiple"
accept="{{acceptedFilesType}}"
[attr.disabled]="disableButton"
webkitdirectory directory
multiple #uploadFolders>
</div>

View File

@@ -21,6 +21,7 @@ import { DebugElement } from '@angular/core';
import { CoreModule, AlfrescoTranslationService, NotificationService } from 'ng2-alfresco-core';
import { TranslationMock } from '../assets/translation.service.mock';
import { UploadService } from '../services/upload.service';
import { Observable } from 'rxjs/Rx';
describe('UploadButtonComponent', () => {
@@ -53,6 +54,25 @@ describe('UploadButtonComponent', () => {
}
};
let fakeFolderNodeWithoutPermission = {
allowableOperations: [
'update'
],
isFolder: true,
name: 'Folder Fake Name',
nodeType: 'cm:folder'
};
let fakeFolderNodeWithPermission = {
allowableOperations: [
'create',
'update'
],
isFolder: true,
name: 'Folder Fake Name',
nodeType: 'cm:folder'
};
let fakeRejectPromise = new Promise(function (resolve, reject) {
reject(fakeRejectRest);
});
@@ -117,9 +137,70 @@ describe('UploadButtonComponent', () => {
expect(compiled.querySelector('#uploadFolder')).toBeDefined();
});
it('should emit the permissionEvent, without permission and disableWithNoPermission false', (done) => {
component.rootFolderId = '-my-';
component.disableWithNoPermission = false;
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission));
fixture.detectChanges();
component.permissionEvent.subscribe( permission => {
expect(permission).toBeDefined();
expect(permission.type).toEqual('content');
expect(permission.action).toEqual('upload');
expect(permission.permission).toEqual('create');
done();
});
component.onFilesAdded(fakeEvent);
});
it('should show the disabled button, without permission and disableWithNoPermission true', () => {
component.rootFolderId = '-my-';
component.disableWithNoPermission = true;
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission));
component.onFilesAdded(fakeEvent);
let compiled = fixture.debugElement.nativeElement;
fixture.detectChanges();
expect(compiled.querySelector('#upload-single-file')).toBeDefined();
expect(compiled.querySelector('#upload-single-file').disabled).toBe(true);
});
it('should show the enabled button with permission and disableWithNoPermission true', () => {
component.rootFolderId = '-my-';
component.disableWithNoPermission = true;
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
component.onFilesAdded(fakeEvent);
let compiled = fixture.debugElement.nativeElement;
fixture.detectChanges();
expect(compiled.querySelector('#upload-single-file')).toBeDefined();
expect(compiled.querySelector('#upload-single-file').disabled).toBe(false);
});
it('should show the enabled button with permission and disableWithNoPermission false', () => {
component.rootFolderId = '-my-';
component.disableWithNoPermission = false;
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
component.onFilesAdded(fakeEvent);
let compiled = fixture.debugElement.nativeElement;
fixture.detectChanges();
expect(compiled.querySelector('#upload-single-file')).toBeDefined();
expect(compiled.querySelector('#upload-single-file').disabled).toBe(false);
});
it('should call uploadFile with the default root folder', () => {
component.currentFolderPath = '/root-fake-/sites-fake/folder-fake';
component.onSuccess = null;
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue');
fixture.detectChanges();
@@ -132,6 +213,8 @@ describe('UploadButtonComponent', () => {
component.currentFolderPath = '/root-fake-/sites-fake/folder-fake';
component.rootFolderId = '-my-';
component.onSuccess = null;
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue');
fixture.detectChanges();
@@ -144,6 +227,7 @@ describe('UploadButtonComponent', () => {
component.currentFolderPath = '/fake-root-path';
fixture.detectChanges();
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
spyOn(uploadService, 'callApiCreateFolder').and.returnValue(fakeResolvePromise);
component.onSuccess.subscribe(e => {
@@ -161,6 +245,7 @@ describe('UploadButtonComponent', () => {
});
it('should emit an onError event when the folder already exist', (done) => {
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
spyOn(uploadService, 'callApiCreateFolder').and.returnValue(fakeRejectPromise);
component.onError.subscribe(e => {
expect(e.value).toEqual('FILE_UPLOAD.MESSAGES.FOLDER_ALREADY_EXIST');

View File

@@ -15,11 +15,12 @@
* limitations under the License.
*/
import { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core';
import 'rxjs/Rx';
import { Component, ElementRef, Input, Output, EventEmitter, OnInit, OnChanges } from '@angular/core';
import { Subject } from 'rxjs/Rx';
import { AlfrescoTranslationService, LogService, NotificationService } from 'ng2-alfresco-core';
import { UploadService } from '../services/upload.service';
import { FileModel } from '../models/file.model';
import { PermissionModel } from '../models/permissions.model';
declare let componentHandler: any;
@@ -51,7 +52,7 @@ const ERROR_FOLDER_ALREADY_EXIST = 409;
templateUrl: './upload-button.component.html',
styleUrls: ['./upload-button.component.css']
})
export class UploadButtonComponent {
export class UploadButtonComponent implements OnInit, OnChanges {
private static DEFAULT_ROOT_ID: string = '-root-';
@@ -79,6 +80,9 @@ export class UploadButtonComponent {
@Input()
rootFolderId: string = UploadButtonComponent.DEFAULT_ROOT_ID;
@Input()
disableWithNoPermission: boolean = false;
@Output()
onSuccess = new EventEmitter();
@@ -88,6 +92,13 @@ export class UploadButtonComponent {
@Output()
createFolder = new EventEmitter();
@Output()
permissionEvent: EventEmitter<PermissionModel> = new EventEmitter<PermissionModel>();
private disableButton: boolean = false;
private permissionValue: Subject<boolean> = new Subject<boolean>();
constructor(private el: ElementRef,
private uploadService: UploadService,
private translateService: AlfrescoTranslationService,
@@ -98,7 +109,18 @@ export class UploadButtonComponent {
}
}
ngOnInit() {
this.permissionValue.subscribe((hasPermission: boolean) => {
if (!hasPermission && this.disableWithNoPermission) {
this.disableButton = true;
} else {
this.disableButton = undefined;
}
});
}
ngOnChanges(changes) {
this.checkPermission();
let formFields = this.createFormFields();
this.uploadService.setOptions(formFields, this.versioning);
}
@@ -110,7 +132,15 @@ export class UploadButtonComponent {
*/
onFilesAdded($event: any): void {
let files = $event.currentTarget.files;
this.uploadFiles(this.currentFolderPath, files);
this.permissionValue.subscribe((hasPermission: boolean) => {
if (hasPermission) {
this.uploadFiles(this.currentFolderPath, files);
} else {
this.permissionEvent.emit(new PermissionModel({type: 'content', action: 'upload', permission: 'create'}));
}
});
this.checkPermission();
// reset the value of the input file
$event.target.value = '';
}
@@ -122,31 +152,39 @@ export class UploadButtonComponent {
*/
onDirectoryAdded($event: any): void {
let files = $event.currentTarget.files;
let hashMapDir = this.convertIntoHashMap(files);
this.permissionValue.subscribe((hasPermission: boolean) => {
if (hasPermission) {
let hashMapDir = this.convertIntoHashMap(files);
hashMapDir.forEach((filesDir, directoryPath) => {
let directoryName = this.getDirectoryName(directoryPath);
let absolutePath = this.currentFolderPath + this.getDirectoryPath(directoryPath);
hashMapDir.forEach((filesDir, directoryPath) => {
let directoryName = this.getDirectoryName(directoryPath);
let absolutePath = this.currentFolderPath + this.getDirectoryPath(directoryPath);
this.uploadService.createFolder(absolutePath, directoryName, this.rootFolderId)
.subscribe(
res => {
let relativeDir = this.currentFolderPath + '/' + directoryPath;
this.uploadFiles(relativeDir, filesDir);
},
error => {
let errorMessagePlaceholder = this.getErrorMessage(error.response);
if (errorMessagePlaceholder) {
this.onError.emit({value: errorMessagePlaceholder});
let errorMessage = this.formatString(errorMessagePlaceholder, [directoryName]);
if (errorMessage) {
this._showErrorNotificationBar(errorMessage);
this.uploadService.createFolder(absolutePath, directoryName, this.rootFolderId)
.subscribe(
res => {
let relativeDir = this.currentFolderPath + '/' + directoryPath;
this.uploadFiles(relativeDir, filesDir);
},
error => {
let errorMessagePlaceholder = this.getErrorMessage(error.response);
if (errorMessagePlaceholder) {
this.onError.emit({value: errorMessagePlaceholder});
let errorMessage = this.formatString(errorMessagePlaceholder, [directoryName]);
if (errorMessage) {
this._showErrorNotificationBar(errorMessage);
}
}
this.logService.error(error);
}
}
this.logService.error(error);
}
);
);
});
} else {
this.permissionEvent.emit(new PermissionModel({type: 'content', action: 'upload', permission: 'create'}));
}
});
this.checkPermission();
// reset the value of the input file
$event.target.value = '';
}
@@ -272,4 +310,28 @@ export class UploadButtonComponent {
}
};
}
checkPermission() {
if (this.rootFolderId) {
this.uploadService.getFolderNode(this.rootFolderId).subscribe(
res => {
this.permissionValue.next(this.hasCreatePermission(res));
},
error => {
this.logService.error(error);
}
);
}
}
private hasCreatePermission(node: any): boolean {
if (this.hasPermissions(node)) {
return node.allowableOperations.find(permision => permision === 'create') ? true : false;
}
return false;
}
private hasPermissions(node: any): boolean {
return node && node.allowableOperations ? true : false;
}
}

View File

@@ -0,0 +1,30 @@
/*!
* @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 class PermissionModel {
type: string;
action: string;
permission: string;
constructor(obj?: any) {
if (obj) {
this.type = obj.type || null;
this.action = obj.action || null;
this.permission = obj.permission || null;
}
}
}

View File

@@ -20,7 +20,7 @@ import { Response } from '@angular/http';
import { Observer, Observable } from 'rxjs/Rx';
import { AlfrescoApiService, LogService } from 'ng2-alfresco-core';
import { FileModel } from '../models/file.model';
import { MinimalNodeEntity } from 'alfresco-js-api';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
/**
*
@@ -184,4 +184,17 @@ export class UploadService {
this.totalCompletedObserver.next(total);
}
}
getFolderNode(nodeId: string): Observable<MinimalNodeEntryEntity> {
let opts: any = {
includeSource: true,
include: ['allowableOperations']
};
return Observable.fromPromise(this.apiService.getInstance().nodes.getNodeInfo(nodeId, opts))
.map((response: any) => {
return response;
})
.catch(err => this.handleError(err));
}
}