#159 improved error reporting for document-list (#1180)

- expose ‘error’ event
- change path only on successful navigation
- extend demo shell with error handling and reporting
- additional unit tests for document-list
This commit is contained in:
Denys Vuika
2016-11-30 14:52:06 +00:00
committed by Eugenio Romano
parent da70a72bba
commit 7eab89c5ef
8 changed files with 171 additions and 60 deletions

View File

@@ -7,3 +7,11 @@
margin: 0; margin: 0;
} }
} }
.error-message {
text-align: left;
}
.error-message--text {
color: #d50000;
}

View File

@@ -7,11 +7,18 @@
[currentFolderPath]="currentPath" [currentFolderPath]="currentPath"
[target]="documentList"> [target]="documentList">
</alfresco-document-list-breadcrumb> </alfresco-document-list-breadcrumb>
<div *ngIf="errorMessage" class="error-message">
<button (click)="resetError()" class="mdl-button mdl-js-button mdl-button--icon">
<i class="material-icons">highlight_off</i>
</button>
<span class="error-message--text">{{errorMessage}}</span>
</div>
<alfresco-document-list <alfresco-document-list
#documentList #documentList
[currentFolderPath]="currentPath" [currentFolderPath]="currentPath"
[contextMenuActions]="true" [contextMenuActions]="true"
[contentActions]="true" [contentActions]="true"
(error)="onNavigationError($event)"
(preview)="showFile($event)" (preview)="showFile($event)"
(folderChange)="onFolderChanged($event)"> (folderChange)="onFolderChanged($event)">
<!-- <!--
@@ -117,6 +124,9 @@
<li> <li>
<button (click)="documentList.currentFolderPath = '/'">Go to root</button> <button (click)="documentList.currentFolderPath = '/'">Go to root</button>
</li> </li>
<li>
<button (click)="documentList.currentFolderPath = '!@£$%^&*()'">Go to the wrong path</button>
</li>
<li> <li>
<button (click)="fileDialog.toggleShowDialog()">Show/Hide File Dialog</button> <button (click)="fileDialog.toggleShowDialog()">Show/Hide File Dialog</button>
</li> </li>

View File

@@ -38,6 +38,7 @@ declare let __moduleName: string;
export class FilesComponent implements OnInit { export class FilesComponent implements OnInit {
currentPath: string = '/Sites/swsdp/documentLibrary'; currentPath: string = '/Sites/swsdp/documentLibrary';
errorMessage: string = null;
fileNodeId: any; fileNodeId: any;
fileShowed: boolean = false; fileShowed: boolean = false;
multipleFileUpload: boolean = false; multipleFileUpload: boolean = false;
@@ -120,6 +121,16 @@ export class FilesComponent implements OnInit {
this.router.navigate(['/activiti/tasksnode', event.value.entry.id]); this.router.navigate(['/activiti/tasksnode', event.value.entry.id]);
} }
onNavigationError(err: any) {
if (err) {
this.errorMessage = err.message || 'Navigation error';
}
}
resetError() {
this.errorMessage = null;
}
private setupBpmActions(actions: any[]) { private setupBpmActions(actions: any[]) {
actions.map(def => { actions.map(def => {
let documentAction = new DocumentActionModel(); let documentAction = new DocumentActionModel();

View File

@@ -85,15 +85,18 @@ describe('DocumentListBreadcrumb', () => {
component.onRoutePathClick(node, null); component.onRoutePathClick(node, null);
}); });
it('should update document list on click', () => { it('should update document list on click', (done) => {
let documentList = new DocumentList(null, null, null); let documentList = new DocumentList(null, null, null);
spyOn(documentList, 'displayFolderContent').and.stub(); spyOn(documentList, 'displayFolderContent').and.returnValue(Promise.resolve());
let node = <PathNode> { name: 'name', path: '/path' }; let node = <PathNode> { name: 'name', path: '/path' };
component.target = documentList; component.target = documentList;
component.onRoutePathClick(node, null); component.onRoutePathClick(node, null);
setTimeout(() => {
expect(documentList.currentFolderPath).toBe(node.path); expect(documentList.currentFolderPath).toBe(node.path);
done();
}, 0);
}); });
it('should do nothing for same path', () => { it('should do nothing for same path', () => {

View File

@@ -15,13 +15,14 @@
* limitations under the License. * limitations under the License.
*/ */
import { NgZone } from '@angular/core'; import { NgZone, TemplateRef } from '@angular/core';
import { DataColumn } from 'ng2-alfresco-datatable'; import { DataTableComponent, DataColumn, DataRowEvent } from 'ng2-alfresco-datatable';
import { DocumentList } from './document-list'; import { DocumentList } from './document-list';
import { DocumentListServiceMock } from './../assets/document-list.service.mock'; import { DocumentListServiceMock } from './../assets/document-list.service.mock';
import { ContentActionModel } from '../models/content-action.model'; import { ContentActionModel } from '../models/content-action.model';
import { FileNode, FolderNode } from '../assets/document-library.model.mock'; import { FileNode, FolderNode } from '../assets/document-library.model.mock';
import { NodeMinimalEntry } from '../models/document-library.model'; import { NodeMinimalEntry } from '../models/document-library.model';
import { ShareDataRow, RowFilter, ImageResolver } from './../data/share-datatable-adapter';
describe('DocumentList', () => { describe('DocumentList', () => {
@@ -204,23 +205,14 @@ describe('DocumentList', () => {
}); });
it('should display folder content for new folder path', () => { it('should display folder content for new folder path', () => {
spyOn(documentList, 'displayFolderContent').and.stub(); spyOn(documentList, 'displayFolderContent').and.returnValue(Promise.resolve());
let newPath = '/some/new/path'; let newPath = '/some/new/path';
documentList.currentFolderPath = newPath; documentList.currentFolderPath = newPath;
expect(documentList.displayFolderContent).toHaveBeenCalledWith(newPath); expect(documentList.displayFolderContent).toHaveBeenCalledWith(newPath);
}); });
it('should not display folder content for same path', () => {
spyOn(documentList, 'displayFolderContent').and.stub();
documentList.currentFolderPath = '/test';
expect(documentList.displayFolderContent).toHaveBeenCalledTimes(1);
documentList.currentFolderPath = '/test';
expect(documentList.displayFolderContent).toHaveBeenCalledTimes(1);
});
it('should reset to default path', () => { it('should reset to default path', () => {
spyOn(documentList, 'displayFolderContent').and.stub(); spyOn(documentList, 'displayFolderContent').and.returnValue(Promise.resolve());
documentList.currentFolderPath = null; documentList.currentFolderPath = null;
expect(documentList.currentFolderPath).toBe(documentList.DEFAULT_ROOT_FOLDER); expect(documentList.currentFolderPath).toBe(documentList.DEFAULT_ROOT_FOLDER);
@@ -228,7 +220,7 @@ describe('DocumentList', () => {
}); });
it('should emit folder changed event', (done) => { it('should emit folder changed event', (done) => {
spyOn(documentList, 'displayFolderContent').and.stub(); spyOn(documentList, 'displayFolderContent').and.returnValue(Promise.resolve());
documentList.folderChange.subscribe(e => { documentList.folderChange.subscribe(e => {
done(); done();
}); });
@@ -237,7 +229,7 @@ describe('DocumentList', () => {
}); });
it('should emit folder changed event with folder details', (done) => { it('should emit folder changed event with folder details', (done) => {
spyOn(documentList, 'displayFolderContent').and.stub(); spyOn(documentList, 'displayFolderContent').and.returnValue(Promise.resolve());
let path = '/path'; let path = '/path';
@@ -249,20 +241,6 @@ describe('DocumentList', () => {
documentList.currentFolderPath = path; documentList.currentFolderPath = path;
}); });
it('should emit folder changed event only once', () => {
spyOn(documentList, 'displayFolderContent').and.stub();
let path = '/new/path';
let calls = 0;
documentList.folderChange.subscribe(e => {
calls++;
});
documentList.currentFolderPath = path;
documentList.currentFolderPath = path;
documentList.currentFolderPath = path;
expect(calls).toBe(1);
});
it('should execute context action on callback', () => { it('should execute context action on callback', () => {
let action = { let action = {
node: {}, node: {},
@@ -500,4 +478,87 @@ describe('DocumentList', () => {
expect(documentList.isMobile).toHaveBeenCalled(); expect(documentList.isMobile).toHaveBeenCalled();
expect(documentList.navigationMode).toBe(DocumentList.SINGLE_CLICK_NAVIGATION); expect(documentList.navigationMode).toBe(DocumentList.SINGLE_CLICK_NAVIGATION);
}); });
it('should emit error on wrong path', (done) => {
let raised = false;
documentList.error.subscribe(err => raised = true);
spyOn(documentList, 'displayFolderContent').and.returnValue(Promise.reject(false));
documentList.currentFolderPath = 'wrong-path';
setTimeout(() => {
expect(raised).toBeTruthy();
done();
}, 0);
});
it('should require dataTable to check empty template', () => {
documentList.dataTable = null;
expect(documentList.isEmptyTemplateDefined()).toBeFalsy();
});
it('should check [empty folder] template ', () => {
documentList.emptyFolderTemplate = <TemplateRef<any>> {};
documentList.dataTable = new DataTableComponent();
expect(documentList.dataTable).toBeDefined();
expect(documentList.isEmptyTemplateDefined()).toBeTruthy();
documentList.emptyFolderTemplate = null;
expect(documentList.isEmptyTemplateDefined()).toBeFalsy();
});
it('should set root path for underlying adapter', () => {
documentList.rootPath = 'test';
expect(documentList.data.rootPath).toBe('test');
});
it('should set default root path for underlying adapter', () => {
documentList.rootPath = null;
expect(documentList.data.rootPath).toBe(documentList.data.DEFAULT_ROOT_PATH);
});
it('should fetch root path from underlying adapter', () => {
documentList.data.rootPath = 'test';
expect(documentList.rootPath).toBe('test');
});
it('should not fetch root path when adapter missing', () => {
documentList.data = null;
expect(documentList.rootPath).toBeNull();
});
it('should set row filter for underlying adapter', () => {
let filter = <RowFilter> {};
spyOn(documentList.data, 'setFilter').and.callThrough();
documentList.rowFilter = filter;
expect(documentList.data.setFilter).toHaveBeenCalledWith(filter);
});
it('should set image resolver for underlying adapter', () => {
let resolver = <ImageResolver> {};
spyOn(documentList.data, 'setImageResolver').and.callThrough();
documentList.imageResolver = resolver;
expect(documentList.data.setImageResolver).toHaveBeenCalledWith(resolver);
});
it('should emit [nodeClick] event on row click', () => {
let node = new NodeMinimalEntry();
let row = new ShareDataRow(node);
let event = <DataRowEvent> { value: row };
spyOn(documentList, 'onNodeClick').and.callThrough();
documentList.onRowClick(event);
expect(documentList.onNodeClick).toHaveBeenCalledWith(node);
});
it('should emit [nodeDblClick] event on row double-click', () => {
let node = new NodeMinimalEntry();
let row = new ShareDataRow(node);
let event = <DataRowEvent> { value: row };
spyOn(documentList, 'onNodeDblClick').and.callThrough();
documentList.onRowDblClick(event);
expect(documentList.onNodeDblClick).toHaveBeenCalledWith(node);
});
}); });

View File

@@ -118,6 +118,9 @@ export class DocumentList implements OnInit, AfterContentInit {
@Output() @Output()
preview: EventEmitter<any> = new EventEmitter(); preview: EventEmitter<any> = new EventEmitter();
@Output()
error: EventEmitter<any> = new EventEmitter();
@ViewChild(DataTableComponent) @ViewChild(DataTableComponent)
dataTable: DataTableComponent; dataTable: DataTableComponent;
@@ -130,10 +133,14 @@ export class DocumentList implements OnInit, AfterContentInit {
@Input() @Input()
set currentFolderPath(value: string) { set currentFolderPath(value: string) {
if (value !== this._path) { if (value !== this._path) {
this._path = value || this.DEFAULT_ROOT_FOLDER; const path = value || this.DEFAULT_ROOT_FOLDER;
this.displayFolderContent(this._path); this.displayFolderContent(path)
this.folderChange.emit({ .then(() => {
path: this.currentFolderPath this._path = path;
this.folderChange.emit({ path: path });
})
.catch(err => {
this.error.emit(err);
}); });
} }
} }
@@ -260,14 +267,17 @@ export class DocumentList implements OnInit, AfterContentInit {
} }
} }
displayFolderContent(path: string) { displayFolderContent(path: string): Promise<any> {
this.data.loadPath(path); return this.data.loadPath(path);
} }
reload() { reload() {
this.ngZone.run(() => { this.ngZone.run(() => {
if (this.currentFolderPath) { if (this.currentFolderPath) {
this.displayFolderContent(this.currentFolderPath); this.displayFolderContent(this.currentFolderPath)
.catch(err => {
this.error.emit(err);
});
} }
}); });
} }

View File

@@ -322,7 +322,7 @@ describe('ShareDataTableAdapter', () => {
expect(value).toBeNull(); expect(value).toBeNull();
}); });
it('should log load error', () => { it('should log load error', (done) => {
let error = 'My Error'; let error = 'My Error';
documentListService.getFolderReject = true; documentListService.getFolderReject = true;
documentListService.getFolderRejectError = error; documentListService.getFolderRejectError = error;
@@ -331,10 +331,10 @@ describe('ShareDataTableAdapter', () => {
spyOn(documentListService, 'getFolder').and.callThrough(); spyOn(documentListService, 'getFolder').and.callThrough();
let adapter = new ShareDataTableAdapter(documentListService, null, null); let adapter = new ShareDataTableAdapter(documentListService, null, null);
adapter.loadPath('/some/path'); adapter.loadPath('/some/path').catch(err => {
expect(err).toBe(error);
expect(documentListService.getFolder).toHaveBeenCalled(); done();
expect(console.error).toHaveBeenCalledWith(error); });
}); });
it('should generate file icon path based on mime type', () => { it('should generate file icon path based on mime type', () => {

View File

@@ -203,9 +203,9 @@ export class ShareDataTableAdapter implements DataTableAdapter, PaginationProvid
this.setSorting(sorting); this.setSorting(sorting);
} }
loadPath(path: string) { loadPath(path: string): Promise<any> {
return new Promise((resolve, reject) => {
if (path && this.documentListService) { if (path && this.documentListService) {
this.currentPath = path;
this.documentListService this.documentListService
.getFolder(path, { .getFolder(path, {
maxItems: this._maxItems, maxItems: this._maxItems,
@@ -213,11 +213,19 @@ export class ShareDataTableAdapter implements DataTableAdapter, PaginationProvid
rootPath: this.rootPath rootPath: this.rootPath
}) })
.subscribe(val => { .subscribe(val => {
this.currentPath = path;
this.loadPage(<NodePaging>val); this.loadPage(<NodePaging>val);
this.dataLoaded.emit(null); this.dataLoaded.emit(null);
resolve(true);
}, },
error => console.error(error)); error => {
reject(error);
});
} else {
resolve(false);
} }
});
} }
setFilter(filter: RowFilter) { setFilter(filter: RowFilter) {