[ADF-2588] make comment component compatible with content (#3128)

This commit is contained in:
Mario Romano
2018-03-30 11:13:38 +01:00
committed by Eugenio Romano
parent f985dd11d5
commit 653a510a5c
46 changed files with 1186 additions and 462 deletions

View File

@@ -2,6 +2,10 @@
<ng-template let-node="node" #sidebarTemplate> <ng-template let-node="node" #sidebarTemplate>
<adf-info-drawer title="Details"> <adf-info-drawer title="Details">
<adf-info-drawer-tab label="Comments">
<adf-comments [nodeId]="nodeId"></adf-comments>
</adf-info-drawer-tab>
<adf-info-drawer-tab label="Properties"> <adf-info-drawer-tab label="Properties">
<adf-content-metadata-card [node]="node"></adf-content-metadata-card> <adf-content-metadata-card [node]="node"></adf-content-metadata-card>
</adf-info-drawer-tab> </adf-info-drawer-tab>

View File

@@ -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) | | [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) | | [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) | | [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 ## Directives

View File

@@ -41,7 +41,7 @@ export class SomeComponent implements OnInit {
}, },
} }
]; ];
onClickCommentRow(comment: CommentProcessModel) { onClickCommentRow(comment: CommentProcessModel) {
console.log('Clicked row: ', comment); console.log('Clicked row: ', comment);
} }
@@ -50,10 +50,10 @@ export class SomeComponent implements OnInit {
In the component template use the comment list component: In the component template use the comment list component:
```html ```html
<adf-comment-list <adf-comment-list
[comments]="comments" [comments]="comments"
(clickRow)="onClickCommentRow($event)"> (clickRow)="onClickCommentRow($event)">
</adf-comment-list> </adf-comment-list>
``` ```
### Properties ### Properties

View File

@@ -4,11 +4,11 @@ Status: Active
--- ---
# Comments Component # 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) ![adf-comments](../docassets/images/adf-comments.png)
## Basic Usage ## Basic Usage Task
```html ```html
<adf-comments <adf-comments
@@ -17,11 +17,21 @@ Displays comments from users involved in a specified task and allows an involved
</adf-comments> </adf-comments>
``` ```
## Basic Usage Content
```html
<adf-comments
[nodeId]="YOUR_NODE_ID"
[readOnly]="YOUR_READ_ONLY_FLAG">
</adf-comments>
```
### Properties ### Properties
| Name | Type | Default value | Description | | Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- | | ---- | ---- | ------------- | ----------- |
| taskId | `string` | | The numeric ID of the task. | | taskId | `string` | | The numeric ID of the task. |
| nodeId | `string` | | The ID of the node. |
| readOnly | `boolean` | `false` | Are the comments read only? | | readOnly | `boolean` | `false` | Are the comments read only? |
### Events ### Events

View File

@@ -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) | | [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) | | [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) | | [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) | | [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 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) | | [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) |

View File

@@ -32,7 +32,7 @@ describe('VersionListComponent', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ declarations: [
VersionListComponent VersionListComponent
] ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents(); }).compileComponents();
})); }));

View File

@@ -1,4 +1,4 @@
<mat-list class="adf-comment-list"> <mat-list class="adf-comment-list">
<mat-list-item *ngFor="let comment of comments" <mat-list-item *ngFor="let comment of comments"
(click)="selectComment(comment)" (click)="selectComment(comment)"
class="adf-comment-list-item" class="adf-comment-list-item"
@@ -6,13 +6,14 @@
id="adf-comment-{{comment?.id}}"> id="adf-comment-{{comment?.id}}">
<div id="comment-user-icon" class="adf-comment-img-container"> <div id="comment-user-icon" class="adf-comment-img-container">
<div <div
*ngIf="!comment.createdBy.pictureId" *ngIf="!isPictureDefined(comment.createdBy)"
class="adf-comment-user-icon"> class="adf-comment-user-icon">
{{getUserShortName(comment.createdBy)}}</div> {{getUserShortName(comment.createdBy)}}
</div>
<div> <div>
<img *ngIf="comment.createdBy.pictureId" <img *ngIf="isPictureDefined(comment.createdBy)"
class="adf-people-img" class="adf-people-img"
[src]="peopleProcessService.getUserImage(comment.createdBy)" [src]="getUserImage(comment.createdBy)"
/> />
</div> </div>
</div> </div>

View File

@@ -15,11 +15,14 @@
* limitations under the License. * limitations under the License.
*/ */
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 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 { CommentListComponent } from './comment-list.component';
import { By } from '@angular/platform-browser'; 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({ const testUser: UserProcessModel = new UserProcessModel({
id: '1', id: '1',
@@ -28,45 +31,113 @@ const testUser: UserProcessModel = new UserProcessModel({
email: 'tu@domain.com' email: 'tu@domain.com'
}); });
const testDate = new Date(); const testDate = new Date();
const testComment: CommentProcessModel = new CommentProcessModel({ const processCommentOne: CommentModel = new CommentModel({
id: 1, id: 1,
message: 'Test Comment', message: 'Test Comment',
created: testDate.toDateString(), created: testDate.toDateString(),
createdBy: testUser createdBy: testUser
}); });
const secondtestComment: CommentProcessModel = new CommentProcessModel({ const processCommentTwo: CommentModel = new CommentModel({
id: 2, id: 2,
message: '2nd Test Comment', message: '2nd Test Comment',
created: new Date().toDateString(), created: new Date().toDateString(),
createdBy: testUser 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', () => { describe('CommentListComponent', () => {
let commentList: CommentListComponent; let commentList: CommentListComponent;
let fixture: ComponentFixture<CommentListComponent>; let fixture: ComponentFixture<CommentListComponent>;
let element: HTMLElement; let element: HTMLElement;
let ecmUserService: EcmUserService;
let peopleProcessService: PeopleProcessService;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ declarations: [
CommentListComponent CommentListComponent
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [ providers: [
DatePipe DatePipe,
PeopleProcessService,
EcmUserService
] ]
}).compileComponents().then(() => { }).compileComponents().then(() => {
fixture = TestBed.createComponent(CommentListComponent); fixture = TestBed.createComponent(CommentListComponent);
ecmUserService = TestBed.get(EcmUserService);
peopleProcessService = TestBed.get(PeopleProcessService);
commentList = fixture.componentInstance; commentList = fixture.componentInstance;
element = fixture.nativeElement; element = fixture.nativeElement;
fixture.detectChanges(); 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(() => { it('should emit row click event', async(() => {
commentList.comments = [testComment]; commentList.comments = [processCommentOne];
commentList.clickRow.subscribe(selectedComment => { commentList.clickRow.subscribe(selectedComment => {
expect(selectedComment.id).toEqual(1); 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(() => { it('should deselect the previous selected comment when a new one is clicked', async(() => {
testComment.isSelected = true; processCommentOne.isSelected = true;
commentList.selectedComment = testComment; commentList.selectedComment = processCommentOne;
commentList.comments = [testComment, secondtestComment]; commentList.comments = [processCommentOne, processCommentTwo];
commentList.clickRow.subscribe(selectedComment => { commentList.clickRow.subscribe(selectedComment => {
fixture.detectChanges(); fixture.detectChanges();
@@ -111,31 +182,31 @@ describe('CommentListComponent', () => {
})); }));
it('should show comment message when input is given', async(() => { it('should show comment message when input is given', async(() => {
commentList.comments = [testComment]; commentList.comments = [processCommentOne];
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
let elements = fixture.nativeElement.querySelectorAll('#comment-message'); let elements = fixture.nativeElement.querySelectorAll('#comment-message');
expect(elements.length).toBe(1); 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(); expect(fixture.nativeElement.querySelector('#comment-message:empty')).toBeNull();
}); });
})); }));
it('should show comment user when input is given', async(() => { it('should show comment user when input is given', async(() => {
commentList.comments = [testComment]; commentList.comments = [processCommentOne];
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
let elements = fixture.nativeElement.querySelectorAll('#comment-user'); let elements = fixture.nativeElement.querySelectorAll('#comment-user');
expect(elements.length).toBe(1); 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(); expect(fixture.nativeElement.querySelector('#comment-user:empty')).toBeNull();
}); });
})); }));
it('should show comment date time when input is given', async(() => { it('should show comment date time when input is given', async(() => {
commentList.comments = [testComment]; commentList.comments = [processCommentOne];
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
@@ -147,7 +218,7 @@ describe('CommentListComponent', () => {
})); }));
it('comment date time should start with Today when comment date is today', async(() => { it('comment date time should start with Today when comment date is today', async(() => {
commentList.comments = [testComment]; commentList.comments = [processCommentOne];
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
@@ -157,8 +228,8 @@ describe('CommentListComponent', () => {
})); }));
it('comment date time should start with Yesterday when comment date is yesterday', async(() => { it('comment date time should start with Yesterday when comment date is yesterday', async(() => {
testComment.created = new Date((Date.now() - 24 * 3600 * 1000)); processCommentOne.created = new Date((Date.now() - 24 * 3600 * 1000));
commentList.comments = [testComment]; commentList.comments = [processCommentOne];
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { 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(() => { 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)); processCommentOne.created = new Date((Date.now() - 24 * 3600 * 1000 * 2));
commentList.comments = [testComment]; commentList.comments = [processCommentOne];
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
@@ -180,14 +251,56 @@ describe('CommentListComponent', () => {
})); }));
it('should show user icon when input is given', async(() => { it('should show user icon when input is given', async(() => {
commentList.comments = [testComment]; commentList.comments = [processCommentOne];
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
let elements = fixture.nativeElement.querySelectorAll('#comment-user-icon'); let elements = fixture.nativeElement.querySelectorAll('#comment-user-icon');
expect(elements.length).toBe(1); 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(); 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);
});
}));
}); });

View File

@@ -15,9 +15,11 @@
* limitations under the License. * 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 { 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({ @Component({
selector: 'adf-comment-list', selector: 'adf-comment-list',
@@ -30,18 +32,19 @@ export class CommentListComponent {
/** The comments data used to populate the list. */ /** The comments data used to populate the list. */
@Input() @Input()
comments: CommentProcessModel[]; comments: CommentModel[];
/** Emitted when the user clicks on one of the comment rows. */ /** Emitted when the user clicks on one of the comment rows. */
@Output() @Output()
clickRow: EventEmitter<CommentProcessModel> = new EventEmitter<CommentProcessModel>(); clickRow: EventEmitter<CommentModel> = new EventEmitter<CommentModel>();
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) { if (this.selectedComment) {
this.selectedComment.isSelected = false; this.selectedComment.isSelected = false;
} }
@@ -50,7 +53,7 @@ export class CommentListComponent {
this.clickRow.emit(this.selectedComment); this.clickRow.emit(this.selectedComment);
} }
getUserShortName(user: UserProcessModel): string { getUserShortName(user: any): string {
let shortName = ''; let shortName = '';
if (user) { if (user) {
if (user.firstName) { if (user.firstName) {
@@ -63,6 +66,18 @@ export class CommentListComponent {
return shortName; 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 { transformDate(aDate: string): string {
let formattedDate: string; let formattedDate: string;
let givenDate = Number.parseInt(this.datePipe.transform(aDate, 'yMMdd')); let givenDate = Number.parseInt(this.datePipe.transform(aDate, 'yMMdd'));
@@ -79,4 +94,8 @@ export class CommentListComponent {
} }
return formattedDate; return formattedDate;
} }
private isAContentUsers(user: any): boolean {
return user.avatarId;
}
} }

View File

@@ -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<CommentsComponent>;
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();
});
});
});

View File

@@ -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<any> = new EventEmitter<any>();
comments: CommentModel [] = [];
private commentObserver: Observer<CommentModel>;
comment$: Observable<CommentModel>;
message: string;
beingAdded: boolean = false;
constructor(private commentProcessService: CommentProcessService, private commentContentService: CommentContentService) {
this.comment$ = new Observable<CommentModel>(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;
}
}

View File

@@ -20,9 +20,9 @@ import { NgModule } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { MaterialModule } from '../material.module'; import { MaterialModule } from '../material.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 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 { CommentListComponent } from './comment-list.component';
import { CommentsComponent } from './comments.component'; import { CommentsComponent } from './comments.component';
@@ -37,14 +37,13 @@ import { CommentsComponent } from './comments.component';
TranslateModule TranslateModule
], ],
declarations: [ declarations: [
ProcessCommentsComponent,
CommentListComponent, CommentListComponent,
CommentsComponent CommentsComponent
], ],
exports: [ exports: [
ProcessCommentsComponent,
CommentListComponent, CommentListComponent,
CommentsComponent CommentsComponent
] ]
}) })
export class CommentsModule {} export class CommentsModule {
}

View File

@@ -15,6 +15,5 @@
* limitations under the License. * limitations under the License.
*/ */
export * from './process-comments.component';
export * from './comment-list.component'; export * from './comment-list.component';
export * from './comments.component'; export * from './comments.component';

View File

@@ -39,6 +39,7 @@ import { ViewerModule } from './viewer/viewer.module';
import { FormModule } from './form/form.module'; import { FormModule } from './form/form.module';
import { SidenavLayoutModule } from './sidenav-layout/sidenav-layout.module'; import { SidenavLayoutModule } from './sidenav-layout/sidenav-layout.module';
import { SideBarActionModule } from './sidebar/sidebar-action.module'; import { SideBarActionModule } from './sidebar/sidebar-action.module';
import { CommentsModule } from './comments/comments.module';
import { DirectiveModule } from './directives/directive.module'; import { DirectiveModule } from './directives/directive.module';
import { PipeModule } from './pipes/pipe.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 { CardItemTypeService } from './card-view/services/card-item-types.service';
import { CardViewUpdateService } from './card-view/services/card-view-update.service'; import { CardViewUpdateService } from './card-view/services/card-view-update.service';
import { CommentProcessService } from './services/comment-process.service'; import { CommentProcessService } from './services/comment-process.service';
import { CommentContentService } from './services/comment-content.service';
import { ContentService } from './services/content.service'; import { ContentService } from './services/content.service';
import { CookieService } from './services/cookie.service'; import { CookieService } from './services/cookie.service';
import { DeletedNodesApiService } from './services/deleted-nodes-api.service'; import { DeletedNodesApiService } from './services/deleted-nodes-api.service';
@@ -116,6 +118,7 @@ export function providers() {
SitesService, SitesService,
DiscoveryApiService, DiscoveryApiService,
CommentProcessService, CommentProcessService,
CommentContentService,
SearchConfigurationService SearchConfigurationService
]; ];
} }
@@ -141,6 +144,7 @@ export function providers() {
CardViewModule, CardViewModule,
CollapsableModule, CollapsableModule,
FormModule, FormModule,
CommentsModule,
LoginModule, LoginModule,
LanguageMenuModule, LanguageMenuModule,
InfoDrawerModule, InfoDrawerModule,
@@ -174,6 +178,7 @@ export function providers() {
CardViewModule, CardViewModule,
CollapsableModule, CollapsableModule,
FormModule, FormModule,
CommentsModule,
LoginModule, LoginModule,
LanguageMenuModule, LanguageMenuModule,
InfoDrawerModule, InfoDrawerModule,
@@ -206,6 +211,7 @@ export class CoreModuleLazy {
CardViewModule, CardViewModule,
CollapsableModule, CollapsableModule,
FormModule, FormModule,
CommentsModule,
LoginModule, LoginModule,
LanguageMenuModule, LanguageMenuModule,
InfoDrawerModule, InfoDrawerModule,
@@ -239,6 +245,7 @@ export class CoreModuleLazy {
CardViewModule, CardViewModule,
CollapsableModule, CollapsableModule,
FormModule, FormModule,
CommentsModule,
LoginModule, LoginModule,
LanguageMenuModule, LanguageMenuModule,
InfoDrawerModule, InfoDrawerModule,

View File

@@ -35,6 +35,7 @@ export * from './form/form.module';
export * from './sidenav-layout/sidenav-layout.module'; export * from './sidenav-layout/sidenav-layout.module';
export * from './pipes/pipe.module'; export * from './pipes/pipe.module';
export * from './directives/directive.module'; export * from './directives/directive.module';
export * from './comments/comments.module';
export * from './viewer'; export * from './viewer';
export * from './userinfo'; export * from './userinfo';
@@ -53,6 +54,7 @@ export * from './card-view';
export * from './app-config'; export * from './app-config';
export * from './form'; export * from './form';
export * from './sidenav-layout'; export * from './sidenav-layout';
export * from './comments';
export * from './pipes'; export * from './pipes';
export * from './services'; export * from './services';

View File

@@ -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'
}
};

View File

@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { CommentProcessModel } from '../models/comment-process.model'; import { CommentModel } from '../models/comment.model';
import { UserProcessModel } from '../models/user-process.model'; import { UserProcessModel } from '../models/user-process.model';
export let fakeUser1 = { id: 1, email: 'fake-email@dom.com', firstName: 'firstName', lastName: 'lastName' }; 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, id: 13,
firstName: 'Wilbur', firstName: 'Wilbur',
lastName: 'Adams', lastName: 'Adams',

View File

@@ -17,6 +17,11 @@
import { CommentRepresentation, LightUserRepresentation } from 'alfresco-js-api'; 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 { export class CommentProcessModel implements CommentRepresentation {
id: number; id: number;
message: string; message: string;

View File

@@ -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;
}
}
}

View File

@@ -19,6 +19,7 @@ export * from './file.model';
export * from './permissions.enum'; export * from './permissions.enum';
export * from './product-version.model'; export * from './product-version.model';
export * from './user-process.model'; export * from './user-process.model';
export * from './comment-process.model'; export * from './comment.model';
export * from './ecm-company.model'; export * from './ecm-company.model';
export * from './redirection.model'; export * from './redirection.model';
export * from './comment-process.model';

View File

@@ -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)
});
});
});
});

View File

@@ -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<CommentModel> {
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<CommentModel[]> {
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');
}
}

View File

@@ -16,7 +16,7 @@
*/ */
import { async, TestBed } from '@angular/core/testing'; 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 { fakeProcessComment, fakeTasksComment, fakeUser1 } from '../mock/comment-process-service.mock';
import { CommentProcessService } from './comment-process.service'; import { CommentProcessService } from './comment-process.service';
import { AlfrescoApiService } from './alfresco-api.service'; import { AlfrescoApiService } from './alfresco-api.service';
@@ -142,7 +142,7 @@ describe('Comment ProcessService Service', () => {
it('should add a comment task ', (done) => { it('should add a comment task ', (done) => {
service.addTaskComment('999', 'fake-comment-message').subscribe( service.addTaskComment('999', 'fake-comment-message').subscribe(
(res: CommentProcessModel) => { (res: CommentModel) => {
expect(res).toBeDefined(); expect(res).toBeDefined();
expect(res.id).not.toEqual(null); expect(res.id).not.toEqual(null);
expect(res.message).toEqual('fake-comment-message'); expect(res.message).toEqual('fake-comment-message');
@@ -167,7 +167,7 @@ describe('Comment ProcessService Service', () => {
it('should return the tasks comments ', (done) => { it('should return the tasks comments ', (done) => {
service.getTaskComments('999').subscribe( service.getTaskComments('999').subscribe(
(res: CommentProcessModel[]) => { (res: CommentModel[]) => {
expect(res).toBeDefined(); expect(res).toBeDefined();
expect(res.length).toEqual(2); expect(res.length).toEqual(2);
expect(res[0].message).toEqual('fake-message-1'); expect(res[0].message).toEqual('fake-message-1');

View File

@@ -17,7 +17,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable'; 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 { UserProcessModel } from '../models/user-process.model';
import { AlfrescoApiService } from './alfresco-api.service'; import { AlfrescoApiService } from './alfresco-api.service';
import { LogService } from './log.service'; import { LogService } from './log.service';
@@ -32,47 +32,57 @@ export class CommentProcessService {
private logService: LogService) { private logService: LogService) {
} }
addTaskComment(taskId: string, message: string): Observable<CommentProcessModel> { addTaskComment(taskId: string, message: string): Observable<CommentModel> {
return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.addTaskComment({message: message}, taskId)) return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.addTaskComment({message: message}, taskId))
.map(res => res) .map(res => res)
.map((response: CommentProcessModel) => { .map((response: CommentModel) => {
return new CommentProcessModel({id: response.id, message: response.message, created: response.created, createdBy: response.createdBy}); return new CommentModel({
id: response.id,
message: response.message,
created: response.created,
createdBy: response.createdBy
});
}).catch(err => this.handleError(err)); }).catch(err => this.handleError(err));
} }
getTaskComments(taskId: string): Observable<CommentProcessModel[]> { getTaskComments(taskId: string): Observable<CommentModel[]> {
return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.getTaskComments(taskId)) return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.getTaskComments(taskId))
.map(res => res) .map(res => res)
.map((response: any) => { .map((response: any) => {
let comments: CommentProcessModel[] = []; let comments: CommentModel[] = [];
response.data.forEach((comment: CommentProcessModel) => { response.data.forEach((comment: CommentModel) => {
let user = new UserProcessModel(comment.createdBy); 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; return comments;
}).catch(err => this.handleError(err)); }).catch(err => this.handleError(err));
} }
getProcessInstanceComments(processInstanceId: string): Observable<CommentProcessModel[]> { getProcessInstanceComments(processInstanceId: string): Observable<CommentModel[]> {
return Observable.fromPromise(this.apiService.getInstance().activiti.commentsApi.getProcessInstanceComments(processInstanceId)) return Observable.fromPromise(this.apiService.getInstance().activiti.commentsApi.getProcessInstanceComments(processInstanceId))
.map(res => res) .map(res => res)
.map((response: any) => { .map((response: any) => {
let comments: CommentProcessModel[] = []; let comments: CommentModel[] = [];
response.data.forEach((comment: CommentProcessModel) => { response.data.forEach((comment: CommentModel) => {
let user = new UserProcessModel(comment.createdBy); 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; return comments;
}).catch(err => this.handleError(err)); }).catch(err => this.handleError(err));
} }
addProcessInstanceComment(processInstanceId: string, message: string): Observable<CommentProcessModel> { addProcessInstanceComment(processInstanceId: string, message: string): Observable<CommentModel> {
return Observable.fromPromise( return Observable.fromPromise(
this.apiService.getInstance().activiti.commentsApi.addProcessInstanceComment({ message: message }, processInstanceId) this.apiService.getInstance().activiti.commentsApi.addProcessInstanceComment({message: message}, processInstanceId)
) )
.map((response: CommentProcessModel) => { .map((response: CommentModel) => {
return new CommentProcessModel({id: response.id, message: response.message, created: response.created, createdBy: response.createdBy}); return new CommentModel({
id: response.id,
message: response.message,
created: response.created,
createdBy: response.createdBy
});
}).catch(err => this.handleError(err)); }).catch(err => this.handleError(err));
} }

View File

@@ -47,3 +47,4 @@ export * from './sites.service';
export * from './discovery-api.service'; export * from './discovery-api.service';
export * from './comment-process.service'; export * from './comment-process.service';
export * from './search-configuration.service'; export * from './search-configuration.service';
export * from './comment-content.service';

View File

@@ -23,6 +23,8 @@
@import '../viewer/components/imgViewer.component'; @import '../viewer/components/imgViewer.component';
@import '../form/components/form.component'; @import '../form/components/form.component';
@import '../sidebar/sidebar-action-menu.component'; @import '../sidebar/sidebar-action-menu.component';
@import '../comments/comment-list.component';
@import '../comments/comments.component';
@mixin adf-core-theme($theme) { @mixin adf-core-theme($theme) {
@include adf-colors-theme($theme); @include adf-colors-theme($theme);
@@ -48,6 +50,8 @@
@include adf-text-viewer-theme($theme); @include adf-text-viewer-theme($theme);
@include adf-form-component-theme($theme); @include adf-form-component-theme($theme);
@include adf-sidebar-action-menu-theme($theme); @include adf-sidebar-action-menu-theme($theme);
@include adf-task-list-comment-list-theme($theme);
@include adf-task-list-comment-theme($theme);
} }

View File

@@ -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<CommentsComponent>;
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();
});
});
});

View File

@@ -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<any> = new EventEmitter<any>();
comments: CommentProcessModel [] = [];
private commentObserver: Observer<CommentProcessModel>;
comment$: Observable<CommentProcessModel>;
message: string;
beingAdded: boolean = false;
constructor(private commentProcessService: CommentProcessService) {
this.comment$ = new Observable<CommentProcessModel>(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;
}
}

View File

@@ -21,7 +21,12 @@ export * from './process-list/process-list.module';
export * from './task-list/task-list.module'; export * from './task-list/task-list.module';
export * from './app-list/apps-list.module'; export * from './app-list/apps-list.module';
export * from './attachment/attachment.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 './people/people.module';
export * from './content-widget/content-widget.module'; export * from './content-widget/content-widget.module';
@@ -29,6 +34,12 @@ export * from './process-list';
export * from './task-list'; export * from './task-list';
export * from './app-list'; export * from './app-list';
export * from './attachment'; 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 './people';
export * from './content-widget'; export * from './content-widget';

View File

@@ -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';

View File

@@ -21,7 +21,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MatInputModule } from '@angular/material'; import { MatInputModule } from '@angular/material';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { CommentListComponent, CommentsComponent } from '../index';
import { CommentProcessService, PeopleProcessService } from '@alfresco/adf-core'; import { CommentProcessService, PeopleProcessService } from '@alfresco/adf-core';
import { ProcessService } from '../process-list/services/process.service'; import { ProcessService } from '../process-list/services/process.service';
@@ -40,9 +39,7 @@ describe('ActivitiProcessInstanceComments', () => {
MatInputModule MatInputModule
], ],
declarations: [ declarations: [
ProcessCommentsComponent, ProcessCommentsComponent
CommentsComponent,
CommentListComponent
], ],
providers: [ providers: [
ProcessService, ProcessService,

View File

@@ -15,7 +15,7 @@
* limitations under the License. * 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 { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer'; import { Observer } from 'rxjs/Observer';
@@ -39,18 +39,18 @@ export class ProcessCommentsComponent implements OnChanges {
@Output() @Output()
error: EventEmitter<any> = new EventEmitter<any>(); error: EventEmitter<any> = new EventEmitter<any>();
comments: CommentProcessModel [] = []; comments: CommentModel [] = [];
private commentObserver: Observer<CommentProcessModel>; private commentObserver: Observer<CommentModel>;
comment$: Observable<CommentProcessModel>; comment$: Observable<CommentModel>;
message: string; message: string;
beingAdded: boolean = false; beingAdded: boolean = false;
constructor(private commentProcessService: CommentProcessService) { constructor(private commentProcessService: CommentProcessService) {
this.comment$ = new Observable<CommentProcessModel>(observer => this.commentObserver = observer).share(); this.comment$ = new Observable<CommentModel>(observer => this.commentObserver = observer).share();
this.comment$.subscribe((comment: CommentProcessModel) => { this.comment$.subscribe((comment: CommentModel) => {
this.comments.push(comment); this.comments.push(comment);
}); });
} }
@@ -70,8 +70,8 @@ export class ProcessCommentsComponent implements OnChanges {
this.resetComments(); this.resetComments();
if (processInstanceId) { if (processInstanceId) {
this.commentProcessService.getProcessInstanceComments(processInstanceId).subscribe( this.commentProcessService.getProcessInstanceComments(processInstanceId).subscribe(
(res: CommentProcessModel[]) => { (res: CommentModel[]) => {
res = res.sort((comment1: CommentProcessModel, comment2: CommentProcessModel) => { res = res.sort((comment1: CommentModel, comment2: CommentModel) => {
let date1 = new Date(comment1.created); let date1 = new Date(comment1.created);
let date2 = new Date(comment2.created); let date2 = new Date(comment2.created);
return date1 > date2 ? -1 : date1 < date2 ? 1 : 0; return date1 > date2 ? -1 : date1 < date2 ? 1 : 0;
@@ -96,7 +96,7 @@ export class ProcessCommentsComponent implements OnChanges {
this.beingAdded = true; this.beingAdded = true;
this.commentProcessService.addProcessInstanceComment(this.processInstanceId, this.message) this.commentProcessService.addProcessInstanceComment(this.processInstanceId, this.message)
.subscribe( .subscribe(
(res: CommentProcessModel) => { (res: CommentModel) => {
this.comments.unshift(res); this.comments.unshift(res);
this.message = ''; this.message = '';
this.beingAdded = false; this.beingAdded = false;

View File

@@ -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 {
}

View File

@@ -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';

View File

@@ -20,13 +20,12 @@ import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout'; import { FlexLayoutModule } from '@angular/flex-layout';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core'; 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 { MaterialModule } from '../material.module';
import { ProcessCommentsModule } from '../process-comments/process-comments.module';
import { CardViewModule, DataColumnModule, DataTableModule, DirectiveModule, PipeModule } from '@alfresco/adf-core'; import { CardViewModule, DataColumnModule, DataTableModule, DirectiveModule, PipeModule } from '@alfresco/adf-core';
import { TaskListModule } from '../task-list/task-list.module'; import { TaskListModule } from '../task-list/task-list.module';
import { PeopleModule } from '../people/people.module'; import { PeopleModule } from '../people/people.module';
import { CommentsModule } from '../comments/comments.module';
import { ContentWidgetModule } from '../content-widget/content-widget.module'; import { ContentWidgetModule } from '../content-widget/content-widget.module';
import { ProcessAuditDirective } from './components/process-audit.directive'; import { ProcessAuditDirective } from './components/process-audit.directive';
@@ -57,7 +56,8 @@ import { ProcessFilterService } from './services/process-filter.service';
DirectiveModule, DirectiveModule,
PeopleModule, PeopleModule,
CommentsModule, CommentsModule,
ContentWidgetModule ContentWidgetModule,
ProcessCommentsModule
], ],
declarations: [ declarations: [
ProcessInstanceListComponent, ProcessInstanceListComponent,
@@ -82,4 +82,5 @@ import { ProcessFilterService } from './services/process-filter.service';
StartProcessInstanceComponent StartProcessInstanceComponent
] ]
}) })
export class ProcessListModule {} export class ProcessListModule {
}

View File

@@ -18,14 +18,14 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 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 { MaterialModule } from './material.module';
import { ProcessListModule } from './process-list/process-list.module'; import { ProcessListModule } from './process-list/process-list.module';
import { TaskListModule } from './task-list/task-list.module'; import { TaskListModule } from './task-list/task-list.module';
import { AppsListModule } from './app-list/apps-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 { AttachmentModule } from './attachment/attachment.module';
import { PeopleModule } from './people/people.module'; import { PeopleModule } from './people/people.module';
@@ -34,6 +34,7 @@ import { PeopleModule } from './people/people.module';
CoreModule, CoreModule,
CommonModule, CommonModule,
CommentsModule, CommentsModule,
ProcessCommentsModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
MaterialModule, MaterialModule,
@@ -56,6 +57,7 @@ import { PeopleModule } from './people/people.module';
exports: [ exports: [
CommonModule, CommonModule,
CommentsModule, CommentsModule,
ProcessCommentsModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
ProcessListModule, ProcessListModule,

View File

@@ -1,9 +1,7 @@
@import '../process-list/components/process-filters.component'; @import '../process-list/components/process-filters.component';
@import '../attachment/process-attachment-list.component'; @import '../attachment/process-attachment-list.component';
@import '../attachment/task-attachment-list.component'; @import '../attachment/task-attachment-list.component';
@import '../comments/comment-list.component'; @import '../process-comments/process-comments.component';
@import '../comments/comments.component';
@import '../comments/process-comments.component';
@import '../people/people.module'; @import '../people/people.module';
@import '../task-list/components/start-task.component'; @import '../task-list/components/start-task.component';
@import '../task-list/components/task-filters.component'; @import '../task-list/components/task-filters.component';
@@ -13,8 +11,6 @@
@mixin adf-process-services-theme($theme) { @mixin adf-process-services-theme($theme) {
@include adf-process-filters-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-process-comment-theme($theme);
@include adf-task-list-start-task-theme($theme); @include adf-task-list-start-task-theme($theme);
@include adf-people-module-theme($theme); @include adf-people-module-theme($theme);

View File

@@ -30,6 +30,7 @@ import { noDataMock, taskDetailsMock, taskFormMock, tasksMock, taskDetailsWithOu
import { TaskListService } from './../services/tasklist.service'; import { TaskListService } from './../services/tasklist.service';
import { PeopleSearchComponent } from '../../people'; import { PeopleSearchComponent } from '../../people';
import { TaskDetailsComponent } from './task-details.component'; import { TaskDetailsComponent } from './task-details.component';
import { DatePipe } from '@angular/common';
declare let jasmine: any; declare let jasmine: any;
@@ -68,7 +69,8 @@ describe('TaskDetailsComponent', () => {
TaskListService, TaskListService,
PeopleProcessService, PeopleProcessService,
CommentProcessService, CommentProcessService,
AuthenticationService AuthenticationService,
DatePipe
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).compileComponents(); }).compileComponents();
@@ -83,7 +85,6 @@ describe('TaskDetailsComponent', () => {
component = fixture.componentInstance; component = fixture.componentInstance;
service = fixture.debugElement.injector.get(TaskListService); service = fixture.debugElement.injector.get(TaskListService);
formService = fixture.debugElement.injector.get(FormService); formService = fixture.debugElement.injector.get(FormService);
commentProcessService = TestBed.get(CommentProcessService);
getTaskDetailsSpy = spyOn(service, 'getTaskDetails').and.returnValue(Observable.of(taskDetailsMock)); getTaskDetailsSpy = spyOn(service, 'getTaskDetails').and.returnValue(Observable.of(taskDetailsMock));
spyOn(formService, 'getTaskForm').and.returnValue(Observable.of(taskFormMock)); spyOn(formService, 'getTaskForm').and.returnValue(Observable.of(taskFormMock));
@@ -93,9 +94,14 @@ describe('TaskDetailsComponent', () => {
getTasksSpy = spyOn(service, 'getTasks').and.returnValue(Observable.of(tasksMock)); getTasksSpy = spyOn(service, 'getTasks').and.returnValue(Observable.of(tasksMock));
assignTaskSpy = spyOn(service, 'assignTask').and.returnValue(Observable.of(fakeUser)); assignTaskSpy = spyOn(service, 'assignTask').and.returnValue(Observable.of(fakeUser));
completeTaskSpy = spyOn(service, 'completeTask').and.returnValue(Observable.of({})); completeTaskSpy = spyOn(service, 'completeTask').and.returnValue(Observable.of({}));
spyOn(commentProcessService, 'getTaskComments').and.returnValue(Observable.of(noDataMock));
spyOn(service, 'getTaskChecklist').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', () => { 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'); component.taskDetails.endDate = new Date('2017-10-03T17:03:57.311+0000');
fixture.detectChanges(); 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', () => { 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'); component.taskDetails.endDate = new Date('2017-10-03T17:03:57.311+0000');
fixture.detectChanges(); 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', () => { 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; component.taskDetails.endDate = null;
fixture.detectChanges(); 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', () => { 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'); component.taskDetails.endDate = new Date('2017-10-03T17:03:57.311+0000');
fixture.detectChanges(); 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', () => { it('should comments be present if showComments is true', () => {

View File

@@ -22,7 +22,8 @@ import {
ClickNotification, ClickNotification,
LogService, LogService,
UpdateNotification, UpdateNotification,
FormRenderingService FormRenderingService,
CommentsComponent
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { import {
Component, Component,
@@ -42,7 +43,6 @@ import { ContentLinkModel, FormFieldValidator, FormModel, FormOutcomeEvent } fro
import { TaskQueryRequestRepresentationModel } from '../models/filter.model'; import { TaskQueryRequestRepresentationModel } from '../models/filter.model';
import { TaskDetailsModel } from '../models/task-details.model'; import { TaskDetailsModel } from '../models/task-details.model';
import { TaskListService } from './../services/tasklist.service'; import { TaskListService } from './../services/tasklist.service';
import { CommentsComponent } from '../../comments';
import { AttachFileWidgetComponent, AttachFolderWidgetComponent } from '../../content-widget'; import { AttachFileWidgetComponent, AttachFolderWidgetComponent } from '../../content-widget';
@Component({ @Component({

View File

@@ -19,14 +19,13 @@ import { CommonModule, DatePipe } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout'; import { FlexLayoutModule } from '@angular/flex-layout';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { FormModule } 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 { CardViewModule, DataColumnModule, DataTableModule, DirectiveModule, InfoDrawerModule } from '@alfresco/adf-core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MaterialModule } from '../material.module'; import { MaterialModule } from '../material.module';
import { PeopleModule } from '../people/people.module'; import { PeopleModule } from '../people/people.module';
import { CommentsModule } from '../comments/comments.module';
import { ContentWidgetModule } from '../content-widget/content-widget.module'; import { ContentWidgetModule } from '../content-widget/content-widget.module';
import { TaskUploadService } from './services/task-upload.service'; import { TaskUploadService } from './services/task-upload.service';
import { ProcessUploadService } from './services/process-upload.service'; import { ProcessUploadService } from './services/process-upload.service';
@@ -59,6 +58,7 @@ import { TaskStandaloneComponent } from './components/task-standalone.component'
ReactiveFormsModule, ReactiveFormsModule,
PeopleModule, PeopleModule,
CommentsModule, CommentsModule,
ProcessCommentsModule,
ContentWidgetModule ContentWidgetModule
], ],
declarations: [ declarations: [
@@ -91,4 +91,5 @@ import { TaskStandaloneComponent } from './components/task-standalone.component'
TaskStandaloneComponent TaskStandaloneComponent
] ]
}) })
export class TaskListModule {} export class TaskListModule {
}