diff --git a/demo-shell/src/app/components/file-view/file-view.component.html b/demo-shell/src/app/components/file-view/file-view.component.html index 595347e39a..90875b7965 100644 --- a/demo-shell/src/app/components/file-view/file-view.component.html +++ b/demo-shell/src/app/components/file-view/file-view.component.html @@ -2,6 +2,10 @@ + + + + diff --git a/docs/core/README.md b/docs/core/README.md index 659f87393e..6e15f2a2c4 100644 --- a/docs/core/README.md +++ b/docs/core/README.md @@ -34,6 +34,8 @@ for more information about installing and using the source code. | [Toolbar component](toolbar.component.md) | Simple container for headers, titles, actions and breadcrumbs. | [Source](../../lib/core/toolbar/toolbar.component.ts) | | [User info component](user-info.component.md) | Shows user information. | [Source](../../lib/core/userinfo/components/user-info.component.ts) | | [Viewer component](viewer.component.md) | Displays content from an ACS repository. | [Source](../../lib/core/viewer/components/viewer.component.ts) | +| [Comment list component](comment-list.component.md) | Shows a list of comments. | [Source](../../lib/core/comments/comment-list.component.ts) | +| [Comments component](comments.component.md) | Displays comments from users involved in a specified task and allows an involved user to add a comment to the task. | [Source](../../lib/core/comments/comments.component.ts) | ## Directives diff --git a/docs/process-services/comment-list.component.md b/docs/core/comment-list.component.md similarity index 96% rename from docs/process-services/comment-list.component.md rename to docs/core/comment-list.component.md index 9887f7962a..3ce21f3f13 100644 --- a/docs/process-services/comment-list.component.md +++ b/docs/core/comment-list.component.md @@ -41,7 +41,7 @@ export class SomeComponent implements OnInit { }, } ]; - + onClickCommentRow(comment: CommentProcessModel) { console.log('Clicked row: ', comment); } @@ -50,10 +50,10 @@ export class SomeComponent implements OnInit { In the component template use the comment list component: ```html - - + ``` ### Properties diff --git a/docs/process-services/comments.component.md b/docs/core/comments.component.md similarity index 64% rename from docs/process-services/comments.component.md rename to docs/core/comments.component.md index d949e4b793..a51efe3c60 100644 --- a/docs/process-services/comments.component.md +++ b/docs/core/comments.component.md @@ -4,11 +4,11 @@ Status: Active --- # Comments Component -Displays comments from users involved in a specified task and allows an involved user to add a comment to the task. +Displays comments from users involved in a specified task or content and allows an involved user to add a comment to a task or a content. ![adf-comments](../docassets/images/adf-comments.png) -## Basic Usage +## Basic Usage Task ```html ``` +## Basic Usage Content + +```html + + +``` + ### Properties | Name | Type | Default value | Description | | ---- | ---- | ------------- | ----------- | | taskId | `string` | | The numeric ID of the task. | +| nodeId | `string` | | The ID of the node. | | readOnly | `boolean` | `false` | Are the comments read only? | ### Events diff --git a/docs/process-services/README.md b/docs/process-services/README.md index 795bbdfdd9..5425107145 100644 --- a/docs/process-services/README.md +++ b/docs/process-services/README.md @@ -16,8 +16,6 @@ for more information about installing and using the source code. | [Create task attachment component](create-task-attachment.component.md) | Displays Upload Component (Drag and Click) to upload the attachment to a specified task | [Source](../../lib/process-services/attachment/create-task-attachment.component.ts) | | [Process attachment list component](process-attachment-list.component.md) | Displays attached documents on a specified process instance | [Source](../../lib/process-services/attachment/process-attachment-list.component.ts) | | [Task attachment list component](task-attachment-list.component.md) | Displays attached documents on a specified task. | [Source](../../lib/process-services/attachment/task-attachment-list.component.ts) | -| [Comment list component](comment-list.component.md) | Shows a list of comments. | [Source](../../lib/process-services/comments/comment-list.component.ts) | -| [Comments component](comments.component.md) | Displays comments from users involved in a specified task and allows an involved user to add a comment to the task. | [Source](../../lib/process-services/comments/comments.component.ts) | | [Process comments component](process-comments.component.md) | Displays comments associated with a particular process instance and allows the user to add new comments. | [Source](../../lib/process-services/comments/process-comments.component.ts) | | [People component](people.component.md) | Displays users involved with a specified task | [Source](../../lib/process-services/people/components/people/people.component.ts) | | [People list component](people-list.component.md) | Shows a list of users (people). | [Source](../../lib/process-services/people/components/people-list/people-list.component.ts) | diff --git a/lib/content-services/version-manager/version-list.component.spec.ts b/lib/content-services/version-manager/version-list.component.spec.ts index 6da2ba617b..6a5565a21d 100644 --- a/lib/content-services/version-manager/version-list.component.spec.ts +++ b/lib/content-services/version-manager/version-list.component.spec.ts @@ -32,7 +32,7 @@ describe('VersionListComponent', () => { TestBed.configureTestingModule({ declarations: [ VersionListComponent - ] + ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }).compileComponents(); })); diff --git a/lib/process-services/comments/comment-list.component.html b/lib/core/comments/comment-list.component.html similarity index 77% rename from lib/process-services/comments/comment-list.component.html rename to lib/core/comments/comment-list.component.html index b009d31ff0..e4f3c1e85f 100644 --- a/lib/process-services/comments/comment-list.component.html +++ b/lib/core/comments/comment-list.component.html @@ -1,4 +1,4 @@ - +
- {{getUserShortName(comment.createdBy)}}
+ {{getUserShortName(comment.createdBy)}} +
-
diff --git a/lib/process-services/comments/comment-list.component.scss b/lib/core/comments/comment-list.component.scss similarity index 100% rename from lib/process-services/comments/comment-list.component.scss rename to lib/core/comments/comment-list.component.scss diff --git a/lib/process-services/comments/comment-list.component.spec.ts b/lib/core/comments/comment-list.component.spec.ts similarity index 54% rename from lib/process-services/comments/comment-list.component.spec.ts rename to lib/core/comments/comment-list.component.spec.ts index 6a8211e1b4..a5926f369a 100644 --- a/lib/process-services/comments/comment-list.component.spec.ts +++ b/lib/core/comments/comment-list.component.spec.ts @@ -15,11 +15,14 @@ * limitations under the License. */ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { CommentProcessModel, UserProcessModel } from '@alfresco/adf-core'; +import { CommentModel, UserProcessModel } from '../models'; import { CommentListComponent } from './comment-list.component'; import { By } from '@angular/platform-browser'; +import { EcmUserService } from '../userinfo/services/ecm-user.service'; +import { PeopleProcessService } from '../services/people-process.service'; const testUser: UserProcessModel = new UserProcessModel({ id: '1', @@ -28,45 +31,113 @@ const testUser: UserProcessModel = new UserProcessModel({ email: 'tu@domain.com' }); const testDate = new Date(); -const testComment: CommentProcessModel = new CommentProcessModel({ +const processCommentOne: CommentModel = new CommentModel({ id: 1, message: 'Test Comment', created: testDate.toDateString(), createdBy: testUser }); -const secondtestComment: CommentProcessModel = new CommentProcessModel({ +const processCommentTwo: CommentModel = new CommentModel({ id: 2, message: '2nd Test Comment', created: new Date().toDateString(), createdBy: testUser }); +const contentCommentUserPictureDefined: CommentModel = new CommentModel({ + id: 2, + message: '2nd Test Comment', + created: new Date().toDateString(), + createdBy: { + enabled: true, + firstName: 'some', + lastName: 'one', + email: 'some-one@somegroup.com', + emailNotificationsEnabled: true, + company: {}, + id: 'fake-email@dom.com', + avatarId: '001-001-001' + } +}); + +const processCommentUserPictureDefined: CommentModel = new CommentModel({ + id: 2, + message: '2nd Test Comment', + created: new Date().toDateString(), + createdBy: { + id: '1', + firstName: 'Test', + lastName: 'User', + email: 'tu@domain.com', + pictureId: '001-001-001' + } +}); + +const contentCommentUserNoPictureDefined: CommentModel = new CommentModel({ + id: 2, + message: '2nd Test Comment', + created: new Date().toDateString(), + createdBy: { + enabled: true, + firstName: 'some', + lastName: 'one', + email: 'some-one@somegroup.com', + emailNotificationsEnabled: true, + company: {}, + id: 'fake-email@dom.com' + } +}); + +const processCommentUserNoPictureDefined: CommentModel = new CommentModel({ + id: 2, + message: '2nd Test Comment', + created: new Date().toDateString(), + createdBy: { + id: '1', + firstName: 'Test', + lastName: 'User', + email: 'tu@domain.com' + } +}); + describe('CommentListComponent', () => { let commentList: CommentListComponent; let fixture: ComponentFixture; let element: HTMLElement; + let ecmUserService: EcmUserService; + let peopleProcessService: PeopleProcessService; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ CommentListComponent ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], providers: [ - DatePipe + DatePipe, + PeopleProcessService, + EcmUserService ] }).compileComponents().then(() => { fixture = TestBed.createComponent(CommentListComponent); + ecmUserService = TestBed.get(EcmUserService); + peopleProcessService = TestBed.get(PeopleProcessService); commentList = fixture.componentInstance; element = fixture.nativeElement; fixture.detectChanges(); }); })); + beforeEach(() => { + spyOn(ecmUserService, 'getUserProfileImage').and.returnValue('content-user-image'); + spyOn(peopleProcessService, 'getUserImage').and.returnValue('process-user-image'); + }); + it('should emit row click event', async(() => { - commentList.comments = [testComment]; + commentList.comments = [processCommentOne]; commentList.clickRow.subscribe(selectedComment => { expect(selectedComment.id).toEqual(1); @@ -84,9 +155,9 @@ describe('CommentListComponent', () => { })); it('should deselect the previous selected comment when a new one is clicked', async(() => { - testComment.isSelected = true; - commentList.selectedComment = testComment; - commentList.comments = [testComment, secondtestComment]; + processCommentOne.isSelected = true; + commentList.selectedComment = processCommentOne; + commentList.comments = [processCommentOne, processCommentTwo]; commentList.clickRow.subscribe(selectedComment => { fixture.detectChanges(); @@ -111,31 +182,31 @@ describe('CommentListComponent', () => { })); it('should show comment message when input is given', async(() => { - commentList.comments = [testComment]; + commentList.comments = [processCommentOne]; fixture.detectChanges(); fixture.whenStable().then(() => { let elements = fixture.nativeElement.querySelectorAll('#comment-message'); expect(elements.length).toBe(1); - expect(elements[0].innerText).toBe(testComment.message); + expect(elements[0].innerText).toBe(processCommentOne.message); expect(fixture.nativeElement.querySelector('#comment-message:empty')).toBeNull(); }); })); it('should show comment user when input is given', async(() => { - commentList.comments = [testComment]; + commentList.comments = [processCommentOne]; fixture.detectChanges(); fixture.whenStable().then(() => { let elements = fixture.nativeElement.querySelectorAll('#comment-user'); expect(elements.length).toBe(1); - expect(elements[0].innerText).toBe(testComment.createdBy.firstName + ' ' + testComment.createdBy.lastName); + expect(elements[0].innerText).toBe(processCommentOne.createdBy.firstName + ' ' + processCommentOne.createdBy.lastName); expect(fixture.nativeElement.querySelector('#comment-user:empty')).toBeNull(); }); })); it('should show comment date time when input is given', async(() => { - commentList.comments = [testComment]; + commentList.comments = [processCommentOne]; fixture.detectChanges(); fixture.whenStable().then(() => { @@ -147,7 +218,7 @@ describe('CommentListComponent', () => { })); it('comment date time should start with Today when comment date is today', async(() => { - commentList.comments = [testComment]; + commentList.comments = [processCommentOne]; fixture.detectChanges(); fixture.whenStable().then(() => { @@ -157,8 +228,8 @@ describe('CommentListComponent', () => { })); it('comment date time should start with Yesterday when comment date is yesterday', async(() => { - testComment.created = new Date((Date.now() - 24 * 3600 * 1000)); - commentList.comments = [testComment]; + processCommentOne.created = new Date((Date.now() - 24 * 3600 * 1000)); + commentList.comments = [processCommentOne]; fixture.detectChanges(); fixture.whenStable().then(() => { @@ -168,8 +239,8 @@ describe('CommentListComponent', () => { })); it('comment date time should not start with Today/Yesterday when comment date is before yesterday', async(() => { - testComment.created = new Date((Date.now() - 24 * 3600 * 1000 * 2)); - commentList.comments = [testComment]; + processCommentOne.created = new Date((Date.now() - 24 * 3600 * 1000 * 2)); + commentList.comments = [processCommentOne]; fixture.detectChanges(); fixture.whenStable().then(() => { @@ -180,14 +251,56 @@ describe('CommentListComponent', () => { })); it('should show user icon when input is given', async(() => { - commentList.comments = [testComment]; + commentList.comments = [processCommentOne]; fixture.detectChanges(); fixture.whenStable().then(() => { let elements = fixture.nativeElement.querySelectorAll('#comment-user-icon'); expect(elements.length).toBe(1); - expect(elements[0].innerText).toContain(commentList.getUserShortName(testComment.createdBy)); + expect(elements[0].innerText).toContain(commentList.getUserShortName(processCommentOne.createdBy)); expect(fixture.nativeElement.querySelector('#comment-user-icon:empty')).toBeNull(); }); })); + + it('should return content picture when is a content user with a picture', async(() => { + commentList.comments = [contentCommentUserPictureDefined]; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + let elements = fixture.nativeElement.querySelectorAll('.adf-people-img'); + expect(elements.length).toBe(1); + expect(fixture.nativeElement.getElementsByClassName('adf-people-img')[0].src).toContain('content-user-image'); + }); + })); + + it('should return process picture when is a process user with a picture', async(() => { + commentList.comments = [processCommentUserPictureDefined]; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + let elements = fixture.nativeElement.querySelectorAll('.adf-people-img'); + expect(elements.length).toBe(1); + expect(fixture.nativeElement.getElementsByClassName('adf-people-img')[0].src).toContain('process-user-image'); + }); + })); + + it('should return content short name when is a content user without a picture', async(() => { + commentList.comments = [contentCommentUserNoPictureDefined]; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + let elements = fixture.nativeElement.querySelectorAll('.adf-comment-user-icon'); + expect(elements.length).toBe(1); + }); + })); + + it('should return process short name when is a process user without a picture', async(() => { + commentList.comments = [processCommentUserNoPictureDefined]; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + let elements = fixture.nativeElement.querySelectorAll('.adf-comment-user-icon'); + expect(elements.length).toBe(1); + }); + })); }); diff --git a/lib/process-services/comments/comment-list.component.ts b/lib/core/comments/comment-list.component.ts similarity index 72% rename from lib/process-services/comments/comment-list.component.ts rename to lib/core/comments/comment-list.component.ts index 962812f2a3..12856ce87f 100644 --- a/lib/process-services/comments/comment-list.component.ts +++ b/lib/core/comments/comment-list.component.ts @@ -15,9 +15,11 @@ * limitations under the License. */ -import { CommentProcessModel, PeopleProcessService, UserProcessModel } from '@alfresco/adf-core'; -import { DatePipe } from '@angular/common'; import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; +import { CommentModel } from '../models/comment.model'; +import { EcmUserService } from '../userinfo/services/ecm-user.service'; +import { PeopleProcessService } from '../services/people-process.service'; +import { DatePipe } from '@angular/common'; @Component({ selector: 'adf-comment-list', @@ -30,18 +32,19 @@ export class CommentListComponent { /** The comments data used to populate the list. */ @Input() - comments: CommentProcessModel[]; + comments: CommentModel[]; /** Emitted when the user clicks on one of the comment rows. */ @Output() - clickRow: EventEmitter = new EventEmitter(); + clickRow: EventEmitter = new EventEmitter(); - selectedComment: CommentProcessModel; + selectedComment: CommentModel; - constructor(private datePipe: DatePipe, public peopleProcessService: PeopleProcessService) { + constructor(private datePipe: DatePipe, public peopleProcessService: PeopleProcessService, + public ecmUserService: EcmUserService) { } - selectComment(comment: CommentProcessModel): void { + selectComment(comment: CommentModel): void { if (this.selectedComment) { this.selectedComment.isSelected = false; } @@ -50,7 +53,7 @@ export class CommentListComponent { this.clickRow.emit(this.selectedComment); } - getUserShortName(user: UserProcessModel): string { + getUserShortName(user: any): string { let shortName = ''; if (user) { if (user.firstName) { @@ -63,6 +66,18 @@ export class CommentListComponent { return shortName; } + isPictureDefined(user: any): boolean { + return user.pictureId || user.avatarId; + } + + getUserImage(user: any): string { + if (this.isAContentUsers(user)) { + return this.ecmUserService.getUserProfileImage(user.avatarId); + } else { + return this.peopleProcessService.getUserImage(user); + } + } + transformDate(aDate: string): string { let formattedDate: string; let givenDate = Number.parseInt(this.datePipe.transform(aDate, 'yMMdd')); @@ -79,4 +94,8 @@ export class CommentListComponent { } return formattedDate; } + + private isAContentUsers(user: any): boolean { + return user.avatarId; + } } diff --git a/lib/process-services/comments/comments.component.html b/lib/core/comments/comments.component.html similarity index 100% rename from lib/process-services/comments/comments.component.html rename to lib/core/comments/comments.component.html diff --git a/lib/process-services/comments/comments.component.scss b/lib/core/comments/comments.component.scss similarity index 100% rename from lib/process-services/comments/comments.component.scss rename to lib/core/comments/comments.component.scss diff --git a/lib/core/comments/comments.component.spec.ts b/lib/core/comments/comments.component.spec.ts new file mode 100644 index 0000000000..99c56c8cd5 --- /dev/null +++ b/lib/core/comments/comments.component.spec.ts @@ -0,0 +1,344 @@ +/*! + * @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 { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { SimpleChange } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Observable } from 'rxjs/Observable'; +import { CommentProcessService } from '../services/comment-process.service'; +import { DatePipe } from '@angular/common'; +import { PeopleProcessService } from '../services/people-process.service'; +import { CommentListComponent } from './comment-list.component'; +import { CommentsComponent } from './comments.component'; +import { CommentContentService } from '../services/comment-content.service'; +import { EcmUserService } from '../userinfo/services/ecm-user.service'; + +describe('CommentsComponent', () => { + + let component: CommentsComponent; + let fixture: ComponentFixture; + let getProcessCommentsSpy: jasmine.Spy; + let addProcessCommentSpy: jasmine.Spy; + let addContentCommentSpy: jasmine.Spy; + let getContentCommentsSpy: jasmine.Spy; + let commentProcessService: CommentProcessService; + let commentContentService: CommentContentService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ], + declarations: [ + CommentsComponent, + CommentListComponent + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + providers: [ + DatePipe, + PeopleProcessService, + CommentProcessService, + CommentContentService, + EcmUserService + ] + }).compileComponents(); + + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommentsComponent); + component = fixture.componentInstance; + + commentProcessService = fixture.debugElement.injector.get(CommentProcessService); + commentContentService = fixture.debugElement.injector.get(CommentContentService); + + addContentCommentSpy = spyOn(commentContentService, 'addNodeComment').and.returnValue(Observable.of({ + id: 123, + message: 'Test Comment', + createdBy: {id: '999'} + })); + + getContentCommentsSpy = spyOn(commentContentService, 'getNodeComments').and.returnValue(Observable.of([ + {message: 'Test1', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}}, + {message: 'Test2', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}}, + {message: 'Test3', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}} + ])); + + getProcessCommentsSpy = spyOn(commentProcessService, 'getTaskComments').and.returnValue(Observable.of([ + {message: 'Test1', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}}, + {message: 'Test2', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}}, + {message: 'Test3', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}} + ])); + addProcessCommentSpy = spyOn(commentProcessService, 'addTaskComment').and.returnValue(Observable.of({ + id: 123, + message: 'Test Comment', + createdBy: {id: '999'} + })); + + }); + + it('should load comments when taskId specified', () => { + let change = new SimpleChange(null, '123', true); + component.ngOnChanges({'taskId': change}); + + expect(getProcessCommentsSpy).toHaveBeenCalled(); + }); + + it('should load comments when nodeId specified', () => { + let change = new SimpleChange(null, '123', true); + component.ngOnChanges({'nodeId': change}); + + expect(getContentCommentsSpy).toHaveBeenCalled(); + }); + + it('should emit an error when an error occurs loading comments', () => { + let emitSpy = spyOn(component.error, 'emit'); + getProcessCommentsSpy.and.returnValue(Observable.throw({})); + + let change = new SimpleChange(null, '123', true); + component.ngOnChanges({'taskId': change}); + + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should not load comments when no taskId is specified', () => { + fixture.detectChanges(); + expect(getProcessCommentsSpy).not.toHaveBeenCalled(); + }); + + it('should display comments when the task has comments', async(() => { + let change = new SimpleChange(null, '123', true); + component.ngOnChanges({'taskId': change}); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.nativeElement.querySelectorAll('#comment-message').length).toBe(3); + expect(fixture.nativeElement.querySelector('#comment-message:empty')).toBeNull(); + }); + })); + + it('should display comments count when the task has comments', async(() => { + let change = new SimpleChange(null, '123', true); + component.ngOnChanges({'taskId': change}); + fixture.whenStable().then(() => { + fixture.detectChanges(); + let element = fixture.nativeElement.querySelector('#comment-header'); + expect(element.innerText).toBe('ADF_TASK_LIST.DETAILS.COMMENTS.HEADER'); + }); + })); + + it('should not display comments when the task has no comments', async(() => { + component.taskId = '123'; + getProcessCommentsSpy.and.returnValue(Observable.of([])); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('#comment-container')).toBeNull(); + }); + })); + + it('should display comments input by default', async(() => { + let change = new SimpleChange(null, '123', true); + component.ngOnChanges({'taskId': change}); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('#comment-input')).not.toBeNull(); + }); + })); + + it('should not display comments input when the task is readonly', async(() => { + component.readOnly = true; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('#comment-input')).toBeNull(); + }); + })); + + describe('change detection taskId', () => { + + let change = new SimpleChange('123', '456', true); + let nullChange = new SimpleChange('123', null, true); + + beforeEach(async(() => { + component.taskId = '123'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + getProcessCommentsSpy.calls.reset(); + }); + })); + + it('should fetch new comments when taskId changed', () => { + component.ngOnChanges({'taskId': change}); + expect(getProcessCommentsSpy).toHaveBeenCalledWith('456'); + }); + + it('should not fetch new comments when empty changeset made', () => { + component.ngOnChanges({}); + expect(getProcessCommentsSpy).not.toHaveBeenCalled(); + }); + + it('should not fetch new comments when taskId changed to null', () => { + component.ngOnChanges({'taskId': nullChange}); + expect(getProcessCommentsSpy).not.toHaveBeenCalled(); + }); + }); + + describe('change detection node', () => { + + let change = new SimpleChange('123', '456', true); + let nullChange = new SimpleChange('123', null, true); + + beforeEach(async(() => { + component.nodeId = '123'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + getContentCommentsSpy.calls.reset(); + }); + })); + + it('should fetch new comments when nodeId changed', () => { + component.ngOnChanges({'nodeId': change}); + expect(getContentCommentsSpy).toHaveBeenCalledWith('456'); + }); + + it('should not fetch new comments when empty changeset made', () => { + component.ngOnChanges({}); + expect(getContentCommentsSpy).not.toHaveBeenCalled(); + }); + + it('should not fetch new comments when nodeId changed to null', () => { + component.ngOnChanges({'nodeId': nullChange}); + expect(getContentCommentsSpy).not.toHaveBeenCalled(); + }); + }); + + describe('Add comment task', () => { + + beforeEach(async(() => { + component.taskId = '123'; + fixture.detectChanges(); + fixture.whenStable(); + })); + + it('should call service to add a comment when enter key is pressed', async(() => { + let event = new KeyboardEvent('keyup', {'key': 'Enter'}); + let element = fixture.nativeElement.querySelector('#comment-input'); + component.message = 'Test Comment'; + element.dispatchEvent(event); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(addProcessCommentSpy).toHaveBeenCalled(); + let elements = fixture.nativeElement.querySelectorAll('#comment-message'); + expect(elements.length).toBe(1); + expect(elements[0].innerText).toBe('Test Comment'); + }); + })); + + it('should not call service to add a comment when comment is empty', async(() => { + let event = new KeyboardEvent('keyup', {'key': 'Enter'}); + let element = fixture.nativeElement.querySelector('#comment-input'); + component.message = ''; + element.dispatchEvent(event); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(addProcessCommentSpy).not.toHaveBeenCalled(); + }); + })); + + it('should clear comment when escape key is pressed', async(() => { + let event = new KeyboardEvent('keyup', {'key': 'Escape'}); + let element = fixture.nativeElement.querySelector('#comment-input'); + component.message = 'Test comment'; + element.dispatchEvent(event); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + element = fixture.nativeElement.querySelector('#comment-input'); + expect(element.value).toBe(''); + }); + })); + + it('should emit an error when an error occurs adding the comment', () => { + let emitSpy = spyOn(component.error, 'emit'); + addProcessCommentSpy.and.returnValue(Observable.throw({})); + component.message = 'Test comment'; + component.add(); + expect(emitSpy).toHaveBeenCalled(); + }); + + }); + + describe('Add comment node', () => { + + beforeEach(async(() => { + component.nodeId = '123'; + fixture.detectChanges(); + fixture.whenStable(); + })); + + it('should call service to add a comment when enter key is pressed', async(() => { + let event = new KeyboardEvent('keyup', {'key': 'Enter'}); + let element = fixture.nativeElement.querySelector('#comment-input'); + component.message = 'Test Comment'; + element.dispatchEvent(event); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(addContentCommentSpy).toHaveBeenCalled(); + let elements = fixture.nativeElement.querySelectorAll('#comment-message'); + expect(elements.length).toBe(1); + expect(elements[0].innerText).toBe('Test Comment'); + }); + })); + + it('should not call service to add a comment when comment is empty', async(() => { + let event = new KeyboardEvent('keyup', {'key': 'Enter'}); + let element = fixture.nativeElement.querySelector('#comment-input'); + component.message = ''; + element.dispatchEvent(event); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(addContentCommentSpy).not.toHaveBeenCalled(); + }); + })); + + it('should clear comment when escape key is pressed', async(() => { + let event = new KeyboardEvent('keyup', {'key': 'Escape'}); + let element = fixture.nativeElement.querySelector('#comment-input'); + component.message = 'Test comment'; + element.dispatchEvent(event); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + element = fixture.nativeElement.querySelector('#comment-input'); + expect(element.value).toBe(''); + }); + })); + + it('should emit an error when an error occurs adding the comment', () => { + let emitSpy = spyOn(component.error, 'emit'); + addContentCommentSpy.and.returnValue(Observable.throw({})); + component.message = 'Test comment'; + component.add(); + expect(emitSpy).toHaveBeenCalled(); + }); + + }); + +}); diff --git a/lib/core/comments/comments.component.ts b/lib/core/comments/comments.component.ts new file mode 100644 index 0000000000..b58fc62bde --- /dev/null +++ b/lib/core/comments/comments.component.ts @@ -0,0 +1,179 @@ +/*! + * @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 { CommentProcessService } from '../services/comment-process.service'; +import { CommentContentService } from '../services/comment-content.service'; +import { CommentModel } from '../models/comment.model'; +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { Observer } from 'rxjs/Observer'; + +@Component({ + selector: 'adf-comments', + templateUrl: './comments.component.html', + styleUrls: ['./comments.component.scss'] +}) +export class CommentsComponent implements OnChanges { + + /** The numeric ID of the task. */ + @Input() + taskId: string; + + /** The numeric ID of the node. */ + @Input() + nodeId: string; + + /** Are the comments read only? */ + @Input() + readOnly: boolean = false; + + /** Emitted when an error occurs while displaying/adding a comment. */ + @Output() + error: EventEmitter = new EventEmitter(); + + comments: CommentModel [] = []; + + private commentObserver: Observer; + comment$: Observable; + + message: string; + + beingAdded: boolean = false; + + constructor(private commentProcessService: CommentProcessService, private commentContentService: CommentContentService) { + this.comment$ = new Observable(observer => this.commentObserver = observer).share(); + this.comment$.subscribe((comment: CommentModel) => { + this.comments.push(comment); + }); + } + + ngOnChanges(changes: SimpleChanges) { + this.taskId = null; + this.nodeId = null; + + this.taskId = changes['taskId'] ? changes['taskId'].currentValue : null; + this.nodeId = changes['nodeId'] ? changes['nodeId'].currentValue : null; + + if (this.taskId || this.nodeId) { + this.getComments(); + } else { + this.resetComments(); + } + } + + private getComments(): void { + this.resetComments(); + if (this.isATask()) { + this.commentProcessService.getTaskComments(this.taskId).subscribe( + (res: CommentModel[]) => { + if (res && res instanceof Array) { + res = res.sort((comment1: CommentModel, comment2: CommentModel) => { + let date1 = new Date(comment1.created); + let date2 = new Date(comment2.created); + return date1 > date2 ? -1 : date1 < date2 ? 1 : 0; + }); + res.forEach((comment) => { + this.commentObserver.next(comment); + }); + } + + }, + (err) => { + this.error.emit(err); + } + ); + } + + if (this.isANode()) { + this.commentContentService.getNodeComments(this.nodeId).subscribe( + (res: CommentModel[]) => { + if (res && res instanceof Array) { + + res = res.sort((comment1: CommentModel, comment2: CommentModel) => { + const date1 = new Date(comment1.created); + const date2 = new Date(comment2.created); + return date1 > date2 ? -1 : date1 < date2 ? 1 : 0; + }); + res.forEach((comment) => { + this.commentObserver.next(comment); + }); + } + }, + (err) => { + this.error.emit(err); + } + ); + } + } + + private resetComments(): void { + this.comments = []; + } + + add(): void { + if (this.message && this.message.trim() && !this.beingAdded) { + this.beingAdded = true; + if (this.isATask()) { + this.commentProcessService.addTaskComment(this.taskId, this.message) + .subscribe( + (res: CommentModel) => { + this.comments.unshift(res); + this.message = ''; + this.beingAdded = false; + + }, + (err) => { + this.error.emit(err); + this.beingAdded = false; + } + ); + } + + if (this.isANode()) { + this.commentContentService.addNodeComment(this.nodeId, this.message) + .subscribe( + (res: CommentModel) => { + this.comments.unshift(res); + this.message = ''; + this.beingAdded = false; + + }, + (err) => { + this.error.emit(err); + this.beingAdded = false; + } + ); + } + } + } + + clear(): void { + this.message = ''; + } + + isReadOnly(): boolean { + return this.readOnly; + } + + isATask(): boolean { + return this.taskId ? true : false; + } + + isANode(): boolean { + return this.nodeId ? true : false; + } +} diff --git a/lib/process-services/comments/comments.module.ts b/lib/core/comments/comments.module.ts similarity index 84% rename from lib/process-services/comments/comments.module.ts rename to lib/core/comments/comments.module.ts index d4709e22ab..e22b708c54 100644 --- a/lib/process-services/comments/comments.module.ts +++ b/lib/core/comments/comments.module.ts @@ -20,9 +20,9 @@ import { NgModule } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { MaterialModule } from '../material.module'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { DataColumnModule, DataTableModule } from '@alfresco/adf-core'; +import { DataColumnModule } from '../data-column/data-column.module'; +import { DataTableModule } from '../datatable/datatable.module'; -import { ProcessCommentsComponent } from './process-comments.component'; import { CommentListComponent } from './comment-list.component'; import { CommentsComponent } from './comments.component'; @@ -37,14 +37,13 @@ import { CommentsComponent } from './comments.component'; TranslateModule ], declarations: [ - ProcessCommentsComponent, CommentListComponent, CommentsComponent ], exports: [ - ProcessCommentsComponent, CommentListComponent, CommentsComponent ] }) -export class CommentsModule {} +export class CommentsModule { +} diff --git a/lib/process-services/comments/index.ts b/lib/core/comments/index.ts similarity index 100% rename from lib/process-services/comments/index.ts rename to lib/core/comments/index.ts diff --git a/lib/process-services/comments/public-api.ts b/lib/core/comments/public-api.ts similarity index 93% rename from lib/process-services/comments/public-api.ts rename to lib/core/comments/public-api.ts index f0b182d4cf..0d90f17352 100644 --- a/lib/process-services/comments/public-api.ts +++ b/lib/core/comments/public-api.ts @@ -15,6 +15,5 @@ * limitations under the License. */ -export * from './process-comments.component'; export * from './comment-list.component'; export * from './comments.component'; diff --git a/lib/core/core.module.ts b/lib/core/core.module.ts index 9873f3d9bf..4a0d207422 100644 --- a/lib/core/core.module.ts +++ b/lib/core/core.module.ts @@ -39,6 +39,7 @@ import { ViewerModule } from './viewer/viewer.module'; import { FormModule } from './form/form.module'; import { SidenavLayoutModule } from './sidenav-layout/sidenav-layout.module'; import { SideBarActionModule } from './sidebar/sidebar-action.module'; +import { CommentsModule } from './comments/comments.module'; import { DirectiveModule } from './directives/directive.module'; import { PipeModule } from './pipes/pipe.module'; @@ -52,6 +53,7 @@ import { AuthenticationService } from './services/authentication.service'; import { CardItemTypeService } from './card-view/services/card-item-types.service'; import { CardViewUpdateService } from './card-view/services/card-view-update.service'; import { CommentProcessService } from './services/comment-process.service'; +import { CommentContentService } from './services/comment-content.service'; import { ContentService } from './services/content.service'; import { CookieService } from './services/cookie.service'; import { DeletedNodesApiService } from './services/deleted-nodes-api.service'; @@ -116,6 +118,7 @@ export function providers() { SitesService, DiscoveryApiService, CommentProcessService, + CommentContentService, SearchConfigurationService ]; } @@ -141,6 +144,7 @@ export function providers() { CardViewModule, CollapsableModule, FormModule, + CommentsModule, LoginModule, LanguageMenuModule, InfoDrawerModule, @@ -174,6 +178,7 @@ export function providers() { CardViewModule, CollapsableModule, FormModule, + CommentsModule, LoginModule, LanguageMenuModule, InfoDrawerModule, @@ -206,6 +211,7 @@ export class CoreModuleLazy { CardViewModule, CollapsableModule, FormModule, + CommentsModule, LoginModule, LanguageMenuModule, InfoDrawerModule, @@ -239,6 +245,7 @@ export class CoreModuleLazy { CardViewModule, CollapsableModule, FormModule, + CommentsModule, LoginModule, LanguageMenuModule, InfoDrawerModule, diff --git a/lib/core/index.ts b/lib/core/index.ts index 60c4be475a..c3e5c4208c 100644 --- a/lib/core/index.ts +++ b/lib/core/index.ts @@ -35,6 +35,7 @@ export * from './form/form.module'; export * from './sidenav-layout/sidenav-layout.module'; export * from './pipes/pipe.module'; export * from './directives/directive.module'; +export * from './comments/comments.module'; export * from './viewer'; export * from './userinfo'; @@ -53,6 +54,7 @@ export * from './card-view'; export * from './app-config'; export * from './form'; export * from './sidenav-layout'; +export * from './comments'; export * from './pipes'; export * from './services'; diff --git a/lib/core/mock/comment-content-service.mock.ts b/lib/core/mock/comment-content-service.mock.ts new file mode 100644 index 0000000000..05ed8381bf --- /dev/null +++ b/lib/core/mock/comment-content-service.mock.ts @@ -0,0 +1,90 @@ +/*! + * @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 let fakeUser1 = { + 'enabled': true, + 'firstName': 'firstName', + 'lastName': 'lastName', + 'email': 'fake-email@dom.com', + 'emailNotificationsEnabled': true, + 'company': {}, + 'id': 'fake-email@dom.com', + 'avatarId': '123-123-123' +}; + +export let fakeUser2 = { + 'enabled': true, + 'firstName': 'some', + 'lastName': 'one', + 'email': 'some-one@somegroup.com', + 'emailNotificationsEnabled': true, + 'company': {}, + 'id': 'fake-email@dom.com', + 'avatarId': '001-001-001' +}; + +export let fakeContentComments = { + list: { + 'pagination': { + 'count': 4, + 'hasMoreItems': false, + 'totalItems': 4, + 'skipCount': 0, + 'maxItems': 100 + }, + entries: [{ + 'entry': { + 'createdAt': '2018-03-27T10:55:45.725+0000', + 'createdBy': fakeUser1, + 'edited': false, + 'modifiedAt': '2018-03-27T10:55:45.725+0000', + 'canEdit': true, + 'modifiedBy': fakeUser1, + 'canDelete': true, + 'id': '35a0cea7-b6d0-4abc-9030-f4e461dd1ac7', + 'content': 'fake-message-1' + } + }, { + 'entry': { + 'createdAt': '2018-03-27T10:55:45.725+0000', + 'createdBy': fakeUser2, + 'edited': false, + 'modifiedAt': '2018-03-27T10:55:45.725+0000', + 'canEdit': true, + 'modifiedBy': fakeUser2, + 'canDelete': true, + 'id': '35a0cea7-b6d0-4abc-9030-f4e461dd1ac7', + 'content': 'fake-message-2' + } + } + ] + } +}; + +export let fakeContentComment = { + 'entry': { + 'createdAt': '2018-03-29T11:49:51.735+0000', + 'createdBy': fakeUser1, + 'edited': false, + 'modifiedAt': '2018-03-29T11:49:51.735+0000', + 'canEdit': true, + 'modifiedBy': fakeUser1, + 'canDelete': true, + 'id': '4d07cdc5-f00c-4391-b39d-a842b12478b2', + 'content': 'fake-comment-message' + } +}; diff --git a/lib/core/mock/comment-process-service.mock.ts b/lib/core/mock/comment-process-service.mock.ts index fa1596e804..bf2af61c71 100644 --- a/lib/core/mock/comment-process-service.mock.ts +++ b/lib/core/mock/comment-process-service.mock.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { CommentProcessModel } from '../models/comment-process.model'; +import { CommentModel } from '../models/comment.model'; import { UserProcessModel } from '../models/user-process.model'; export let fakeUser1 = { id: 1, email: 'fake-email@dom.com', firstName: 'firstName', lastName: 'lastName' }; @@ -34,7 +34,7 @@ export let fakeTasksComment = { ] }; -export let fakeProcessComment = new CommentProcessModel({id: 1, message: 'Test', created: new Date('2016-11-10T03:37:30.010+0000'), createdBy: new UserProcessModel({ +export let fakeProcessComment = new CommentModel({id: 1, message: 'Test', created: new Date('2016-11-10T03:37:30.010+0000'), createdBy: new UserProcessModel({ id: 13, firstName: 'Wilbur', lastName: 'Adams', diff --git a/lib/core/models/comment-process.model.ts b/lib/core/models/comment-process.model.ts index 627e77e891..b478654489 100644 --- a/lib/core/models/comment-process.model.ts +++ b/lib/core/models/comment-process.model.ts @@ -17,6 +17,11 @@ import { CommentRepresentation, LightUserRepresentation } from 'alfresco-js-api'; +/** + * @deprecated + * CommentProcessModel + * (this model is deprecated in 2.3.0 in favour of CommentModel and will be removed in future revisions) + */ export class CommentProcessModel implements CommentRepresentation { id: number; message: string; diff --git a/lib/core/models/comment.model.ts b/lib/core/models/comment.model.ts new file mode 100644 index 0000000000..75e0dc4a9a --- /dev/null +++ b/lib/core/models/comment.model.ts @@ -0,0 +1,34 @@ +/*! + * @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 CommentModel { + id: number; + message: string; + created: Date; + createdBy: any; + isSelected: boolean; + + constructor(obj?: any) { + if (obj) { + this.id = obj.id; + this.message = obj.message; + this.created = obj.created; + this.createdBy = obj.createdBy; + this.isSelected = obj.isSelected ? obj.isSelected : false; + } + } +} diff --git a/lib/core/models/public-api.ts b/lib/core/models/public-api.ts index e2e64f651e..dd59131a54 100644 --- a/lib/core/models/public-api.ts +++ b/lib/core/models/public-api.ts @@ -19,6 +19,7 @@ export * from './file.model'; export * from './permissions.enum'; export * from './product-version.model'; export * from './user-process.model'; -export * from './comment-process.model'; +export * from './comment.model'; export * from './ecm-company.model'; export * from './redirection.model'; +export * from './comment-process.model'; diff --git a/lib/core/services/comment-content.service.spec.ts b/lib/core/services/comment-content.service.spec.ts new file mode 100644 index 0000000000..995accd7a1 --- /dev/null +++ b/lib/core/services/comment-content.service.spec.ts @@ -0,0 +1,91 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TestBed } from '@angular/core/testing'; +import { CommentModel } from '../models/comment.model'; +import { fakeContentComment, fakeContentComments } from '../mock/comment-content-service.mock'; +import { CommentContentService } from './comment-content.service'; +import { LogService } from './log.service'; +import { StorageService } from './storage.service'; + +declare let jasmine: any; +describe('Comment Content Service', () => { + + let service: CommentContentService; + + beforeEach((() => { + TestBed.configureTestingModule({ + providers: [ + CommentContentService, + StorageService, + LogService + ] + }).compileComponents(); + })); + + beforeEach(() => { + service = TestBed.get(CommentContentService); + + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + describe('Node comments', () => { + + it('should add a comment node ', (done) => { + service.addNodeComment('999', 'fake-comment-message').subscribe( + (res: CommentModel) => { + expect(res).toBeDefined(); + expect(res.id).not.toEqual(null); + expect(res.message).toEqual('fake-comment-message'); + expect(res.created).not.toEqual(null); + expect(res.createdBy.email).toEqual('fake-email@dom.com'); + expect(res.createdBy.firstName).toEqual('firstName'); + expect(res.createdBy.lastName).toEqual('lastName'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeContentComment) + }); + }); + + it('should return the nodes comments ', (done) => { + service.getNodeComments('999').subscribe( + (res: CommentModel[]) => { + expect(res).toBeDefined(); + expect(res.length).toEqual(2); + expect(res[0].message).toEqual('fake-message-1'); + expect(res[1].message).toEqual('fake-message-2'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeContentComments) + }); + }); + }); +}); diff --git a/lib/core/services/comment-content.service.ts b/lib/core/services/comment-content.service.ts new file mode 100644 index 0000000000..3b042b417f --- /dev/null +++ b/lib/core/services/comment-content.service.ts @@ -0,0 +1,67 @@ +/*! + * @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 { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { CommentModel } from '../models/comment.model'; +import { AlfrescoApiService } from '../services/alfresco-api.service'; +import { LogService } from '../services/log.service'; +import 'rxjs/add/observable/fromPromise'; +import 'rxjs/add/operator/catch'; +import 'rxjs/add/observable/throw'; + +@Injectable() +export class CommentContentService { + + constructor(private apiService: AlfrescoApiService, + private logService: LogService) { + } + + addNodeComment(nodeId: string, message: string): Observable { + return Observable.fromPromise(this.apiService.getInstance().core.commentsApi.addComment(nodeId, {content: message})) + .map((response: any) => { + return new CommentModel({ + id: response.entry.id, + message: response.entry.content, + created: response.entry.createdAt, + createdBy: response.entry.createdBy + }); + }).catch(err => this.handleError(err)); + } + + getNodeComments(nodeId: string): Observable { + return Observable.fromPromise(this.apiService.getInstance().core.commentsApi.getComments(nodeId)) + .map((response: any) => { + const comments: CommentModel[] = []; + response.list.entries.forEach((comment: any) => { + comments.push(new CommentModel({ + id: comment.entry.id, + message: comment.entry.content, + created: comment.entry.createdAt, + createdBy: comment.entry.createdBy + })); + }); + return comments; + }).catch(err => this.handleError(err)); + } + + private handleError(error: any) { + this.logService.error(error); + return Observable.throw(error || 'Server error'); + } + +} diff --git a/lib/core/services/comment-process.service.spec.ts b/lib/core/services/comment-process.service.spec.ts index 267139fd00..045d6eac6b 100644 --- a/lib/core/services/comment-process.service.spec.ts +++ b/lib/core/services/comment-process.service.spec.ts @@ -16,7 +16,7 @@ */ import { async, TestBed } from '@angular/core/testing'; -import { CommentProcessModel } from '../models'; +import { CommentModel } from '../models/comment.model'; import { fakeProcessComment, fakeTasksComment, fakeUser1 } from '../mock/comment-process-service.mock'; import { CommentProcessService } from './comment-process.service'; import { AlfrescoApiService } from './alfresco-api.service'; @@ -142,7 +142,7 @@ describe('Comment ProcessService Service', () => { it('should add a comment task ', (done) => { service.addTaskComment('999', 'fake-comment-message').subscribe( - (res: CommentProcessModel) => { + (res: CommentModel) => { expect(res).toBeDefined(); expect(res.id).not.toEqual(null); expect(res.message).toEqual('fake-comment-message'); @@ -167,7 +167,7 @@ describe('Comment ProcessService Service', () => { it('should return the tasks comments ', (done) => { service.getTaskComments('999').subscribe( - (res: CommentProcessModel[]) => { + (res: CommentModel[]) => { expect(res).toBeDefined(); expect(res.length).toEqual(2); expect(res[0].message).toEqual('fake-message-1'); diff --git a/lib/core/services/comment-process.service.ts b/lib/core/services/comment-process.service.ts index 8b91c356ce..88118c05c4 100644 --- a/lib/core/services/comment-process.service.ts +++ b/lib/core/services/comment-process.service.ts @@ -17,7 +17,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; -import { CommentProcessModel } from '../models/comment-process.model'; +import { CommentModel } from '../models/comment.model'; import { UserProcessModel } from '../models/user-process.model'; import { AlfrescoApiService } from './alfresco-api.service'; import { LogService } from './log.service'; @@ -32,47 +32,57 @@ export class CommentProcessService { private logService: LogService) { } - addTaskComment(taskId: string, message: string): Observable { + addTaskComment(taskId: string, message: string): Observable { return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.addTaskComment({message: message}, taskId)) .map(res => res) - .map((response: CommentProcessModel) => { - return new CommentProcessModel({id: response.id, message: response.message, created: response.created, createdBy: response.createdBy}); + .map((response: CommentModel) => { + return new CommentModel({ + id: response.id, + message: response.message, + created: response.created, + createdBy: response.createdBy + }); }).catch(err => this.handleError(err)); } - getTaskComments(taskId: string): Observable { + getTaskComments(taskId: string): Observable { return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.getTaskComments(taskId)) .map(res => res) .map((response: any) => { - let comments: CommentProcessModel[] = []; - response.data.forEach((comment: CommentProcessModel) => { + let comments: CommentModel[] = []; + response.data.forEach((comment: CommentModel) => { let user = new UserProcessModel(comment.createdBy); - comments.push(new CommentProcessModel({id: comment.id, message: comment.message, created: comment.created, createdBy: user})); + comments.push(new CommentModel({id: comment.id, message: comment.message, created: comment.created, createdBy: user})); }); return comments; }).catch(err => this.handleError(err)); } - getProcessInstanceComments(processInstanceId: string): Observable { + getProcessInstanceComments(processInstanceId: string): Observable { return Observable.fromPromise(this.apiService.getInstance().activiti.commentsApi.getProcessInstanceComments(processInstanceId)) .map(res => res) .map((response: any) => { - let comments: CommentProcessModel[] = []; - response.data.forEach((comment: CommentProcessModel) => { + let comments: CommentModel[] = []; + response.data.forEach((comment: CommentModel) => { let user = new UserProcessModel(comment.createdBy); - comments.push(new CommentProcessModel({id: comment.id, message: comment.message, created: comment.created, createdBy: user})); + comments.push(new CommentModel({id: comment.id, message: comment.message, created: comment.created, createdBy: user})); }); return comments; }).catch(err => this.handleError(err)); } - addProcessInstanceComment(processInstanceId: string, message: string): Observable { + addProcessInstanceComment(processInstanceId: string, message: string): Observable { return Observable.fromPromise( - this.apiService.getInstance().activiti.commentsApi.addProcessInstanceComment({ message: message }, processInstanceId) + this.apiService.getInstance().activiti.commentsApi.addProcessInstanceComment({message: message}, processInstanceId) ) - .map((response: CommentProcessModel) => { - return new CommentProcessModel({id: response.id, message: response.message, created: response.created, createdBy: response.createdBy}); + .map((response: CommentModel) => { + return new CommentModel({ + id: response.id, + message: response.message, + created: response.created, + createdBy: response.createdBy + }); }).catch(err => this.handleError(err)); } diff --git a/lib/core/services/public-api.ts b/lib/core/services/public-api.ts index f9eea442a1..0042ffcdef 100644 --- a/lib/core/services/public-api.ts +++ b/lib/core/services/public-api.ts @@ -47,3 +47,4 @@ export * from './sites.service'; export * from './discovery-api.service'; export * from './comment-process.service'; export * from './search-configuration.service'; +export * from './comment-content.service'; diff --git a/lib/core/styles/_index.scss b/lib/core/styles/_index.scss index 2c1f230a8d..3bcd5123da 100644 --- a/lib/core/styles/_index.scss +++ b/lib/core/styles/_index.scss @@ -23,6 +23,8 @@ @import '../viewer/components/imgViewer.component'; @import '../form/components/form.component'; @import '../sidebar/sidebar-action-menu.component'; +@import '../comments/comment-list.component'; +@import '../comments/comments.component'; @mixin adf-core-theme($theme) { @include adf-colors-theme($theme); @@ -48,6 +50,8 @@ @include adf-text-viewer-theme($theme); @include adf-form-component-theme($theme); @include adf-sidebar-action-menu-theme($theme); + @include adf-task-list-comment-list-theme($theme); + @include adf-task-list-comment-theme($theme); } diff --git a/lib/process-services/comments/comments.component.spec.ts b/lib/process-services/comments/comments.component.spec.ts deleted file mode 100644 index 43a3fceeb6..0000000000 --- a/lib/process-services/comments/comments.component.spec.ts +++ /dev/null @@ -1,231 +0,0 @@ -/*! - * @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 { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { Observable } from 'rxjs/Observable'; - -import { FormModule } from '@alfresco/adf-core'; -import { CommentProcessService } from '@alfresco/adf-core'; - -import { DatePipe } from '@angular/common'; -import { MatInputModule } from '@angular/material'; -import { PeopleProcessService } from '@alfresco/adf-core'; -import { TaskListService } from '../task-list/services/tasklist.service'; -import { CommentListComponent } from './comment-list.component'; -import { CommentsComponent } from './comments.component'; - -describe('CommentsComponent', () => { - - let component: CommentsComponent; - let fixture: ComponentFixture; - let getCommentsSpy: jasmine.Spy; - let addCommentSpy: jasmine.Spy; - let commentProcessService: CommentProcessService; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - FormModule, - MatInputModule - ], - declarations: [ - CommentsComponent, - CommentListComponent - ], - providers: [ - TaskListService, - DatePipe, - PeopleProcessService, - CommentProcessService - ] - }).compileComponents(); - - })); - - beforeEach(() => { - fixture = TestBed.createComponent(CommentsComponent); - component = fixture.componentInstance; - commentProcessService = fixture.debugElement.injector.get(CommentProcessService); - - getCommentsSpy = spyOn(commentProcessService, 'getTaskComments').and.returnValue(Observable.of([ - { message: 'Test1', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} }, - { message: 'Test2', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} }, - { message: 'Test3', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} } - ])); - addCommentSpy = spyOn(commentProcessService, 'addTaskComment').and.returnValue(Observable.of({id: 123, message: 'Test Comment', createdBy: {id: '999'}})); - - }); - - it('should load comments when taskId specified', () => { - let change = new SimpleChange(null, '123', true); - component.ngOnChanges({ 'taskId': change }); - - expect(getCommentsSpy).toHaveBeenCalled(); - }); - - it('should emit an error when an error occurs loading comments', () => { - let emitSpy = spyOn(component.error, 'emit'); - getCommentsSpy.and.returnValue(Observable.throw({})); - - let change = new SimpleChange(null, '123', true); - component.ngOnChanges({ 'taskId': change }); - - expect(emitSpy).toHaveBeenCalled(); - }); - - it('should not load comments when no taskId is specified', () => { - fixture.detectChanges(); - expect(getCommentsSpy).not.toHaveBeenCalled(); - }); - - it('should display comments when the task has comments', async(() => { - let change = new SimpleChange(null, '123', true); - component.ngOnChanges({ 'taskId': change }); - - fixture.whenStable().then(() => { - fixture.detectChanges(); - expect(fixture.nativeElement.querySelectorAll('#comment-message').length).toBe(3); - expect(fixture.nativeElement.querySelector('#comment-message:empty')).toBeNull(); - }); - })); - - it('should display comments count when the task has comments', async(() => { - let change = new SimpleChange(null, '123', true); - component.ngOnChanges({ 'taskId': change }); - fixture.whenStable().then(() => { - fixture.detectChanges(); - let element = fixture.nativeElement.querySelector('#comment-header'); - expect(element.innerText).toBe('ADF_TASK_LIST.DETAILS.COMMENTS.HEADER'); - }); - })); - - it('should not display comments when the task has no comments', async(() => { - component.taskId = '123'; - getCommentsSpy.and.returnValue(Observable.of([])); - fixture.whenStable().then(() => { - fixture.detectChanges(); - expect(fixture.nativeElement.querySelector('#comment-container')).toBeNull(); - }); - })); - - it('should display comments input by default', async(() => { - let change = new SimpleChange(null, '123', true); - component.ngOnChanges({ 'taskId': change }); - fixture.whenStable().then(() => { - fixture.detectChanges(); - expect(fixture.nativeElement.querySelector('#comment-input')).not.toBeNull(); - }); - })); - - it('should not display comments input when the task is readonly', async(() => { - component.readOnly = true; - fixture.detectChanges(); - fixture.whenStable().then(() => { - fixture.detectChanges(); - expect(fixture.nativeElement.querySelector('#comment-input')).toBeNull(); - }); - })); - - describe('change detection', () => { - - let change = new SimpleChange('123', '456', true); - let nullChange = new SimpleChange('123', null, true); - - beforeEach(async(() => { - component.taskId = '123'; - fixture.detectChanges(); - fixture.whenStable().then(() => { - getCommentsSpy.calls.reset(); - }); - })); - - it('should fetch new comments when taskId changed', () => { - component.ngOnChanges({ 'taskId': change }); - expect(getCommentsSpy).toHaveBeenCalledWith('456'); - }); - - it('should not fetch new comments when empty changeset made', () => { - component.ngOnChanges({}); - expect(getCommentsSpy).not.toHaveBeenCalled(); - }); - - it('should not fetch new comments when taskId changed to null', () => { - component.ngOnChanges({ 'taskId': nullChange }); - expect(getCommentsSpy).not.toHaveBeenCalled(); - }); - }); - - describe('Add comment', () => { - - beforeEach(async(() => { - component.taskId = '123'; - fixture.detectChanges(); - fixture.whenStable(); - })); - - it('should call service to add a comment when enter key is pressed', async(() => { - let event = new KeyboardEvent('keyup', {'key': 'Enter'}); - let element = fixture.nativeElement.querySelector('#comment-input'); - component.message = 'Test Comment'; - element.dispatchEvent(event); - fixture.detectChanges(); - fixture.whenStable().then(() => { - fixture.detectChanges(); - expect(addCommentSpy).toHaveBeenCalled(); - let elements = fixture.nativeElement.querySelectorAll('#comment-message'); - expect(elements.length).toBe(1); - expect(elements[0].innerText).toBe('Test Comment'); - }); - })); - - it('should not call service to add a comment when comment is empty', async(() => { - let event = new KeyboardEvent('keyup', {'key': 'Enter'}); - let element = fixture.nativeElement.querySelector('#comment-input'); - component.message = ''; - element.dispatchEvent(event); - fixture.detectChanges(); - fixture.whenStable().then(() => { - fixture.detectChanges(); - expect(addCommentSpy).not.toHaveBeenCalled(); - }); - })); - - it('should clear comment when escape key is pressed', async(() => { - let event = new KeyboardEvent('keyup', {'key': 'Escape'}); - let element = fixture.nativeElement.querySelector('#comment-input'); - component.message = 'Test comment'; - element.dispatchEvent(event); - fixture.detectChanges(); - fixture.whenStable().then(() => { - fixture.detectChanges(); - element = fixture.nativeElement.querySelector('#comment-input'); - expect(element.value).toBe(''); - }); - })); - - it('should emit an error when an error occurs adding the comment', () => { - let emitSpy = spyOn(component.error, 'emit'); - addCommentSpy.and.returnValue(Observable.throw({})); - component.message = 'Test comment'; - component.add(); - expect(emitSpy).toHaveBeenCalled(); - }); - - }); - -}); diff --git a/lib/process-services/comments/comments.component.ts b/lib/process-services/comments/comments.component.ts deleted file mode 100644 index 866af1f233..0000000000 --- a/lib/process-services/comments/comments.component.ts +++ /dev/null @@ -1,121 +0,0 @@ -/*! - * @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 { CommentProcessModel, CommentProcessService } from '@alfresco/adf-core'; -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; -import { Observable } from 'rxjs/Observable'; -import { Observer } from 'rxjs/Observer'; - -@Component({ - selector: 'adf-comments', - templateUrl: './comments.component.html', - styleUrls: ['./comments.component.scss'] -}) -export class CommentsComponent implements OnChanges { - - /** The numeric ID of the task. */ - @Input() - taskId: string; - - /** Are the comments read only? */ - @Input() - readOnly: boolean = false; - - /** Emitted when an error occurs while displaying/adding a comment. */ - @Output() - error: EventEmitter = new EventEmitter(); - - comments: CommentProcessModel [] = []; - - private commentObserver: Observer; - comment$: Observable; - - message: string; - - beingAdded: boolean = false; - - constructor(private commentProcessService: CommentProcessService) { - this.comment$ = new Observable(observer => this.commentObserver = observer).share(); - this.comment$.subscribe((comment: CommentProcessModel) => { - this.comments.push(comment); - }); - } - - ngOnChanges(changes: SimpleChanges) { - let taskId = changes['taskId']; - if (taskId) { - if (taskId.currentValue) { - this.getTaskComments(taskId.currentValue); - } else { - this.resetComments(); - } - } - } - - private getTaskComments(taskId: string): void { - this.resetComments(); - if (taskId) { - this.commentProcessService.getTaskComments(taskId).subscribe( - (res: CommentProcessModel[]) => { - res = res.sort((comment1: CommentProcessModel, comment2: CommentProcessModel) => { - let date1 = new Date(comment1.created); - let date2 = new Date(comment2.created); - return date1 > date2 ? -1 : date1 < date2 ? 1 : 0; - }); - res.forEach((comment) => { - this.commentObserver.next(comment); - }); - }, - (err) => { - this.error.emit(err); - } - ); - } - } - - private resetComments(): void { - this.comments = []; - } - - add(): void { - if (this.message && this.message.trim() && !this.beingAdded) { - this.beingAdded = true; - this.commentProcessService.addTaskComment(this.taskId, this.message) - .subscribe( - (res: CommentProcessModel) => { - this.comments.unshift(res); - this.message = ''; - this.beingAdded = false; - - }, - (err) => { - this.error.emit(err); - this.beingAdded = false; - } - ); - } - } - - clear(): void { - this.message = ''; - } - - isReadOnly(): boolean { - return this.readOnly; - } - -} diff --git a/lib/process-services/index.ts b/lib/process-services/index.ts index 0e2cd46021..e459b12c77 100644 --- a/lib/process-services/index.ts +++ b/lib/process-services/index.ts @@ -21,7 +21,12 @@ export * from './process-list/process-list.module'; export * from './task-list/task-list.module'; export * from './app-list/apps-list.module'; export * from './attachment/attachment.module'; -export * from './comments/comments.module'; + +/** @deprecated in 2.3.0, part of the module moved in the core */ +export { CommentsModule } from '@alfresco/adf-core'; + +export * from './process-comments/process-comments.module'; + export * from './people/people.module'; export * from './content-widget/content-widget.module'; @@ -29,6 +34,12 @@ export * from './process-list'; export * from './task-list'; export * from './app-list'; export * from './attachment'; -export * from './comments'; + +/** @deprecated in 2.3.0, component moved in the core */ +export { CommentListComponent } from '@alfresco/adf-core'; +/** @deprecated in 2.3.0, component moved in the core */ +export { CommentsComponent } from '@alfresco/adf-core'; + +export * from './process-comments'; export * from './people'; export * from './content-widget'; diff --git a/lib/process-services/process-comments/index.ts b/lib/process-services/process-comments/index.ts new file mode 100644 index 0000000000..4c6ac1d58f --- /dev/null +++ b/lib/process-services/process-comments/index.ts @@ -0,0 +1,18 @@ +/*! + * @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 * from './public-api'; diff --git a/lib/process-services/comments/process-comments.component.html b/lib/process-services/process-comments/process-comments.component.html similarity index 100% rename from lib/process-services/comments/process-comments.component.html rename to lib/process-services/process-comments/process-comments.component.html diff --git a/lib/process-services/comments/process-comments.component.scss b/lib/process-services/process-comments/process-comments.component.scss similarity index 100% rename from lib/process-services/comments/process-comments.component.scss rename to lib/process-services/process-comments/process-comments.component.scss diff --git a/lib/process-services/comments/process-comments.component.spec.ts b/lib/process-services/process-comments/process-comments.component.spec.ts similarity index 96% rename from lib/process-services/comments/process-comments.component.spec.ts rename to lib/process-services/process-comments/process-comments.component.spec.ts index 6a43d17501..771f68fab9 100644 --- a/lib/process-services/comments/process-comments.component.spec.ts +++ b/lib/process-services/process-comments/process-comments.component.spec.ts @@ -21,7 +21,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { MatInputModule } from '@angular/material'; import { Observable } from 'rxjs/Observable'; -import { CommentListComponent, CommentsComponent } from '../index'; import { CommentProcessService, PeopleProcessService } from '@alfresco/adf-core'; import { ProcessService } from '../process-list/services/process.service'; @@ -40,9 +39,7 @@ describe('ActivitiProcessInstanceComments', () => { MatInputModule ], declarations: [ - ProcessCommentsComponent, - CommentsComponent, - CommentListComponent + ProcessCommentsComponent ], providers: [ ProcessService, diff --git a/lib/process-services/comments/process-comments.component.ts b/lib/process-services/process-comments/process-comments.component.ts similarity index 85% rename from lib/process-services/comments/process-comments.component.ts rename to lib/process-services/process-comments/process-comments.component.ts index 43eaa180b5..6dec5bf860 100644 --- a/lib/process-services/comments/process-comments.component.ts +++ b/lib/process-services/process-comments/process-comments.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { CommentProcessModel, CommentProcessService } from '@alfresco/adf-core'; +import { CommentModel, CommentProcessService } from '@alfresco/adf-core'; import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Observer } from 'rxjs/Observer'; @@ -39,18 +39,18 @@ export class ProcessCommentsComponent implements OnChanges { @Output() error: EventEmitter = new EventEmitter(); - comments: CommentProcessModel [] = []; + comments: CommentModel [] = []; - private commentObserver: Observer; - comment$: Observable; + private commentObserver: Observer; + comment$: Observable; message: string; beingAdded: boolean = false; constructor(private commentProcessService: CommentProcessService) { - this.comment$ = new Observable(observer => this.commentObserver = observer).share(); - this.comment$.subscribe((comment: CommentProcessModel) => { + this.comment$ = new Observable(observer => this.commentObserver = observer).share(); + this.comment$.subscribe((comment: CommentModel) => { this.comments.push(comment); }); } @@ -70,8 +70,8 @@ export class ProcessCommentsComponent implements OnChanges { this.resetComments(); if (processInstanceId) { this.commentProcessService.getProcessInstanceComments(processInstanceId).subscribe( - (res: CommentProcessModel[]) => { - res = res.sort((comment1: CommentProcessModel, comment2: CommentProcessModel) => { + (res: CommentModel[]) => { + res = res.sort((comment1: CommentModel, comment2: CommentModel) => { let date1 = new Date(comment1.created); let date2 = new Date(comment2.created); return date1 > date2 ? -1 : date1 < date2 ? 1 : 0; @@ -96,7 +96,7 @@ export class ProcessCommentsComponent implements OnChanges { this.beingAdded = true; this.commentProcessService.addProcessInstanceComment(this.processInstanceId, this.message) .subscribe( - (res: CommentProcessModel) => { + (res: CommentModel) => { this.comments.unshift(res); this.message = ''; this.beingAdded = false; diff --git a/lib/process-services/process-comments/process-comments.module.ts b/lib/process-services/process-comments/process-comments.module.ts new file mode 100644 index 0000000000..cfdec735f8 --- /dev/null +++ b/lib/process-services/process-comments/process-comments.module.ts @@ -0,0 +1,46 @@ +/*! + * @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 { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { MaterialModule } from '../material.module'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { DataColumnModule, DataTableModule, CommentsModule } from '@alfresco/adf-core'; + +import { ProcessCommentsComponent } from './process-comments.component'; + +@NgModule({ + imports: [ + DataColumnModule, + DataTableModule, + FormsModule, + ReactiveFormsModule, + MaterialModule, + CommonModule, + TranslateModule, + CommentsModule + ], + declarations: [ + ProcessCommentsComponent + ], + exports: [ + ProcessCommentsComponent + ] +}) +export class ProcessCommentsModule { +} diff --git a/lib/process-services/process-comments/public-api.ts b/lib/process-services/process-comments/public-api.ts new file mode 100644 index 0000000000..6b3195aa5c --- /dev/null +++ b/lib/process-services/process-comments/public-api.ts @@ -0,0 +1,18 @@ +/*! + * @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 * from './process-comments.component'; diff --git a/lib/process-services/process-list/process-list.module.ts b/lib/process-services/process-list/process-list.module.ts index 908851274e..1b6b26a7aa 100644 --- a/lib/process-services/process-list/process-list.module.ts +++ b/lib/process-services/process-list/process-list.module.ts @@ -20,13 +20,12 @@ import { NgModule } from '@angular/core'; import { FlexLayoutModule } from '@angular/flex-layout'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { FormModule } from '@alfresco/adf-core'; +import { FormModule, CommentsModule } from '@alfresco/adf-core'; import { MaterialModule } from '../material.module'; - +import { ProcessCommentsModule } from '../process-comments/process-comments.module'; import { CardViewModule, DataColumnModule, DataTableModule, DirectiveModule, PipeModule } from '@alfresco/adf-core'; import { TaskListModule } from '../task-list/task-list.module'; import { PeopleModule } from '../people/people.module'; -import { CommentsModule } from '../comments/comments.module'; import { ContentWidgetModule } from '../content-widget/content-widget.module'; import { ProcessAuditDirective } from './components/process-audit.directive'; @@ -57,7 +56,8 @@ import { ProcessFilterService } from './services/process-filter.service'; DirectiveModule, PeopleModule, CommentsModule, - ContentWidgetModule + ContentWidgetModule, + ProcessCommentsModule ], declarations: [ ProcessInstanceListComponent, @@ -82,4 +82,5 @@ import { ProcessFilterService } from './services/process-filter.service'; StartProcessInstanceComponent ] }) -export class ProcessListModule {} +export class ProcessListModule { +} diff --git a/lib/process-services/process.module.ts b/lib/process-services/process.module.ts index cd373a5d73..2aa75eb875 100644 --- a/lib/process-services/process.module.ts +++ b/lib/process-services/process.module.ts @@ -18,14 +18,14 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { CoreModule, TRANSLATION_PROVIDER } from '@alfresco/adf-core'; +import { CoreModule, TRANSLATION_PROVIDER, CommentsModule } from '@alfresco/adf-core'; import { MaterialModule } from './material.module'; import { ProcessListModule } from './process-list/process-list.module'; import { TaskListModule } from './task-list/task-list.module'; import { AppsListModule } from './app-list/apps-list.module'; -import { CommentsModule } from './comments/comments.module'; +import { ProcessCommentsModule } from './process-comments/process-comments.module'; import { AttachmentModule } from './attachment/attachment.module'; import { PeopleModule } from './people/people.module'; @@ -34,6 +34,7 @@ import { PeopleModule } from './people/people.module'; CoreModule, CommonModule, CommentsModule, + ProcessCommentsModule, FormsModule, ReactiveFormsModule, MaterialModule, @@ -56,6 +57,7 @@ import { PeopleModule } from './people/people.module'; exports: [ CommonModule, CommentsModule, + ProcessCommentsModule, FormsModule, ReactiveFormsModule, ProcessListModule, diff --git a/lib/process-services/styles/_index.scss b/lib/process-services/styles/_index.scss index 79f5c206bb..daa5753fdf 100644 --- a/lib/process-services/styles/_index.scss +++ b/lib/process-services/styles/_index.scss @@ -1,9 +1,7 @@ @import '../process-list/components/process-filters.component'; @import '../attachment/process-attachment-list.component'; @import '../attachment/task-attachment-list.component'; -@import '../comments/comment-list.component'; -@import '../comments/comments.component'; -@import '../comments/process-comments.component'; +@import '../process-comments/process-comments.component'; @import '../people/people.module'; @import '../task-list/components/start-task.component'; @import '../task-list/components/task-filters.component'; @@ -13,8 +11,6 @@ @mixin adf-process-services-theme($theme) { @include adf-process-filters-theme($theme); - @include adf-task-list-comment-list-theme($theme); - @include adf-task-list-comment-theme($theme); @include adf-process-comment-theme($theme); @include adf-task-list-start-task-theme($theme); @include adf-people-module-theme($theme); diff --git a/lib/process-services/task-list/components/task-details.component.spec.ts b/lib/process-services/task-list/components/task-details.component.spec.ts index 63dcd2acfd..f1e257f801 100644 --- a/lib/process-services/task-list/components/task-details.component.spec.ts +++ b/lib/process-services/task-list/components/task-details.component.spec.ts @@ -30,6 +30,7 @@ import { noDataMock, taskDetailsMock, taskFormMock, tasksMock, taskDetailsWithOu import { TaskListService } from './../services/tasklist.service'; import { PeopleSearchComponent } from '../../people'; import { TaskDetailsComponent } from './task-details.component'; +import { DatePipe } from '@angular/common'; declare let jasmine: any; @@ -68,7 +69,8 @@ describe('TaskDetailsComponent', () => { TaskListService, PeopleProcessService, CommentProcessService, - AuthenticationService + AuthenticationService, + DatePipe ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -83,7 +85,6 @@ describe('TaskDetailsComponent', () => { component = fixture.componentInstance; service = fixture.debugElement.injector.get(TaskListService); formService = fixture.debugElement.injector.get(FormService); - commentProcessService = TestBed.get(CommentProcessService); getTaskDetailsSpy = spyOn(service, 'getTaskDetails').and.returnValue(Observable.of(taskDetailsMock)); spyOn(formService, 'getTaskForm').and.returnValue(Observable.of(taskFormMock)); @@ -93,9 +94,14 @@ describe('TaskDetailsComponent', () => { getTasksSpy = spyOn(service, 'getTasks').and.returnValue(Observable.of(tasksMock)); assignTaskSpy = spyOn(service, 'assignTask').and.returnValue(Observable.of(fakeUser)); completeTaskSpy = spyOn(service, 'completeTask').and.returnValue(Observable.of({})); - spyOn(commentProcessService, 'getTaskComments').and.returnValue(Observable.of(noDataMock)); spyOn(service, 'getTaskChecklist').and.returnValue(Observable.of(noDataMock)); + commentProcessService = fixture.debugElement.injector.get(CommentProcessService); + spyOn(commentProcessService, 'getTaskComments').and.returnValue(Observable.of([ + {message: 'Test1', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}}, + {message: 'Test2', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}}, + {message: 'Test3', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}} + ])); }); it('should load task details when taskId specified', () => { @@ -336,7 +342,7 @@ describe('TaskDetailsComponent', () => { component.taskDetails.endDate = new Date('2017-10-03T17:03:57.311+0000'); fixture.detectChanges(); - expect((component.activiticomments as any).nativeElement.readOnly).toBe(true); + expect((component.activiticomments as any).readOnly).toBe(true); }); it('should comments be readonly if the task is complete and user are NOT involved', () => { @@ -348,7 +354,7 @@ describe('TaskDetailsComponent', () => { component.taskDetails.endDate = new Date('2017-10-03T17:03:57.311+0000'); fixture.detectChanges(); - expect((component.activiticomments as any).nativeElement.readOnly).toBe(true); + expect((component.activiticomments as any).readOnly).toBe(true); }); it('should comments NOT be readonly if the task is NOT complete and user are NOT involved', () => { @@ -360,7 +366,7 @@ describe('TaskDetailsComponent', () => { component.taskDetails.endDate = null; fixture.detectChanges(); - expect((component.activiticomments as any).nativeElement.readOnly).toBe(false); + expect((component.activiticomments as any).readOnly).toBe(false); }); it('should comments NOT be readonly if the task is complete and user are involved', () => { @@ -372,7 +378,7 @@ describe('TaskDetailsComponent', () => { component.taskDetails.endDate = new Date('2017-10-03T17:03:57.311+0000'); fixture.detectChanges(); - expect((component.activiticomments as any).nativeElement.readOnly).toBe(false); + expect((component.activiticomments as any).readOnly).toBe(false); }); it('should comments be present if showComments is true', () => { diff --git a/lib/process-services/task-list/components/task-details.component.ts b/lib/process-services/task-list/components/task-details.component.ts index a3caea38e7..b3e77e7795 100644 --- a/lib/process-services/task-list/components/task-details.component.ts +++ b/lib/process-services/task-list/components/task-details.component.ts @@ -22,7 +22,8 @@ import { ClickNotification, LogService, UpdateNotification, - FormRenderingService + FormRenderingService, + CommentsComponent } from '@alfresco/adf-core'; import { Component, @@ -42,7 +43,6 @@ import { ContentLinkModel, FormFieldValidator, FormModel, FormOutcomeEvent } fro import { TaskQueryRequestRepresentationModel } from '../models/filter.model'; import { TaskDetailsModel } from '../models/task-details.model'; import { TaskListService } from './../services/tasklist.service'; -import { CommentsComponent } from '../../comments'; import { AttachFileWidgetComponent, AttachFolderWidgetComponent } from '../../content-widget'; @Component({ diff --git a/lib/process-services/task-list/task-list.module.ts b/lib/process-services/task-list/task-list.module.ts index b76e1f79d8..f598714378 100644 --- a/lib/process-services/task-list/task-list.module.ts +++ b/lib/process-services/task-list/task-list.module.ts @@ -19,14 +19,13 @@ import { CommonModule, DatePipe } from '@angular/common'; import { NgModule } from '@angular/core'; import { FlexLayoutModule } from '@angular/flex-layout'; import { TranslateModule } from '@ngx-translate/core'; -import { FormModule } from '@alfresco/adf-core'; - -import { CardViewModule, DataColumnModule, DataTableModule, DirectiveModule, InfoDrawerModule } from '@alfresco/adf-core'; +import { FormModule, CommentsModule } from '@alfresco/adf-core'; +import { ProcessCommentsModule } from '../process-comments/process-comments.module'; +import { CardViewModule, DataColumnModule, DataTableModule, DirectiveModule, InfoDrawerModule } from '@alfresco/adf-core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MaterialModule } from '../material.module'; import { PeopleModule } from '../people/people.module'; -import { CommentsModule } from '../comments/comments.module'; import { ContentWidgetModule } from '../content-widget/content-widget.module'; import { TaskUploadService } from './services/task-upload.service'; import { ProcessUploadService } from './services/process-upload.service'; @@ -59,6 +58,7 @@ import { TaskStandaloneComponent } from './components/task-standalone.component' ReactiveFormsModule, PeopleModule, CommentsModule, + ProcessCommentsModule, ContentWidgetModule ], declarations: [ @@ -91,4 +91,5 @@ import { TaskStandaloneComponent } from './components/task-standalone.component' TaskStandaloneComponent ] }) -export class TaskListModule {} +export class TaskListModule { +}