diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-attachment-list.component.css b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-attachment-list.component.css new file mode 100644 index 0000000000..170e76fd6c --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-attachment-list.component.css @@ -0,0 +1,17 @@ +alfresco-datatable >>> .column-header { + color: #232323; + font-size: 15px; +} + +alfresco-datatable >>> .data-cell { + cursor: pointer !important; +} + +.no-attachment-message { + border: 1px solid rgb(224, 224, 224); + background: #fff; + text-align: left; + border-top: none; + padding: 10px; + text-align: center; +} diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-attachment-list.component.html b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-attachment-list.component.html new file mode 100644 index 0000000000..3613ae89f9 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-attachment-list.component.html @@ -0,0 +1,15 @@ +
+ No Attachments Found +
+ + + + + + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-attachment-list.component.spec.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-attachment-list.component.spec.ts new file mode 100644 index 0000000000..cc0c99e4bb --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-attachment-list.component.spec.ts @@ -0,0 +1,210 @@ +/*! + * @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 { SimpleChange } from '@angular/core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { Observable } from 'rxjs/Rx'; + +import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core'; +import { ActivitiContentService } from 'ng2-activiti-form'; +import { DataTableModule, ObjectDataRow, DataRowEvent, ObjectDataTableAdapter, DataSorting } from 'ng2-alfresco-datatable'; + +import { ActivitiProcessAttachmentListComponent } from './activiti-process-attachment-list.component'; +import { TranslationMock } from './../assets/translation.service.mock'; + +describe('Activiti Process Instance Attachment List', () => { + + let componentHandler: any; + let service: ActivitiContentService; + let component: ActivitiProcessAttachmentListComponent; + let fixture: ComponentFixture; + let getProcessRelatedContentSpy: jasmine.Spy; + let deleteContentSpy: jasmine.Spy; + let getFileRawContentSpy: jasmine.Spy; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule.forRoot(), + DataTableModule.forRoot() + ], + declarations: [ + ActivitiProcessAttachmentListComponent + ], + providers: [ + { provide: AlfrescoTranslationService, useClass: TranslationMock }, + ActivitiContentService + ] + }).compileComponents(); + })); + + beforeEach(() => { + + fixture = TestBed.createComponent(ActivitiProcessAttachmentListComponent); + component = fixture.componentInstance; + service = fixture.debugElement.injector.get(ActivitiContentService); + + getProcessRelatedContentSpy = spyOn(service, 'getProcessRelatedContent').and.returnValue(Observable.of( + { + 'size': 2, + 'total': 2, + 'start': 0, + 'data': [{ + 'id': 4001, + 'name': 'Invoice01.pdf', + 'created': '2017-05-12T12:50:05.522+0000', + 'createdBy': { + 'id': 1, + 'firstName': 'Apps', + 'lastName': 'Administrator', + 'email': 'admin@app.activiti.com', + 'company': 'Alfresco.com', + 'pictureId': 3003 + }, + 'relatedContent': true, + 'contentAvailable': true, + 'link': false, + 'mimeType': 'application/pdf', + 'simpleType': 'pdf', + 'previewStatus': 'created', + 'thumbnailStatus': 'created' + }, + { + 'id': 4002, + 'name': 'Invoice02.pdf', + 'created': '2017-05-12T12:50:05.522+0000', + 'createdBy': { + 'id': 1, + 'firstName': 'Apps', + 'lastName': 'Administrator', + 'email': 'admin@app.activiti.com', + 'company': 'Alfresco.com', + 'pictureId': 3003 + }, + 'relatedContent': true, + 'contentAvailable': true, + 'link': false, + 'mimeType': 'application/pdf', + 'simpleType': 'pdf', + 'previewStatus': 'created', + 'thumbnailStatus': 'created' + }] + })); + + deleteContentSpy = spyOn(service, 'deleteRelatedContent').and.returnValue(Observable.of({successCode: true})); + + let blobObj = new Blob(); + getFileRawContentSpy = spyOn(service, 'getFileRawContent').and.returnValue(Observable.of( + blobObj + )); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered', + 'upgradeElement' + ]); + window['componentHandler'] = componentHandler; + }); + + it('should load attachments when processInstanceId specified', () => { + let change = new SimpleChange(null, '123', true); + component.ngOnChanges({ 'processInstanceId': change }); + expect(getProcessRelatedContentSpy).toHaveBeenCalled(); + }); + + it('should emit an error when an error occurs loading attachments', () => { + let emitSpy = spyOn(component.error, 'emit'); + getProcessRelatedContentSpy.and.returnValue(Observable.throw({})); + let change = new SimpleChange(null, '123', true); + component.ngOnChanges({ 'processInstanceId': change }); + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should not attach when no processInstanceId is specified', () => { + fixture.detectChanges(); + expect(getProcessRelatedContentSpy).not.toHaveBeenCalled(); + }); + + it('should display attachments when the process has attachments', async(() => { + let change = new SimpleChange(null, '123', true); + component.ngOnChanges({ 'processInstanceId': change }); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.queryAll(By.css('alfresco-datatable tbody tr')).length).toBe(2); + }); + })); + + it('should not display attachments when the process has no attachments', async(() => { + component.processInstanceId = '123'; + getProcessRelatedContentSpy.and.returnValue(Observable.of({ + 'size': 0, + 'total': 0, + 'start': 0, + 'data': [] + })); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.queryAll(By.css('alfresco-datatable tbody tr')).length).toBe(0); + }); + })); + + describe('change detection', () => { + + let change = new SimpleChange('123', '456', true); + let nullChange = new SimpleChange('123', null, true); + + beforeEach(async(() => { + component.processInstanceId = '123'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + getProcessRelatedContentSpy.calls.reset(); + }); + })); + + it('should fetch new attachments when processInstanceId changed', () => { + component.ngOnChanges({ 'processInstanceId': change }); + expect(getProcessRelatedContentSpy).toHaveBeenCalledWith('456'); + }); + + it('should NOT fetch new attachments when empty changeset made', () => { + component.ngOnChanges({}); + expect(getProcessRelatedContentSpy).not.toHaveBeenCalled(); + }); + + it('should NOT fetch new attachments when processInstanceId changed to null', () => { + component.ngOnChanges({ 'processInstanceId': nullChange }); + expect(getProcessRelatedContentSpy).not.toHaveBeenCalled(); + }); + }); + + describe('Delete attachments', () => { + + beforeEach(async(() => { + component.processInstanceId = '123'; + fixture.detectChanges(); + fixture.whenStable(); + })); + + it('should display a dialog to the user when the Add button clicked', () => { + expect(true).toBe(true); + }); + + }); + +}); diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-attachment-list.component.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-attachment-list.component.ts new file mode 100644 index 0000000000..16139580e1 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-attachment-list.component.ts @@ -0,0 +1,163 @@ +/*! + * @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, OnChanges, Input, SimpleChanges, Output, EventEmitter } from '@angular/core'; +import { AlfrescoTranslationService, ContentService } from 'ng2-alfresco-core'; +import { ActivitiContentService } from 'ng2-activiti-form'; + +@Component({ + selector: 'activiti-process-attachment-list', + styleUrls: ['./activiti-process-attachment-list.component.css'], + templateUrl: './activiti-process-attachment-list.component.html' +}) +export class ActivitiProcessAttachmentListComponent implements OnChanges { + + @Input() + processInstanceId: string; + + @Output() + attachmentClick = new EventEmitter(); + + @Output() + error: EventEmitter = new EventEmitter(); + + attachments: any[] = []; + + constructor(private translateService: AlfrescoTranslationService, + private activitiContentService: ActivitiContentService, + private contentService: ContentService) { + + if (translateService) { + translateService.addTranslationFolder('ng2-activiti-processlist', 'node_modules/ng2-activiti-processlist/src'); + } + } + + ngOnChanges(changes: SimpleChanges) { + if (changes['processInstanceId'] && changes['processInstanceId'].currentValue) { + this.processInstanceId = changes['processInstanceId'].currentValue; + this.loadAttachmentsByProcessInstanceId(this.processInstanceId); + } + } + + reset () { + this.attachments = []; + } + + private loadAttachmentsByProcessInstanceId(processInstanceId: string) { + if (processInstanceId) { + this.reset(); + this.activitiContentService.getProcessRelatedContent(processInstanceId).subscribe( + (res: any) => { + res.data.forEach(content => { + this.attachments.push({ + id: content.id, + name: content.name, + created: content.created, + createdBy: content.createdBy.firstName + ' ' + content.createdBy.lastName, + icon: this.activitiContentService.getMimeTypeIcon(content.mimeType) + }); + }); + + }, + (err) => { + this.error.emit(err); + }); + } + } + + private deleteAttachmentById(contentId: string) { + if (contentId) { + this.activitiContentService.deleteRelatedContent(contentId).subscribe( + (res: any) => { + this.attachments = this.attachments.filter(content => { + return content.id !== contentId; + }); + }, + (err) => { + this.error.emit(err); + }); + } + } + + isEmpty(): boolean { + return this.attachments && this.attachments.length === 0; + } + + onShowRowActionsMenu(event: any) { + let viewAction = { + title: 'View', + name: 'view' + }; + + let removeAction = { + title: 'Remove', + name: 'remove' + }; + + let downloadAction = { + title: 'Download', + name: 'download' + }; + + event.value.actions = [ + viewAction, + removeAction, + downloadAction + ]; + } + + onExecuteRowAction(event: any) { + let args = event.value; + let action = args.action; + if (action.name === 'view') { + this.emitDocumentContent(args.row.obj); + } else if (action.name === 'remove') { + this.deleteAttachmentById(args.row.obj.id); + } else if (action.name === 'download') { + this.downloadContent(args.row.obj); + } + } + + openContent(event: any): void { + let content = event.value.obj; + this.emitDocumentContent(content); + } + + emitDocumentContent(content: any) { + this.activitiContentService.getFileRawContent(content.id).subscribe( + (blob: Blob) => { + content.contentBlob = blob; + this.attachmentClick.emit(content); + }, + (err) => { + this.error.emit(err); + } + ); + } + + /** + * Invoke content download. + */ + downloadContent(content: any): void { + this.activitiContentService.getFileRawContent(content.id).subscribe( + (blob: Blob) => this.contentService.downloadBlob(blob, content.name), + (err) => { + this.error.emit(err); + } + ); + } +}