[ADF-1040] Change document list style rows based on permissions model (#2085)

* Change document list style rows based on permissions model

* fix test
This commit is contained in:
Eugenio Romano
2017-07-17 17:22:08 +01:00
parent 6947e04208
commit a9142e2ca2
29 changed files with 382 additions and 167 deletions

View File

@@ -50,6 +50,7 @@
</ng-container> </ng-container>
<adf-document-list <adf-document-list
#documentList #documentList
[permissionsStyle]="permissionsStyle"
[creationMenuActions]="!useCustomToolbar" [creationMenuActions]="!useCustomToolbar"
[currentFolderId]="currentFolderId" [currentFolderId]="currentFolderId"
[contextMenuActions]="true" [contextMenuActions]="true"

View File

@@ -19,7 +19,7 @@ import { ChangeDetectorRef, Component, Input, OnInit, Optional, ViewChild } from
import { MdDialog } from '@angular/material'; import { MdDialog } from '@angular/material';
import { ActivatedRoute, Params } from '@angular/router'; import { ActivatedRoute, Params } from '@angular/router';
import { AlfrescoContentService, FileUploadCompleteEvent, FolderCreatedEvent, NotificationService, UploadService } from 'ng2-alfresco-core'; import { AlfrescoContentService, FileUploadCompleteEvent, FolderCreatedEvent, NotificationService, UploadService } from 'ng2-alfresco-core';
import { DocumentListComponent } from 'ng2-alfresco-documentlist'; import { DocumentListComponent, PermissionStyleModel } from 'ng2-alfresco-documentlist';
import { CreateFolderDialogComponent } from '../../dialogs/create-folder.dialog'; import { CreateFolderDialogComponent } from '../../dialogs/create-folder.dialog';
@@ -65,6 +65,8 @@ export class FilesComponent implements OnInit {
@ViewChild(DocumentListComponent) @ViewChild(DocumentListComponent)
documentList: DocumentListComponent; documentList: DocumentListComponent;
permissionsStyle: PermissionStyleModel[] = [];
constructor(private changeDetector: ChangeDetectorRef, constructor(private changeDetector: ChangeDetectorRef,
private notificationService: NotificationService, private notificationService: NotificationService,
private uploadService: UploadService, private uploadService: UploadService,

View File

@@ -39,8 +39,7 @@ module.exports = {
loader: 'tslint-loader', loader: 'tslint-loader',
options: { options: {
emitErrors: true, emitErrors: true,
failOnHint: true, failOnHint: true
fix:true
}, },
exclude: [/node_modules/, /bundles/, /dist/, /demo/] exclude: [/node_modules/, /bundles/, /dist/, /demo/]
}, },

View File

@@ -117,7 +117,11 @@ export * from './src/events/base.event';
export * from './src/events/base-ui.event'; export * from './src/events/base-ui.event';
export * from './src/events/folder-created.event'; export * from './src/events/folder-created.event';
export * from './src/events/file.event'; export * from './src/events/file.event';
export * from './src/models/index';
export * from './src/models/card-view-textitem.model';
export * from './src/models/card-view-dateitem.model';
export * from './src/models/file.model';
export * from './src/models/permissions.enum';
export * from './src/models/index'; export * from './src/models/index';

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16">
<path fill="#000" fill-opacity=".28" fill-rule="evenodd" d="M8 0H2C.9 0 0 .9 0 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2h-8L8 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 239 B

View File

@@ -15,6 +15,13 @@
* limitations under the License. * limitations under the License.
*/ */
export * from './card-view-textitem.model'; export class PermissionsEnum extends String {
export * from './card-view-dateitem.model'; static DELETE: string = 'delete';
export * from './file.model'; static UPDATE: string = 'update';
static CREATE: string = 'create';
static UPDATEPERMISSIONS: string = 'updatePermissions';
static NOT_DELETE: string = '!delete';
static NOT_UPDATE: string = '!update';
static NOT_CREATE: string = '!create';
static NOT_UPDATEPERMISSIONS: string = '!updatePermissions';
}

View File

@@ -44,8 +44,7 @@ describe('AlfrescoContentService', () => {
imports: [ imports: [
AppConfigModule AppConfigModule
], ],
declarations: [ declarations: [],
],
providers: [ providers: [
AlfrescoApiService, AlfrescoApiService,
AlfrescoContentService, AlfrescoContentService,
@@ -53,7 +52,7 @@ describe('AlfrescoContentService', () => {
AlfrescoSettingsService, AlfrescoSettingsService,
StorageService, StorageService,
UserPreferencesService, UserPreferencesService,
{ provide: CookieService, useClass: CookieServiceMock }, {provide: CookieService, useClass: CookieServiceMock},
LogService LogService
] ]
}).compileComponents(); }).compileComponents();
@@ -111,4 +110,20 @@ describe('AlfrescoContentService', () => {
responseText: JSON.stringify({'entry': {'id': 'fake-post-ticket', 'userId': 'admin'}}) responseText: JSON.stringify({'entry': {'id': 'fake-post-ticket', 'userId': 'admin'}})
}); });
}); });
it('should havePermission should be false if allowableOperation is not present in the node', () => {
let permissionNode = {};
expect(contentService.hasPermission(permissionNode, 'create')).toBeFalsy();
});
it('should havePermission should be true if allowableOperation is present and you have the permission for the request operation', () => {
let permissionNode = {allowableOperations: ['delete', 'update', 'create', 'updatePermissions']};
expect(contentService.hasPermission(permissionNode, 'create')).toBeTruthy();
});
it('should havePermission should be false if allowableOperation is present but you don\'t have the permission for the request operation', () => {
let permissionNode = {allowableOperations: ['delete', 'update', 'updatePermissions']};
expect(contentService.hasPermission(permissionNode, 'create')).toBeFalsy();
});
}); });

View File

@@ -18,6 +18,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs/Rx'; import { Observable, Subject } from 'rxjs/Rx';
import { FolderCreatedEvent } from '../events/folder-created.event'; import { FolderCreatedEvent } from '../events/folder-created.event';
import { PermissionsEnum } from '../models/permissions.enum';
import { AlfrescoApiService } from './alfresco-api.service'; import { AlfrescoApiService } from './alfresco-api.service';
import { AuthenticationService } from './authentication.service'; import { AuthenticationService } from './authentication.service';
import { LogService } from './log.service'; import { LogService } from './log.service';
@@ -71,6 +72,7 @@ export class AlfrescoContentService {
return dataContent; return dataContent;
})).catch(this.handleError); })).catch(this.handleError);
} }
/** /**
* Create a folder * Create a folder
* @param name - the folder name * @param name - the folder name

View File

@@ -50,7 +50,8 @@ export class ThumbnailService {
'application/vnd.apple.keynote': require('../assets/images/ft_ic_presentation.svg'), 'application/vnd.apple.keynote': require('../assets/images/ft_ic_presentation.svg'),
'application/vnd.apple.pages': require('../assets/images/ft_ic_document.svg'), 'application/vnd.apple.pages': require('../assets/images/ft_ic_document.svg'),
'application/vnd.apple.numbers': require('../assets/images/ft_ic_spreadsheet.svg'), 'application/vnd.apple.numbers': require('../assets/images/ft_ic_spreadsheet.svg'),
'folder': require('../assets/images/ft_ic_folder.svg') 'folder': require('../assets/images/ft_ic_folder.svg'),
'disable/folder': require('../assets/images/ft_ic_folder_disable.svg')
}; };
constructor(public contentService: AlfrescoContentService) { constructor(public contentService: AlfrescoContentService) {

View File

@@ -531,9 +531,11 @@ interface DataTableAdapter {
} }
interface DataRow { interface DataRow {
isSelected: boolean; isSelected: boolean;
hasValue(key: string): boolean; isDropTarget?: boolean;
getValue(key: string): any; hasValue(key: string): boolean;
getValue(key: string): any;
cssClass?: string;
} }
interface DataColumn { interface DataColumn {

View File

@@ -35,7 +35,7 @@
[class.is-selected]="row.isSelected" [class.is-selected]="row.isSelected"
[adf-upload]="allowDropFiles && rowAllowsDrop(row)" [adf-upload-data]="row" [adf-upload]="allowDropFiles && rowAllowsDrop(row)" [adf-upload-data]="row"
[ngStyle]="rowStyle" [ngStyle]="rowStyle"
[ngClass]="rowStyleClass"> [ngClass]="getRowStyle(row)">
<!-- Actions (left) --> <!-- Actions (left) -->
<td *ngIf="actions && actionsPosition === 'left'"> <td *ngIf="actions && actionsPosition === 'left'">

View File

@@ -69,7 +69,7 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
rowStyle: string; rowStyle: string;
@Input() @Input()
rowStyleClass: string; rowStyleClass: string = '';
@Input() @Input()
showHeader: boolean = true; showHeader: boolean = true;
@@ -107,10 +107,9 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
private singleClickStreamSub: Subscription; private singleClickStreamSub: Subscription;
private multiClickStreamSub: Subscription; private multiClickStreamSub: Subscription;
constructor( constructor(translateService: AlfrescoTranslationService,
translateService: AlfrescoTranslationService, @Optional() private el: ElementRef,
@Optional() private el: ElementRef, private differs: IterableDiffers) {
private differs: IterableDiffers) {
if (differs) { if (differs) {
this.differ = differs.find([]).create(null); this.differ = differs.find([]).create(null);
} }
@@ -130,7 +129,7 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
if (this.isPropertyChanged(changes['data'])) { if (this.isPropertyChanged(changes['data'])) {
if (this.isTableEmpty()) { if (this.isTableEmpty()) {
this.initTable(); this.initTable();
}else { } else {
this.data = changes['data'].currentValue; this.data = changes['data'].currentValue;
} }
return; return;
@@ -207,7 +206,7 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
} }
private unsubscribeClickStream() { private unsubscribeClickStream() {
if (this.singleClickStreamSub) { if (this.singleClickStreamSub) {
this.singleClickStreamSub.unsubscribe(); this.singleClickStreamSub.unsubscribe();
} }
if (this.multiClickStreamSub) { if (this.multiClickStreamSub) {
@@ -379,4 +378,10 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
return this.selectionMode && this.selectionMode.toLowerCase() === 'multiple'; return this.selectionMode && this.selectionMode.toLowerCase() === 'multiple';
} }
getRowStyle(row: DataRow): string {
row.cssClass = row.cssClass ? row.cssClass : '';
this.rowStyleClass = this.rowStyleClass ? this.rowStyleClass : '';
return `${row.cssClass} ${this.rowStyleClass}`;
}
} }

View File

@@ -33,6 +33,7 @@ export interface DataTableAdapter {
export interface DataRow { export interface DataRow {
isSelected: boolean; isSelected: boolean;
isDropTarget?: boolean; isDropTarget?: boolean;
cssClass?: string;
hasValue(key: string): boolean; hasValue(key: string): boolean;
getValue(key: string): any; getValue(key: string): any;
} }

View File

@@ -89,7 +89,8 @@ The properties currentFolderId, folderNode and node are the entry initialization
| rowStyleClass | string | | The CSS class to apply to every row | | rowStyleClass | string | | The CSS class to apply to every row |
| currentFolderId | string | null | Initial node ID of displayed folder. Can be `-root-`, `-shared-`, `-my-`, or a fixed node ID | | currentFolderId | string | null | Initial node ID of displayed folder. Can be `-root-`, `-shared-`, `-my-`, or a fixed node ID |
| folderNode | [MinimalNodeEntryEntity](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeMinimalEntry.md) | null | Currently displayed folder node | | folderNode | [MinimalNodeEntryEntity](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeMinimalEntry.md) | null | Currently displayed folder node |
| node | `NodePaging` | null | Document list will show all the node contained in the NodePaging entity | | permissionsStyle | [PermissionStyleModel[]](https://github.com/Alfresco/alfresco-ng2-components/blob/master/ng2-components/ng2-alfresco-documentlist/src/models/permissions-style.model.ts) | null | with this array you can define different styles depending from the permission of the user on that node. The PermissionStyleModel allow you to select also if you want apply the style only on the file or folder nodes. With PermissionStyleModel.permission accept the following values [Permissions](https://github.com/Alfresco/alfresco-ng2-components/blob/master/ng2-components/ng2-alfresco-core/src/models/permissions.enum.ts) [see more](#custom-row-permissions-style). |
| node | [NodePaging](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodePaging.md) | null | Document list will show all the node contained in the NodePaging entity |
| navigate | boolean | true | Toggles navigation to folder content or file preview | | navigate | boolean | true | Toggles navigation to folder content or file preview |
| loading | boolean | false | Toggles the loading state and animated spinners for the component. Used in combination with `navigate=false` to perform custom navigation and loading state indication. | | loading | boolean | false | Toggles the loading state and animated spinners for the component. Used in combination with `navigate=false` to perform custom navigation and loading state indication. |
| navigationMode | string (click,dblclick) | dblclick | User interaction for folder navigation or file preview | | navigationMode | string (click,dblclick) | dblclick | User interaction for folder navigation or file preview |
@@ -1025,6 +1026,64 @@ Now you can declare columns and assign `desktop-only` class where needed:
![Responsive Mobile](docs/assets/responsive-mobile.png) ![Responsive Mobile](docs/assets/responsive-mobile.png)
### Custom row permissions style
You can personalize the style of the row based on the permissions.
The property to valorize is permissionsStyle[]:[PermissionStyleModel[]](https://github.com/Alfresco/alfresco-ng2-components/blob/master/ng2-components/ng2-alfresco-documentlist/src/models/permissions-style.model.ts).
The permissionsStyle array can define different styles depending from the permission of the user on that node.
[PermissionStyleModel](https://github.com/Alfresco/alfresco-ng2-components/blob/master/ng2-components/ng2-alfresco-documentlist/src/models/permissions-style.model.ts)
| Property | Description |
| isFile/isFolder | allow you to select if you want apply the style to file/folder nodes |
| permission | is an enum value [Permissions](https://github.com/Alfresco/alfresco-ng2-core/blob/master/ng2-components/ng2-alfresco-documentlist/src/models/permissions.enum.ts) |
| css| the name of the class to add |
#### Examples
If you want to change the style on rows where the user can create content:
```ts
let permissionsStyle: PermissionStyleModel[] = [];
this.permissionsStyle.push(new PermissionStyleModel('document-list__create', PermissionsEnum.CREATE));
```
```html
<adf-document-list [permissionsStyle]="permissionsStyle">
</adf-document-list>
```
```css
adf-document-list >>> adf-datatable >>> tr.alfresco-datatable__row.document-list__create {
color: rgb(57, 239, 121);
}
```
If you want to change the style on the folders where the user doesn't have the permission to update it:
```ts
let permissionsStyle: PermissionStyleModel[] = [];
this.permissionsStyle.push(new PermissionStyleModel('document-list__disable', PermissionsEnum.NOT_CREATE, false, true));
```
```html
<adf-document-list [permissionsStyle]="permissionsStyle">
</adf-document-list>
```
```css
adf-document-list >>> adf-datatable >>> tr.alfresco-datatable__row.document-list__disable {
color: rgba(0, 0, 0, 0.28);
}
```
### Custom 'empty folder' template ### Custom 'empty folder' template
By default DocumentList provides the following content for the empty folder: By default DocumentList provides the following content for the empty folder:

View File

@@ -56,6 +56,7 @@ export * from './src/services/document-list.service';
export * from './src/models/content-action.model'; export * from './src/models/content-action.model';
export * from './src/models/document-library.model'; export * from './src/models/document-library.model';
export * from './src/models/permissions.model'; export * from './src/models/permissions.model';
export * from './src/models/permissions-style.model';
export const DOCUMENT_LIST_DIRECTIVES: any[] = [ export const DOCUMENT_LIST_DIRECTIVES: any[] = [
DocumentListComponent, DocumentListComponent,

View File

@@ -50,6 +50,15 @@ module.exports = function (config) {
Chrome_travis_ci: { Chrome_travis_ci: {
base: 'Chrome', base: 'Chrome',
flags: ['--no-sandbox'] flags: ['--no-sandbox']
},
Chrome_headless: {
base: 'Chrome',
flags: [
'--no-sandbox',
'--headless',
'--disable-gpu',
'--remote-debugging-port=9222'
]
} }
}, },

View File

@@ -16,7 +16,8 @@
*/ */
import { EventEmitter } from '@angular/core'; import { EventEmitter } from '@angular/core';
import { async, TestBed } from '@angular/core/testing';
import { AlfrescoContentService, CoreModule } from 'ng2-alfresco-core';
import { FileNode } from './../../assets/document-library.model.mock'; import { FileNode } from './../../assets/document-library.model.mock';
import { DocumentListServiceMock } from './../../assets/document-list.service.mock'; import { DocumentListServiceMock } from './../../assets/document-list.service.mock';
import { ContentActionHandler } from './../../models/content-action.model'; import { ContentActionHandler } from './../../models/content-action.model';
@@ -33,10 +34,24 @@ describe('ContentAction', () => {
let documentActions: DocumentActionsService; let documentActions: DocumentActionsService;
let folderActions: FolderActionsService; let folderActions: FolderActionsService;
let contentService: AlfrescoContentService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
],
providers: [
AlfrescoContentService
]
}).compileComponents();
}));
beforeEach(() => { beforeEach(() => {
contentService = TestBed.get(AlfrescoContentService);
let documentServiceMock = new DocumentListServiceMock(); let documentServiceMock = new DocumentListServiceMock();
documentActions = new DocumentActionsService(null, null); documentActions = new DocumentActionsService(null, null);
folderActions = new FolderActionsService(null); folderActions = new FolderActionsService(null, contentService);
documentList = new DocumentListComponent(documentServiceMock, null, null, null); documentList = new DocumentListComponent(documentServiceMock, null, null, null);
actionList = new ContentActionListComponent(documentList); actionList = new ContentActionListComponent(documentList);
@@ -70,7 +85,8 @@ describe('ContentAction', () => {
it('should get action handler from document actions service', () => { it('should get action handler from document actions service', () => {
let handler = function() {}; let handler = function () {
};
spyOn(documentActions, 'getHandler').and.returnValue(handler); spyOn(documentActions, 'getHandler').and.returnValue(handler);
let action = new ContentActionComponent(actionList, documentActions, null); let action = new ContentActionComponent(actionList, documentActions, null);
@@ -86,7 +102,8 @@ describe('ContentAction', () => {
}); });
it('should get action handler from folder actions service', () => { it('should get action handler from folder actions service', () => {
let handler = function() {}; let handler = function () {
};
spyOn(folderActions, 'getHandler').and.returnValue(handler); spyOn(folderActions, 'getHandler').and.returnValue(handler);
let action = new ContentActionComponent(actionList, null, folderActions); let action = new ContentActionComponent(actionList, null, folderActions);
@@ -188,14 +205,16 @@ describe('ContentAction', () => {
}); });
it('should find document action handler via service', () => { it('should find document action handler via service', () => {
let handler = <ContentActionHandler> function (obj: any, target?: any) {}; let handler = <ContentActionHandler> function (obj: any, target?: any) {
};
let action = new ContentActionComponent(actionList, documentActions, null); let action = new ContentActionComponent(actionList, documentActions, null);
spyOn(documentActions, 'getHandler').and.returnValue(handler); spyOn(documentActions, 'getHandler').and.returnValue(handler);
expect(action.getSystemHandler('document', 'name')).toBe(handler); expect(action.getSystemHandler('document', 'name')).toBe(handler);
}); });
it('should find folder action handler via service', () => { it('should find folder action handler via service', () => {
let handler = <ContentActionHandler> function (obj: any, target?: any) {}; let handler = <ContentActionHandler> function (obj: any, target?: any) {
};
let action = new ContentActionComponent(actionList, null, folderActions); let action = new ContentActionComponent(actionList, null, folderActions);
spyOn(folderActions, 'getHandler').and.returnValue(handler); spyOn(folderActions, 'getHandler').and.returnValue(handler);
expect(action.getSystemHandler('folder', 'name')).toBe(handler); expect(action.getSystemHandler('folder', 'name')).toBe(handler);

View File

@@ -747,7 +747,7 @@ describe('DocumentList', () => {
it('should emit [nodeClick] event on row click', () => { it('should emit [nodeClick] event on row click', () => {
let node = new NodeMinimalEntry(); let node = new NodeMinimalEntry();
let row = new ShareDataRow(node); let row = new ShareDataRow(node, null, null);
let event = new DataRowEvent(row, null); let event = new DataRowEvent(row, null);
spyOn(documentList, 'onNodeClick').and.callThrough(); spyOn(documentList, 'onNodeClick').and.callThrough();
@@ -757,7 +757,7 @@ describe('DocumentList', () => {
it('should emit node-click DOM event', (done) => { it('should emit node-click DOM event', (done) => {
let node = new NodeMinimalEntry(); let node = new NodeMinimalEntry();
let row = new ShareDataRow(node); let row = new ShareDataRow(node, null, null);
let event = new DataRowEvent(row, null); let event = new DataRowEvent(row, null);
const htmlElement = fixture.debugElement.nativeElement as HTMLElement; const htmlElement = fixture.debugElement.nativeElement as HTMLElement;
@@ -770,7 +770,7 @@ describe('DocumentList', () => {
it('should emit [nodeDblClick] event on row double-click', () => { it('should emit [nodeDblClick] event on row double-click', () => {
let node = new NodeMinimalEntry(); let node = new NodeMinimalEntry();
let row = new ShareDataRow(node); let row = new ShareDataRow(node, null, null);
let event = new DataRowEvent(row, null); let event = new DataRowEvent(row, null);
spyOn(documentList, 'onNodeDblClick').and.callThrough(); spyOn(documentList, 'onNodeDblClick').and.callThrough();
@@ -780,7 +780,7 @@ describe('DocumentList', () => {
it('should emit node-dblclick DOM event', (done) => { it('should emit node-dblclick DOM event', (done) => {
let node = new NodeMinimalEntry(); let node = new NodeMinimalEntry();
let row = new ShareDataRow(node); let row = new ShareDataRow(node, null, null);
let event = new DataRowEvent(row, null); let event = new DataRowEvent(row, null);
const htmlElement = fixture.debugElement.nativeElement as HTMLElement; const htmlElement = fixture.debugElement.nativeElement as HTMLElement;

View File

@@ -21,18 +21,11 @@ import {
} from '@angular/core'; } from '@angular/core';
import { MinimalNodeEntity, MinimalNodeEntryEntity, NodePaging, Pagination } from 'alfresco-js-api'; import { MinimalNodeEntity, MinimalNodeEntryEntity, NodePaging, Pagination } from 'alfresco-js-api';
import { AlfrescoTranslationService, DataColumnListComponent } from 'ng2-alfresco-core'; import { AlfrescoTranslationService, DataColumnListComponent } from 'ng2-alfresco-core';
import { import { DataCellEvent, DataColumn, DataRowActionEvent, DataRowEvent, DataSorting, DataTableComponent, ObjectDataColumn } from 'ng2-alfresco-datatable';
DataCellEvent,
DataColumn,
DataRowActionEvent,
DataRowEvent,
DataSorting,
DataTableComponent,
ObjectDataColumn
} from 'ng2-alfresco-datatable';
import { Observable, Subject } from 'rxjs/Rx'; import { Observable, Subject } from 'rxjs/Rx';
import { ImageResolver, RowFilter, ShareDataRow, ShareDataTableAdapter } from './../data/share-datatable-adapter'; import { ImageResolver, RowFilter, ShareDataRow, ShareDataTableAdapter } from './../data/share-datatable-adapter';
import { ContentActionModel } from './../models/content-action.model'; import { ContentActionModel } from './../models/content-action.model';
import { PermissionStyleModel } from './../models/permissions-style.model';
import { DocumentListService } from './../services/document-list.service'; import { DocumentListService } from './../services/document-list.service';
import { NodeEntityEvent, NodeEntryEvent } from './node.event'; import { NodeEntityEvent, NodeEntryEvent } from './node.event';
@@ -51,6 +44,9 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
@ContentChild(DataColumnListComponent) columnList: DataColumnListComponent; @ContentChild(DataColumnListComponent) columnList: DataColumnListComponent;
@Input()
permissionsStyle: PermissionStyleModel[] = [];
@Input() @Input()
navigate: boolean = true; navigate: boolean = true;
@@ -202,6 +198,8 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
ngOnInit() { ngOnInit() {
this.data = new ShareDataTableAdapter(this.documentListService, null, this.getDefaultSorting()); this.data = new ShareDataTableAdapter(this.documentListService, null, this.getDefaultSorting());
this.data.thumbnails = this.thumbnails; this.data.thumbnails = this.thumbnails;
this.data.permissionsStyle = this.permissionsStyle;
this.contextActionHandler.subscribe(val => this.contextActionCallback(val)); this.contextActionHandler.subscribe(val => this.contextActionCallback(val));
this.enforceSingleClickNavigationForMobile(); this.enforceSingleClickNavigationForMobile();

View File

@@ -18,14 +18,7 @@
import { SimpleChange } from '@angular/core'; import { SimpleChange } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MdButtonModule, MdIconModule, MdMenuModule } from '@angular/material'; import { MdButtonModule, MdIconModule, MdMenuModule } from '@angular/material';
import { import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core';
AlfrescoApiService,
AlfrescoAuthenticationService,
AlfrescoSettingsService,
AlfrescoTranslationService,
CoreModule,
LogService
} from 'ng2-alfresco-core';
import { DocumentListService } from './../services/document-list.service'; import { DocumentListService } from './../services/document-list.service';
import { DocumentMenuActionComponent } from './document-menu-action.component'; import { DocumentMenuActionComponent } from './document-menu-action.component';
@@ -90,17 +83,14 @@ describe('Document menu action', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
CoreModule, CoreModule.forRoot(),
MdMenuModule, MdMenuModule,
MdButtonModule, MdButtonModule,
MdIconModule MdIconModule
], ],
declarations: [DocumentMenuActionComponent], declarations: [DocumentMenuActionComponent],
providers: [ providers: [
AlfrescoSettingsService, AlfrescoTranslationService,
AlfrescoAuthenticationService,
AlfrescoApiService,
LogService,
DocumentListService DocumentListService
] ]
}); });

View File

@@ -17,7 +17,7 @@
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
import { MinimalNodeEntity } from 'alfresco-js-api'; import { MinimalNodeEntity } from 'alfresco-js-api';
import { AlfrescoTranslationService, LogService } from 'ng2-alfresco-core'; import { AlfrescoContentService, AlfrescoTranslationService, LogService } from 'ng2-alfresco-core';
import { PermissionModel } from '../models/permissions.model'; import { PermissionModel } from '../models/permissions.model';
import { ContentActionModel } from './../models/content-action.model'; import { ContentActionModel } from './../models/content-action.model';
@@ -62,7 +62,8 @@ export class DocumentMenuActionComponent implements OnChanges {
constructor(private documentListService: DocumentListService, constructor(private documentListService: DocumentListService,
private translateService: AlfrescoTranslationService, private translateService: AlfrescoTranslationService,
private logService: LogService) { private logService: LogService,
private contentService: AlfrescoContentService) {
if (translateService) { if (translateService) {
translateService.addTranslationFolder('ng2-alfresco-documentlist', 'assets/ng2-alfresco-documentlist'); translateService.addTranslationFolder('ng2-alfresco-documentlist', 'assets/ng2-alfresco-documentlist');
@@ -154,17 +155,8 @@ export class DocumentMenuActionComponent implements OnChanges {
return !this.hasCreatePermission() && this.disableWithNoPermission ? true : undefined; return !this.hasCreatePermission() && this.disableWithNoPermission ? true : undefined;
} }
hasPermission(permission: string): boolean {
let hasPermission: boolean = false;
if (this.allowableOperations) {
let permFound = this.allowableOperations.find(element => element === permission);
hasPermission = permFound ? true : false;
}
return hasPermission;
}
hasCreatePermission() { hasCreatePermission() {
return this.hasPermission('create'); return this.contentService.hasPermission(this, 'create');
} }
loadCurrentNodePermissions(nodeId: string) { loadCurrentNodePermissions(nodeId: string) {

View File

@@ -130,7 +130,7 @@ describe('ShareDataTableAdapter', () => {
format: 'medium' // Jul 15, 2015, 9:43:11 PM format: 'medium' // Jul 15, 2015, 9:43:11 PM
}; };
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null); let adapter = new ShareDataTableAdapter(documentListService, null);
let value = adapter.getValue(row, col); let value = adapter.getValue(row, col);
@@ -150,7 +150,7 @@ describe('ShareDataTableAdapter', () => {
format: null format: null
}; };
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null); let adapter = new ShareDataTableAdapter(documentListService, null);
let value = adapter.getValue(row, col); let value = adapter.getValue(row, col);
@@ -168,7 +168,7 @@ describe('ShareDataTableAdapter', () => {
type: 'string' type: 'string'
}; };
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null); let adapter = new ShareDataTableAdapter(documentListService, null);
let value = adapter.getValue(row, col); let value = adapter.getValue(row, col);
@@ -186,7 +186,7 @@ describe('ShareDataTableAdapter', () => {
format: 'medium' format: 'medium'
}; };
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null); let adapter = new ShareDataTableAdapter(documentListService, null);
spyOn(console, 'error').and.stub(); spyOn(console, 'error').and.stub();
@@ -201,7 +201,7 @@ describe('ShareDataTableAdapter', () => {
let file = new FileNode(); let file = new FileNode();
file.entry.content.mimeType = null; file.entry.content.mimeType = null;
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
let col = <DataColumn> {type: 'image', key: '$thumbnail'}; let col = <DataColumn> {type: 'image', key: '$thumbnail'};
let value = adapter.getValue(row, col); let value = adapter.getValue(row, col);
@@ -215,7 +215,7 @@ describe('ShareDataTableAdapter', () => {
let file = new FileNode(); let file = new FileNode();
file.entry.content = null; file.entry.content = null;
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
let col = <DataColumn> {type: 'image', key: '$thumbnail'}; let col = <DataColumn> {type: 'image', key: '$thumbnail'};
let value = adapter.getValue(row, col); let value = adapter.getValue(row, col);
@@ -230,7 +230,7 @@ describe('ShareDataTableAdapter', () => {
file.entry['icon'] = imageUrl; file.entry['icon'] = imageUrl;
let adapter = new ShareDataTableAdapter(documentListService, null); let adapter = new ShareDataTableAdapter(documentListService, null);
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
let col = <DataColumn> {type: 'image', key: 'icon'}; let col = <DataColumn> {type: 'image', key: 'icon'};
let value = adapter.getValue(row, col); let value = adapter.getValue(row, col);
@@ -240,7 +240,7 @@ describe('ShareDataTableAdapter', () => {
it('should resolve folder icon', () => { it('should resolve folder icon', () => {
let adapter = new ShareDataTableAdapter(documentListService, null); let adapter = new ShareDataTableAdapter(documentListService, null);
let row = new ShareDataRow(new FolderNode()); let row = new ShareDataRow(new FolderNode(), documentListService, null);
let col = <DataColumn> {type: 'image', key: '$thumbnail'}; let col = <DataColumn> {type: 'image', key: '$thumbnail'};
let value = adapter.getValue(row, col); let value = adapter.getValue(row, col);
@@ -256,7 +256,7 @@ describe('ShareDataTableAdapter', () => {
adapter.thumbnails = true; adapter.thumbnails = true;
let file = new FileNode(); let file = new FileNode();
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
let col = <DataColumn> {type: 'image', key: '$thumbnail'}; let col = <DataColumn> {type: 'image', key: '$thumbnail'};
let value = adapter.getValue(row, col); let value = adapter.getValue(row, col);
@@ -271,7 +271,7 @@ describe('ShareDataTableAdapter', () => {
file.entry.isFile = false; file.entry.isFile = false;
file.entry.isFolder = false; file.entry.isFolder = false;
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
let col = <DataColumn> {type: 'image', key: '$thumbnail'}; let col = <DataColumn> {type: 'image', key: '$thumbnail'};
let value = adapter.getValue(row, col); let value = adapter.getValue(row, col);
@@ -289,9 +289,9 @@ describe('ShareDataTableAdapter', () => {
adapter.setSorting(new DataSorting('name', 'asc')); adapter.setSorting(new DataSorting('name', 'asc'));
adapter.setRows([ adapter.setRows([
new ShareDataRow(file2), new ShareDataRow(file2, documentListService, null),
new ShareDataRow(file1), new ShareDataRow(file1, documentListService, null),
new ShareDataRow(folder) new ShareDataRow(folder, documentListService, null)
]); ]);
let sorted = adapter.getRows(); let sorted = adapter.getRows();
@@ -311,8 +311,8 @@ describe('ShareDataTableAdapter', () => {
let adapter = new ShareDataTableAdapter(documentListService, [col]); let adapter = new ShareDataTableAdapter(documentListService, [col]);
adapter.setRows([ adapter.setRows([
new ShareDataRow(file2), new ShareDataRow(file2, documentListService, null),
new ShareDataRow(file1) new ShareDataRow(file1, documentListService, null)
]); ]);
adapter.sort('dateProp', 'asc'); adapter.sort('dateProp', 'asc');
@@ -330,27 +330,44 @@ describe('ShareDataTableAdapter', () => {
describe('ShareDataRow', () => { describe('ShareDataRow', () => {
let documentListService: DocumentListService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
],
providers: [
DocumentListService
]
}).compileComponents();
}));
beforeEach(() => {
documentListService = TestBed.get(DocumentListService);
});
it('should wrap node', () => { it('should wrap node', () => {
let file = new FileNode(); let file = new FileNode();
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
expect(row.node).toBe(file); expect(row.node).toBe(file);
}); });
it('should require object source', () => { it('should require object source', () => {
expect(() => { expect(() => {
return new ShareDataRow(null); return new ShareDataRow(null, documentListService, null);
}).toThrowError(ShareDataRow.ERR_OBJECT_NOT_FOUND); }).toThrowError(ShareDataRow.ERR_OBJECT_NOT_FOUND);
}); });
it('should resolve value from node entry', () => { it('should resolve value from node entry', () => {
let file = new FileNode('test'); let file = new FileNode('test');
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
expect(row.getValue('name')).toBe('test'); expect(row.getValue('name')).toBe('test');
}); });
it('should check value', () => { it('should check value', () => {
let file = new FileNode('test'); let file = new FileNode('test');
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
expect(row.hasValue('name')).toBeTruthy(); expect(row.hasValue('name')).toBeTruthy();
expect(row.hasValue('missing')).toBeFalsy(); expect(row.hasValue('missing')).toBeFalsy();
@@ -359,21 +376,21 @@ describe('ShareDataRow', () => {
it('should be set as drop target when user has permission for that node', () => { it('should be set as drop target when user has permission for that node', () => {
let file = new FolderNode('test'); let file = new FolderNode('test');
file.entry['allowableOperations'] = ['create']; file.entry['allowableOperations'] = ['create'];
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
expect(row.isDropTarget).toBeTruthy(); expect(row.isDropTarget).toBeTruthy();
}); });
it('should not be set as drop target when user has permission for that node', () => { it('should not be set as drop target when user has permission for that node', () => {
let file = new FolderNode('test'); let file = new FolderNode('test');
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
expect(row.isDropTarget).toBeFalsy(); expect(row.isDropTarget).toBeFalsy();
}); });
it('should not be set as drop target when element is not a Folder', () => { it('should not be set as drop target when element is not a Folder', () => {
let file = new FileNode('test'); let file = new FileNode('test');
let row = new ShareDataRow(file); let row = new ShareDataRow(file, documentListService, null);
expect(row.isDropTarget).toBeFalsy(); expect(row.isDropTarget).toBeFalsy();
}); });

View File

@@ -16,10 +16,11 @@
*/ */
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { MinimalNode, MinimalNodeEntity, NodePaging } from 'alfresco-js-api';
import { ObjectUtils } from 'ng2-alfresco-core'; import { ObjectUtils } from 'ng2-alfresco-core';
import { PermissionsEnum } from 'ng2-alfresco-core';
import { DataColumn, DataRow, DataSorting, DataTableAdapter } from 'ng2-alfresco-datatable'; import { DataColumn, DataRow, DataSorting, DataTableAdapter } from 'ng2-alfresco-datatable';
import { PermissionStyleModel } from './../models/permissions-style.model';
import { MinimalNodeEntity, NodePaging } from 'alfresco-js-api';
import { DocumentListService } from './../services/document-list.service'; import { DocumentListService } from './../services/document-list.service';
declare var require: any; declare var require: any;
@@ -42,6 +43,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
private imageResolver: ImageResolver; private imageResolver: ImageResolver;
thumbnails: boolean = false; thumbnails: boolean = false;
permissionsStyle: PermissionStyleModel[];
selectedRow: DataRow; selectedRow: DataRow;
constructor(private documentListService: DocumentListService, constructor(private documentListService: DocumentListService,
@@ -194,7 +196,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
if (page && page.list) { if (page && page.list) {
let data = page.list.entries; let data = page.list.entries;
if (data && data.length > 0) { if (data && data.length > 0) {
rows = data.map(item => new ShareDataRow(item)); rows = data.map(item => new ShareDataRow(item, this.documentListService, this.permissionsStyle));
if (this.filter) { if (this.filter) {
rows = rows.filter(this.filter); rows = rows.filter(this.filter);
@@ -229,34 +231,53 @@ export class ShareDataRow implements DataRow {
cache: { [key: string]: any } = {}; cache: { [key: string]: any } = {};
isSelected: boolean = false; isSelected: boolean = false;
isDropTarget: boolean; isDropTarget: boolean;
cssClass: string = '';
get node(): MinimalNodeEntity { get node(): MinimalNodeEntity {
return this.obj; return this.obj;
} }
constructor(private obj: MinimalNodeEntity) { constructor(private obj: MinimalNodeEntity, private documentListService: DocumentListService, private permissionsStyle: PermissionStyleModel[]) {
if (!obj) { if (!obj) {
throw new Error(ShareDataRow.ERR_OBJECT_NOT_FOUND); throw new Error(ShareDataRow.ERR_OBJECT_NOT_FOUND);
} }
this.isDropTarget = this.isFolderAndHasPermissionToUpload(obj); this.isDropTarget = this.isFolderAndHasPermissionToUpload(obj);
if (permissionsStyle) {
this.cssClass = this.getPermissionClass(obj);
}
}
getPermissionClass(nodeEntity: MinimalNodeEntity): string {
let permissionsClasses = '';
this.permissionsStyle.forEach((currentPermissionsStyle: PermissionStyleModel) => {
if (this.applyPermissionStyleToFolder(nodeEntity.entry, currentPermissionsStyle) || this.applyPermissionStyleToFile(nodeEntity.entry, currentPermissionsStyle)) {
if (currentPermissionsStyle.permission.startsWith('!') && !this.documentListService.hasPermission(nodeEntity.entry, currentPermissionsStyle.permission)) {
permissionsClasses += ` ${currentPermissionsStyle.css}`;
} else if (this.documentListService.hasPermission(nodeEntity.entry, currentPermissionsStyle.permission)) {
permissionsClasses += ` ${currentPermissionsStyle.css}`;
}
}
});
return permissionsClasses;
}
private applyPermissionStyleToFile(node: MinimalNode, currentPermissionsStyle: PermissionStyleModel): boolean {
return (currentPermissionsStyle.isFile && node.isFile);
}
private applyPermissionStyleToFolder(node: MinimalNode, currentPermissionsStyle: PermissionStyleModel): boolean {
return (currentPermissionsStyle.isFolder && node.isFolder);
} }
isFolderAndHasPermissionToUpload(obj: MinimalNodeEntity): boolean { isFolderAndHasPermissionToUpload(obj: MinimalNodeEntity): boolean {
return this.isFolder(obj) && this.hasCreatePermission(obj); return this.isFolder(obj) && this.documentListService.hasPermission(obj.entry, 'create');
}
hasCreatePermission(obj: MinimalNodeEntity): boolean {
return this.hasPermission(obj, 'create');
}
private hasPermission(obj: MinimalNodeEntity, permission: string): boolean {
let hasPermission: boolean = false;
if (obj.entry && obj.entry['allowableOperations']) {
let permFound = obj.entry['allowableOperations'].find(element => element === permission);
hasPermission = permFound ? true : false;
}
return hasPermission;
} }
isFolder(obj: MinimalNodeEntity): boolean { isFolder(obj: MinimalNodeEntity): boolean {

View File

@@ -0,0 +1,32 @@
/*!
* @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 { PermissionsEnum } from 'ng2-alfresco-core';
export class PermissionStyleModel {
css: string;
permission: PermissionsEnum;
isFolder: boolean = true;
isFile: boolean = true;
constructor(css: string, permission: PermissionsEnum, isFile: boolean = true, isFolder: boolean = true) {
this.css = css;
this.permission = permission;
this.isFile = isFile;
this.isFolder = isFolder;
}
}

View File

@@ -103,32 +103,21 @@ export class DocumentActionsService {
} }
private deleteNode(obj: any, target?: any, permission?: string): Observable<any> { private deleteNode(obj: any, target?: any, permission?: string): Observable<any> {
let handlerObservale; let handlerObservable;
if (this.canExecuteAction(obj)) { if (this.canExecuteAction(obj)) {
if (this.hasPermission(obj.entry, permission)) { if (this.contentService.hasPermission(obj.entry, permission)) {
handlerObservale = this.documentListService.deleteNode(obj.entry.id); handlerObservable = this.documentListService.deleteNode(obj.entry.id);
handlerObservale.subscribe(() => { handlerObservable.subscribe(() => {
if (target && typeof target.reload === 'function') { if (target && typeof target.reload === 'function') {
target.reload(); target.reload();
} }
}); });
return handlerObservale; return handlerObservable;
} else { } else {
this.permissionEvent.next(new PermissionModel({type: 'content', action: 'delete', permission: permission})); this.permissionEvent.next(new PermissionModel({type: 'content', action: 'delete', permission: permission}));
return Observable.throw(new Error('No permission to delete')); return Observable.throw(new Error('No permission to delete'));
} }
} }
} }
private hasPermission(node: any, permission: string): boolean {
if (this.hasPermissions(node)) {
return node.allowableOperations.find(permision => permision === permission) ? true : false;
}
return false;
}
private hasPermissions(node: any): boolean {
return node && node.allowableOperations ? true : false;
}
} }

View File

@@ -16,12 +16,7 @@
*/ */
import { async, TestBed } from '@angular/core/testing'; import { async, TestBed } from '@angular/core/testing';
import { import { CookieService, CoreModule, LogService, LogServiceMock } from 'ng2-alfresco-core';
CookieService,
CoreModule,
LogService,
LogServiceMock
} from 'ng2-alfresco-core';
import { CookieServiceMock } from '../../../ng2-alfresco-core/src/assets/cookie.service.mock'; import { CookieServiceMock } from '../../../ng2-alfresco-core/src/assets/cookie.service.mock';
import { DocumentListService } from './document-list.service'; import { DocumentListService } from './document-list.service';

View File

@@ -18,7 +18,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Response } from '@angular/http'; import { Response } from '@angular/http';
import { MinimalNodeEntity, MinimalNodeEntryEntity, NodePaging } from 'alfresco-js-api'; import { MinimalNodeEntity, MinimalNodeEntryEntity, NodePaging } from 'alfresco-js-api';
import { AlfrescoApiService, AlfrescoAuthenticationService, AlfrescoContentService, LogService, ThumbnailService } from 'ng2-alfresco-core'; import { AlfrescoApiService, AlfrescoAuthenticationService, AlfrescoContentService, LogService, PermissionsEnum, ThumbnailService } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx'; import { Observable } from 'rxjs/Rx';
@Injectable() @Injectable()
@@ -115,6 +115,10 @@ export class DocumentListService {
return this.thumbnailService.getDefaultMimeTypeIcon(); return this.thumbnailService.getDefaultMimeTypeIcon();
} }
hasPermission(node: any, permission: PermissionsEnum|string): boolean {
return this.contentService.hasPermission(node, permission);
}
private handleError(error: Response) { private handleError(error: Response) {
// in a real world app, we may send the error to some remote logging infrastructure // in a real world app, we may send the error to some remote logging infrastructure
// instead of just logging it to the console // instead of just logging it to the console

View File

@@ -15,8 +15,10 @@
* limitations under the License. * limitations under the License.
*/ */
import { async, TestBed } from '@angular/core/testing';
import { AppConfigModule, CoreModule } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { FileNode, FolderNode } from '../assets/document-library.model.mock'; import { FileNode, FolderNode } from '../assets/document-library.model.mock';
import { DocumentListServiceMock } from '../assets/document-list.service.mock';
import { ContentActionHandler } from '../models/content-action.model'; import { ContentActionHandler } from '../models/content-action.model';
import { DocumentListService } from './document-list.service'; import { DocumentListService } from './document-list.service';
import { FolderActionsService } from './folder-actions.service'; import { FolderActionsService } from './folder-actions.service';
@@ -26,13 +28,29 @@ describe('FolderActionsService', () => {
let service: FolderActionsService; let service: FolderActionsService;
let documentListService: DocumentListService; let documentListService: DocumentListService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot(),
AppConfigModule.forRoot('app.config.json', {
ecmHost: 'http://localhost:9876/ecm'
})
],
providers: [
DocumentListService,
FolderActionsService
]
}).compileComponents();
}));
beforeEach(() => { beforeEach(() => {
documentListService = new DocumentListServiceMock(); service = TestBed.get(FolderActionsService);
service = new FolderActionsService(documentListService); documentListService = TestBed.get(DocumentListService);
}); });
it('should register custom action handler', () => { it('should register custom action handler', () => {
let handler: ContentActionHandler = function () {}; let handler: ContentActionHandler = function () {
};
service.setHandler('<key>', handler); service.setHandler('<key>', handler);
expect(service.getHandler('<key>')).toBe(handler); expect(service.getHandler('<key>')).toBe(handler);
}); });
@@ -42,7 +60,8 @@ describe('FolderActionsService', () => {
}); });
it('should be case insensitive for keys', () => { it('should be case insensitive for keys', () => {
let handler: ContentActionHandler = function () {}; let handler: ContentActionHandler = function () {
};
service.setHandler('<key>', handler); service.setHandler('<key>', handler);
expect(service.getHandler('<KEY>')).toBe(handler); expect(service.getHandler('<KEY>')).toBe(handler);
}); });
@@ -55,9 +74,6 @@ describe('FolderActionsService', () => {
it('should allow action execution only when service available', () => { it('should allow action execution only when service available', () => {
let folder = new FolderNode(); let folder = new FolderNode();
expect(service.canExecuteAction(folder)).toBeTruthy(); expect(service.canExecuteAction(folder)).toBeTruthy();
service = new FolderActionsService(null);
expect(service.canExecuteAction(folder)).toBeFalsy();
}); });
it('should allow action execution only for folder nodes', () => { it('should allow action execution only for folder nodes', () => {
@@ -67,7 +83,8 @@ describe('FolderActionsService', () => {
}); });
it('should set new handler only by key', () => { it('should set new handler only by key', () => {
let handler: ContentActionHandler = function () {}; let handler: ContentActionHandler = function () {
};
expect(service.setHandler(null, handler)).toBeFalsy(); expect(service.setHandler(null, handler)).toBeFalsy();
expect(service.setHandler('', handler)).toBeFalsy(); expect(service.setHandler('', handler)).toBeFalsy();
expect(service.setHandler('my-handler', handler)).toBeTruthy(); expect(service.setHandler('my-handler', handler)).toBeTruthy();
@@ -110,12 +127,17 @@ describe('FolderActionsService', () => {
}); });
it('should delete the folder node if there is the delete permission', () => { it('should delete the folder node if there is the delete permission', () => {
spyOn(documentListService, 'deleteNode').and.callThrough(); spyOn(documentListService, 'deleteNode').and.callFake(() => {
return new Observable<any>(observer => {
observer.next();
observer.complete();
});
});
let permission = 'delete'; let permission = 'delete';
let folder = new FolderNode(); let folder = new FolderNode();
let folderWithPermission: any = folder; let folderWithPermission: any = folder;
folderWithPermission.entry.allowableOperations = [ permission ]; folderWithPermission.entry.allowableOperations = [permission];
const deleteObservale = service.getHandler('delete')(folderWithPermission, null, permission); const deleteObservale = service.getHandler('delete')(folderWithPermission, null, permission);
expect(documentListService.deleteNode).toHaveBeenCalledWith(folder.entry.id); expect(documentListService.deleteNode).toHaveBeenCalledWith(folder.entry.id);
@@ -123,7 +145,12 @@ describe('FolderActionsService', () => {
}); });
it('should not delete the folder node if there is no delete permission', (done) => { it('should not delete the folder node if there is no delete permission', (done) => {
spyOn(documentListService, 'deleteNode').and.callThrough(); spyOn(documentListService, 'deleteNode').and.callFake(() => {
return new Observable<any>(observer => {
observer.next();
observer.complete();
});
});
service.permissionEvent.subscribe((permission) => { service.permissionEvent.subscribe((permission) => {
expect(permission).toBeDefined(); expect(permission).toBeDefined();
@@ -139,7 +166,12 @@ describe('FolderActionsService', () => {
}); });
it('should call the error on the returned Observable if there is no delete permission', (done) => { it('should call the error on the returned Observable if there is no delete permission', (done) => {
spyOn(documentListService, 'deleteNode').and.callThrough(); spyOn(documentListService, 'deleteNode').and.callFake(() => {
return new Observable<any>(observer => {
observer.next();
observer.complete();
});
});
let folder = new FolderNode(); let folder = new FolderNode();
let folderWithPermission: any = folder; let folderWithPermission: any = folder;
@@ -156,7 +188,12 @@ describe('FolderActionsService', () => {
}); });
it('should delete the folder node if there is the delete and others permission ', () => { it('should delete the folder node if there is the delete and others permission ', () => {
spyOn(documentListService, 'deleteNode').and.callThrough(); spyOn(documentListService, 'deleteNode').and.callFake(() => {
return new Observable<any>(observer => {
observer.next();
observer.complete();
});
});
let permission = 'delete'; let permission = 'delete';
let folder = new FolderNode(); let folder = new FolderNode();
@@ -168,7 +205,12 @@ describe('FolderActionsService', () => {
}); });
it('should support deletion only folder node', () => { it('should support deletion only folder node', () => {
spyOn(documentListService, 'deleteNode').and.callThrough(); spyOn(documentListService, 'deleteNode').and.callFake(() => {
return new Observable<any>(observer => {
observer.next();
observer.complete();
});
});
let permission = 'delete'; let permission = 'delete';
let file = new FileNode(); let file = new FileNode();
@@ -183,7 +225,12 @@ describe('FolderActionsService', () => {
}); });
it('should require node id to delete', () => { it('should require node id to delete', () => {
spyOn(documentListService, 'deleteNode').and.callThrough(); spyOn(documentListService, 'deleteNode').and.callFake(() => {
return new Observable<any>(observer => {
observer.next();
observer.complete();
});
});
let folder = new FolderNode(); let folder = new FolderNode();
folder.entry.id = null; folder.entry.id = null;
@@ -192,8 +239,13 @@ describe('FolderActionsService', () => {
expect(documentListService.deleteNode).not.toHaveBeenCalled(); expect(documentListService.deleteNode).not.toHaveBeenCalled();
}); });
it('should reload target upon node deletion', () => { it('should reload target upon node deletion', (done) => {
spyOn(documentListService, 'deleteNode').and.callThrough(); spyOn(documentListService, 'deleteNode').and.callFake(() => {
return new Observable<any>(observer => {
observer.next();
observer.complete();
});
});
let permission = 'delete'; let permission = 'delete';
let target = jasmine.createSpyObj('obj', ['reload']); let target = jasmine.createSpyObj('obj', ['reload']);
@@ -201,9 +253,13 @@ describe('FolderActionsService', () => {
let folderWithPermission: any = folder; let folderWithPermission: any = folder;
folderWithPermission.entry.allowableOperations = [permission]; folderWithPermission.entry.allowableOperations = [permission];
service.getHandler('delete')(folderWithPermission, target, permission); let deleteHandler = service.getHandler('delete')(folderWithPermission, target, permission);
deleteHandler.subscribe(() => {
expect(target.reload).toHaveBeenCalled();
done();
});
expect(documentListService.deleteNode).toHaveBeenCalled(); expect(documentListService.deleteNode).toHaveBeenCalled();
expect(target.reload).toHaveBeenCalled();
}); });
}); });

View File

@@ -16,6 +16,7 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { AlfrescoContentService } from 'ng2-alfresco-core';
import { Observable, Subject } from 'rxjs/Rx'; import { Observable, Subject } from 'rxjs/Rx';
import { ContentActionHandler } from '../models/content-action.model'; import { ContentActionHandler } from '../models/content-action.model';
import { PermissionModel } from '../models/permissions.model'; import { PermissionModel } from '../models/permissions.model';
@@ -28,7 +29,8 @@ export class FolderActionsService {
private handlers: { [id: string]: ContentActionHandler; } = {}; private handlers: { [id: string]: ContentActionHandler; } = {};
constructor(private documentListService?: DocumentListService) { constructor(private documentListService: DocumentListService,
private contentService: AlfrescoContentService) {
this.setupActionHandlers(); this.setupActionHandlers();
} }
@@ -87,32 +89,21 @@ export class FolderActionsService {
} }
private deleteNode(obj: any, target?: any, permission?: string): Observable<any> { private deleteNode(obj: any, target?: any, permission?: string): Observable<any> {
let handlerObservale: Observable<any>; let handlerObservable: Observable<any>;
if (this.canExecuteAction(obj)) { if (this.canExecuteAction(obj)) {
if (this.hasPermission(obj.entry, permission)) { if (this.contentService.hasPermission(obj.entry, permission)) {
handlerObservale = this.documentListService.deleteNode(obj.entry.id); handlerObservable = this.documentListService.deleteNode(obj.entry.id);
handlerObservale.subscribe(() => { handlerObservable.subscribe(() => {
if (target && typeof target.reload === 'function') { if (target && typeof target.reload === 'function') {
target.reload(); target.reload();
} }
}); });
return handlerObservale; return handlerObservable;
} else { } else {
this.permissionEvent.next(new PermissionModel({type: 'folder', action: 'delete', permission: permission})); this.permissionEvent.next(new PermissionModel({type: 'folder', action: 'delete', permission: permission}));
return Observable.throw(new Error('No permission to delete')); return Observable.throw(new Error('No permission to delete'));
} }
} }
} }
private hasPermission(node: any, permissionToCheck: string): boolean {
if (this.hasPermissions(node)) {
return node.allowableOperations.find(permision => permision === permissionToCheck) ? true : false;
}
return false;
}
private hasPermissions(node: any): boolean {
return node && node.allowableOperations ? true : false;
}
} }