diff --git a/demo-shell/src/app/components/file-view/file-view.component.html b/demo-shell/src/app/components/file-view/file-view.component.html index 82c6fe3746..f627613bab 100644 --- a/demo-shell/src/app/components/file-view/file-view.component.html +++ b/demo-shell/src/app/components/file-view/file-view.component.html @@ -4,7 +4,11 @@ - + + diff --git a/demo-shell/src/app/components/file-view/file-view.module.ts b/demo-shell/src/app/components/file-view/file-view.module.ts index 0ac04a1554..ce324a2756 100644 --- a/demo-shell/src/app/components/file-view/file-view.module.ts +++ b/demo-shell/src/app/components/file-view/file-view.module.ts @@ -19,7 +19,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Routes, RouterModule } from '@angular/router'; import { CoreModule, InfoDrawerModule } from '@alfresco/adf-core'; -import { ContentDirectiveModule, VersionManagerModule, ContentMetadataModule } from '@alfresco/adf-content-services'; +import { ContentModule, ContentDirectiveModule, VersionManagerModule, ContentMetadataModule } from '@alfresco/adf-content-services'; import { FileViewComponent } from './file-view.component'; const routes: Routes = [ @@ -35,6 +35,7 @@ const routes: Routes = [ RouterModule.forChild(routes), CoreModule, InfoDrawerModule, + ContentModule, ContentDirectiveModule, ContentMetadataModule, VersionManagerModule diff --git a/docs/content-services/components/node-comments.component.md b/docs/content-services/components/node-comments.component.md new file mode 100644 index 0000000000..46d9be106a --- /dev/null +++ b/docs/content-services/components/node-comments.component.md @@ -0,0 +1,29 @@ +--- +Title: Node Comments Component +Added: v5.1.0 +Status: Active +--- + +# [Node Comments Component](../../../lib/content-services/src/lib/node-comments/node-comments.component.ts "Defined in node-comments.component.ts") + +Displays comments from users involved in a specified content and allows an involved user to add a comment to a content. + +![adf-comments](../../docassets/images/adf-comments.png) + +## Basic Usage Task + +```html + + +``` + +## Class members + +### Properties + +| Name | Type | Default value | Description | +| ---- | ---- | ------------- | ----------- | +| nodeId | `string` | | The numeric ID of the node. | +| readOnly | `boolean` | | Are the comments read only? | diff --git a/docs/content-services/services/node-comments.service.md b/docs/content-services/services/node-comments.service.md new file mode 100644 index 0000000000..84eb734bd1 --- /dev/null +++ b/docs/content-services/services/node-comments.service.md @@ -0,0 +1,28 @@ +--- +Title: Node Comments Service +Added: v6.0.0 +Status: Active +Last reviewed: 2022-12-19 +--- + +# [Node Comments service](../../../lib/content-services/src/lib/node-comments/services/node-comments.service.ts "Defined in node-comments.service.ts") + +Adds and retrieves comments for nodes in Content Services. + +## Class members + +### Methods + +- **add**(id: `string`, message: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CommentModel`](../../../lib/core/src/lib/models/comment.model.ts)`>`
+ Adds a comment to a task. + - _id:_ `string` - ID of the target task + - _message:_ `string` - Text for the comment + - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CommentModel`](../../../lib/core/src/lib/models/comment.model.ts)`>` - Details about the comment +- **get**(id: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CommentModel`](../../../lib/core/src/lib/models/comment.model.ts)`[]>`
+ Gets all comments that have been added to a task. + - _id:_ `string` - ID of the target task + - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CommentModel`](../../../lib/core/src/lib/models/comment.model.ts)`[]>` - Details for each comment + +## See also + +- [Node comments component](../../../lib/content-services/src/lib/node-comments/node-comments.component.ts) diff --git a/docs/content-services/services/task-comments.service.md b/docs/content-services/services/task-comments.service.md new file mode 100644 index 0000000000..8b99c024a3 --- /dev/null +++ b/docs/content-services/services/task-comments.service.md @@ -0,0 +1,28 @@ +--- +Title: Task Comments service +Added: v6.0.0 +Status: Active +Last reviewed: 2022-12-19 +--- + +# [Task Comments service](../../../lib/process-services/src/lib/task-comments/services/task-comments.service.ts "Defined in task-comments.service.ts") + +Adds and retrieves comments for task and process instances in Process Services. + +## Class members + +### Methods + +- **get**(id: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CommentModel`](../../../lib/core/models/comment.model.ts)`[]>`
+ Gets all comments that have been added to a task. + - _id:_ `string` - ID of the target task + - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CommentModel`](../../../lib/core/models/comment.model.ts)`[]>` - Details for each comment +- **add**(id: `string`, message: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CommentModel`](../../../lib/core/models/comment.model.ts)`>`
+ Adds a comment to a process instance. + - _id:_ `string` - ID of the target task + - _message:_ `string` - Text for the comment + - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CommentModel`](../../../lib/core/models/comment.model.ts)`>` - Details of the comment added + +## See also + +- [Task comments component](../../../lib/process-services/src/lib/task-comments/task-comments.component.ts) diff --git a/docs/core/components/comments.component.md b/docs/core/components/comments.component.md index 143faa51ac..b076dd08ab 100644 --- a/docs/core/components/comments.component.md +++ b/docs/core/components/comments.component.md @@ -4,9 +4,9 @@ Added: v2.0.0 Status: Active --- -# [Comments Component](lib/core/src/lib/comments/comments.component.ts "Defined in comments.component.ts") +# [Comments Component](../../../lib/core/src/lib/comments/comments.component.ts "Defined in comments.component.ts") -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. +Displays comments from users involved in a specified environment and allows an involved user to add a comment to a environment. ![adf-comments](../../docassets/images/adf-comments.png) @@ -14,16 +14,7 @@ Displays comments from users involved in a specified task or content and allows ```html - -``` - -## Basic Usage Content - -```html - ``` @@ -34,9 +25,8 @@ Displays comments from users involved in a specified task or content and allows | Name | Type | Default value | Description | | ---- | ---- | ------------- | ----------- | -| nodeId | `string` | | The numeric ID of the node. | +| id | `string` | | The numeric ID of the entity. | | readOnly | `boolean` | false | Are the comments read only? | -| taskId | `string` | | The numeric ID of the task. | ### Events diff --git a/docs/process-services/components/task-comments.component.md b/docs/process-services/components/task-comments.component.md new file mode 100644 index 0000000000..06e45f6422 --- /dev/null +++ b/docs/process-services/components/task-comments.component.md @@ -0,0 +1,29 @@ +--- +Title: Task Comments Component +Added: v5.1.0 +Status: Active +--- + +# [Task Comments Component](../../../lib/process-services/src/lib/task-comments/task-comments.component.ts "Defined in task-comments.component.ts") + +Displays comments from users involved in a specified task and allows an involved user to add a comment to a task. + +![adf-comments](../../docassets/images/adf-comments.png) + +## Basic Usage Task + +```html + + +``` + +## Class members + +### Properties + +| Name | Type | Default value | Description | +| ---- | ---- | ------------- | ----------- | +| taskId | `string` | | The numeric ID of the task. | +| readOnly | `boolean` | | Are the comments read only? | diff --git a/docs/upgrade-guide/upgrade50-60.md b/docs/upgrade-guide/upgrade50-60.md index c5b536b5f9..ad42a87be7 100644 --- a/docs/upgrade-guide/upgrade50-60.md +++ b/docs/upgrade-guide/upgrade50-60.md @@ -126,7 +126,7 @@ v6.0.0 and after: ### Properties and methods - +- ``: The `taskId` input has now been renamed as `id` ### Component selectors diff --git a/lib/content-services/src/lib/content.module.ts b/lib/content-services/src/lib/content.module.ts index fc1aa52b19..31dc2e2c2b 100644 --- a/lib/content-services/src/lib/content.module.ts +++ b/lib/content-services/src/lib/content.module.ts @@ -45,6 +45,7 @@ import { VersionCompatibilityModule } from './version-compatibility/version-comp import { versionCompatibilityFactory } from './version-compatibility/version-compatibility-factory'; import { VersionCompatibilityService } from './version-compatibility/version-compatibility.service'; import { ContentPipeModule } from './pipes/content-pipe.module'; +import { NodeCommentsModule } from './node-comments/node-comments.module'; @NgModule({ imports: [ @@ -73,7 +74,8 @@ import { ContentPipeModule } from './pipes/content-pipe.module'; TreeViewModule, ContentTypeModule, AspectListModule, - VersionCompatibilityModule + VersionCompatibilityModule, + NodeCommentsModule ], providers: [ { @@ -106,7 +108,8 @@ import { ContentPipeModule } from './pipes/content-pipe.module'; TreeViewModule, AspectListModule, ContentTypeModule, - VersionCompatibilityModule + VersionCompatibilityModule, + NodeCommentsModule ] }) export class ContentModule { diff --git a/lib/content-services/src/lib/node-comments/index.ts b/lib/content-services/src/lib/node-comments/index.ts new file mode 100644 index 0000000000..a7e30cc675 --- /dev/null +++ b/lib/content-services/src/lib/node-comments/index.ts @@ -0,0 +1,18 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './public-api'; diff --git a/lib/content-services/src/lib/node-comments/mocks/node-comments.mock.ts b/lib/content-services/src/lib/node-comments/mocks/node-comments.mock.ts new file mode 100644 index 0000000000..b6965babce --- /dev/null +++ b/lib/content-services/src/lib/node-comments/mocks/node-comments.mock.ts @@ -0,0 +1,209 @@ +/*! + * @license + * Copyright 2019 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 { CommentModel, EcmCompanyModel, EcmUserModel } from '@alfresco/adf-core'; + +export const 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 const 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 const 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 const 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' + } +}; + +const fakeCompany: EcmCompanyModel = { + organization: '', + address1: '', + address2: '', + address3: '', + postcode: '', + telephone: '', + fax: '', + email: '' +}; + +const johnDoe: EcmUserModel = { + id: '1', + email: 'john.doe@alfresco.com', + firstName: 'John', + lastName: 'Doe', + company: fakeCompany, + enabled: true, + isAdmin: undefined, + avatarId: '001' +}; + +const janeEod: EcmUserModel = { + id: '2', + email: 'jane.eod@alfresco.com', + firstName: 'Jane', + lastName: 'Eod', + company: fakeCompany, + enabled: true, + isAdmin: undefined +}; + +const robertSmith: EcmUserModel = { + id: '3', + email: 'robert.smith@alfresco.com', + firstName: 'Robert', + lastName: 'Smith', + company: fakeCompany, + enabled: true, + isAdmin: undefined +}; + +export const testUser: EcmUserModel = { + id: '44', + email: 'test.user@hyland.com', + firstName: 'Test', + lastName: 'User', + company: fakeCompany, + enabled: true, + isAdmin: undefined, + avatarId: '044' +}; + +export const getDateXMinutesAgo = (minutes: number) => new Date(new Date().getTime() - minutes * 60000); + +export const commentsNodeData: CommentModel[] = [ + { + id: 1, + message: `I've done this component, is it cool?`, + created: getDateXMinutesAgo(30), + createdBy: johnDoe, + isSelected: false + }, + { + id: 2, + message: 'Yeah', + created: getDateXMinutesAgo(15), + createdBy: janeEod, + isSelected: false + }, + { + id: 3, + message: '+1', + created: getDateXMinutesAgo(12), + createdBy: robertSmith, + isSelected: false + }, + { + id: 4, + message: 'ty', + created: new Date(), + createdBy: johnDoe, + isSelected: false + } +]; + +export const commentsTaskData: CommentModel[] = [ + { + id: 1, + message: `I've done this task, what's next?`, + created: getDateXMinutesAgo(30), + createdBy: johnDoe, + isSelected: false + }, + { + id: 2, + message: `I've assigned you another one 🤠`, + created: getDateXMinutesAgo(15), + createdBy: janeEod, + isSelected: false + }, + { + id: 3, + message: '+1', + created: getDateXMinutesAgo(12), + createdBy: robertSmith, + isSelected: false + }, + { + id: 4, + message: 'Cheers', + created: new Date(), + createdBy: johnDoe, + isSelected: false + } +]; diff --git a/lib/content-services/src/lib/node-comments/node-comments.component.html b/lib/content-services/src/lib/node-comments/node-comments.component.html new file mode 100644 index 0000000000..c888c67d2d --- /dev/null +++ b/lib/content-services/src/lib/node-comments/node-comments.component.html @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/lib/content-services/src/lib/node-comments/node-comments.component.ts b/lib/content-services/src/lib/node-comments/node-comments.component.ts new file mode 100644 index 0000000000..3ed4b60233 --- /dev/null +++ b/lib/content-services/src/lib/node-comments/node-comments.component.ts @@ -0,0 +1,31 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, Input, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'adf-node-comments', + templateUrl: './node-comments.component.html', + encapsulation: ViewEncapsulation.None +}) +export class NodeCommentsComponent { + @Input() + nodeId: string; + + @Input() + readOnly: boolean; +} diff --git a/lib/content-services/src/lib/node-comments/node-comments.module.ts b/lib/content-services/src/lib/node-comments/node-comments.module.ts new file mode 100644 index 0000000000..2bc55880c6 --- /dev/null +++ b/lib/content-services/src/lib/node-comments/node-comments.module.ts @@ -0,0 +1,38 @@ +/*! + * @license + * Copyright 2019 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 { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { NodeCommentsComponent } from './node-comments.component'; +import { ADF_COMMENTS_SERVICE, CoreModule } from '@alfresco/adf-core'; +import { NodeCommentsService } from './services/node-comments.service'; + +@NgModule({ + imports: [ + CommonModule, + CoreModule + ], + declarations: [NodeCommentsComponent], + exports: [NodeCommentsComponent], + providers: [ + { + provide: ADF_COMMENTS_SERVICE, + useClass: NodeCommentsService + } + ] +}) +export class NodeCommentsModule {} diff --git a/lib/content-services/src/lib/node-comments/public-api.ts b/lib/content-services/src/lib/node-comments/public-api.ts new file mode 100644 index 0000000000..e8f379d9b9 --- /dev/null +++ b/lib/content-services/src/lib/node-comments/public-api.ts @@ -0,0 +1,22 @@ +/*! + * @license + * Copyright 2019 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 './node-comments.component'; + +export * from './services/node-comments.service'; + +export * from './node-comments.module'; diff --git a/lib/content-services/src/lib/node-comments/services/node-comments.service.spec.ts b/lib/content-services/src/lib/node-comments/services/node-comments.service.spec.ts new file mode 100644 index 0000000000..85734628f6 --- /dev/null +++ b/lib/content-services/src/lib/node-comments/services/node-comments.service.spec.ts @@ -0,0 +1,88 @@ +/*! + * @license + * Copyright 2019 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, setupTestBed, CoreTestingModule } from '@alfresco/adf-core'; +import { fakeContentComment, fakeContentComments } from '../mocks/node-comments.mock'; +import { TranslateModule } from '@ngx-translate/core'; +import { NodeCommentsService } from './node-comments.service'; + +declare let jasmine: any; + +describe('NodeCommentsService', () => { + + let service: NodeCommentsService; + + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + CoreTestingModule + ] + }); + + beforeEach(() => { + service = TestBed.inject(NodeCommentsService); + + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + describe('Node comments', () => { + + it('should add a comment node ', (done) => { + service.add('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.get('999').subscribe( + (res: CommentModel[]) => { + expect(res).toBeDefined(); + expect(res.length).toEqual(2); + expect(res[0].message).toEqual('fake-message-1'); + expect(res[1].message).toEqual('fake-message-2'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeContentComments) + }); + }); + }); +}); diff --git a/lib/content-services/src/lib/node-comments/services/node-comments.service.ts b/lib/content-services/src/lib/node-comments/services/node-comments.service.ts new file mode 100644 index 0000000000..804384726a --- /dev/null +++ b/lib/content-services/src/lib/node-comments/services/node-comments.service.ts @@ -0,0 +1,102 @@ +/*! + * @license + * Copyright 2019 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 { AlfrescoApiService, LogService, CommentModel } from '@alfresco/adf-core'; +import { CommentEntry, CommentsApi, Comment } from '@alfresco/js-api'; +import { Injectable } from '@angular/core'; +import { Observable, from, throwError } from 'rxjs'; +import { map, catchError } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class NodeCommentsService { + + private _commentsApi: CommentsApi; + get commentsApi(): CommentsApi { + this._commentsApi = this._commentsApi ?? new CommentsApi(this.apiService.getInstance()); + return this._commentsApi; + } + + constructor( + private apiService: AlfrescoApiService, + private logService: LogService + ) {} + + /** + * Gets all comments that have been added to a task. + * + * @param id ID of the target task + * @returns Details for each comment + */ + get(id: string): Observable { + return from(this.commentsApi.listComments(id)) + .pipe( + map((response) => { + const comments: CommentModel[] = []; + + response.list.entries.forEach((comment: CommentEntry) => { + this.addToComments(comments, comment); + }); + + return comments; + }), + catchError( + (err: any) => this.handleError(err) + ) + ); + } + + /** + * Adds a comment to a task. + * + * @param id ID of the target task + * @param message Text for the comment + * @returns Details about the comment + */ + add(id: string, message: string): Observable { + return from(this.commentsApi.createComment(id, { content: message })) + .pipe( + map( + (response: CommentEntry) => this.newCommentModel(response.entry) + ), + catchError( + (err: any) => this.handleError(err) + ) + ); + } + + private addToComments(comments: CommentModel[], comment: CommentEntry): void { + const newComment: Comment = comment.entry; + + comments.push(this.newCommentModel(newComment)); + } + + private newCommentModel(comment: Comment): CommentModel { + return new CommentModel({ + id: comment.id, + message: comment.content, + created: comment.createdAt, + createdBy: comment.createdBy + }); + } + + private handleError(error: any) { + this.logService.error(error); + return throwError(error || 'Server error'); + } +} diff --git a/lib/content-services/src/public-api.ts b/lib/content-services/src/public-api.ts index c8db6e03b3..2d280d6942 100644 --- a/lib/content-services/src/public-api.ts +++ b/lib/content-services/src/public-api.ts @@ -35,6 +35,7 @@ export * from './lib/tree-view/index'; export * from './lib/group/index'; export * from './lib/aspect-list/index'; export * from './lib/content-type/index'; +export * from './lib/node-comments/index'; export * from './lib/new-version-uploader'; export * from './lib/interfaces/index'; export * from './lib/version-compatibility/index'; diff --git a/lib/core/src/lib/comments/comments.component.html b/lib/core/src/lib/comments/comments.component.html index 53e473e64d..1bdf00c96d 100644 --- a/lib/core/src/lib/comments/comments.component.html +++ b/lib/core/src/lib/comments/comments.component.html @@ -1,24 +1,29 @@
- {{'COMMENTS.HEADER' | translate: { count: comments?.length} }} + {{'COMMENTS.HEADER' | translate: { count: comments?.length } }}
-
+
- + (keydown.escape)="clearMessage($event)" + > +
-
diff --git a/lib/core/src/lib/comments/comments.component.spec.ts b/lib/core/src/lib/comments/comments.component.spec.ts index ba41e421c4..fed6285224 100644 --- a/lib/core/src/lib/comments/comments.component.spec.ts +++ b/lib/core/src/lib/comments/comments.component.spec.ts @@ -17,100 +17,74 @@ import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { of, throwError } from 'rxjs'; -import { CommentProcessService } from '../services/comment-process.service'; import { CommentsComponent } from './comments.component'; -import { CommentContentService } from '../services/comment-content.service'; import { setupTestBed } from '../testing/setup-test-bed'; import { CoreTestingModule } from '../testing/core.testing.module'; import { TranslateModule } from '@ngx-translate/core'; -import { CommentModel } from '../models/comment.model'; +import { CommentsServiceMock, commentsResponseMock } from './mocks/comments.service.mock'; +import { ADF_COMMENTS_SERVICE, CommentsService } from './interfaces'; +import { of, throwError } from 'rxjs'; describe('CommentsComponent', () => { let component: CommentsComponent; let fixture: ComponentFixture; - let getProcessCommentsSpy: jasmine.Spy; - let addProcessCommentSpy: jasmine.Spy; - let addContentCommentSpy: jasmine.Spy; - let getContentCommentsSpy: jasmine.Spy; - let commentProcessService: CommentProcessService; - let commentContentService: CommentContentService; + let getCommentSpy: jasmine.Spy; + let addCommentSpy: jasmine.Spy; + let commentsService: CommentsService; setupTestBed({ imports: [ TranslateModule.forRoot(), CoreTestingModule ], - schemas: [CUSTOM_ELEMENTS_SCHEMA] + schemas: [CUSTOM_ELEMENTS_SCHEMA], + providers: [ + { + provide: ADF_COMMENTS_SERVICE, + useClass: CommentsServiceMock + } + ] }); beforeEach(() => { fixture = TestBed.createComponent(CommentsComponent); component = fixture.componentInstance; - commentProcessService = fixture.debugElement.injector.get(CommentProcessService); - commentContentService = fixture.debugElement.injector.get(CommentContentService); + commentsService = fixture.componentInstance['commentsService']; - addContentCommentSpy = spyOn(commentContentService, 'addNodeComment').and.returnValue(of(new CommentModel({ - id: 123, - message: 'Test Comment', - createdBy: {id: '999'} - }))); - - getContentCommentsSpy = spyOn(commentContentService, 'getNodeComments').and.returnValue(of([ - new CommentModel({message: 'Test1', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}}), - new CommentModel({message: 'Test2', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}}), - new CommentModel({message: 'Test3', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}}) - ])); - - getProcessCommentsSpy = spyOn(commentProcessService, 'getTaskComments').and.returnValue(of([ - new CommentModel({message: 'Test1', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}}), - new CommentModel({message: 'Test2', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}}), - new CommentModel({message: 'Test3', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'}}) - ])); - addProcessCommentSpy = spyOn(commentProcessService, 'addTaskComment').and.returnValue(of(new CommentModel({ - id: 123, - message: 'Test Comment', - createdBy: {id: '999'} - }))); + getCommentSpy = spyOn(commentsService, 'get').and.returnValue(commentsResponseMock.getComments()); + addCommentSpy = spyOn(commentsService, 'add').and.returnValue(commentsResponseMock.addComment()); }); afterEach(() => { fixture.destroy(); }); - it('should load comments when taskId specified', () => { + it('should load comments when id specified', () => { const change = new SimpleChange(null, '123', true); - component.ngOnChanges({taskId: change}); + component.ngOnChanges({id: change}); - expect(getProcessCommentsSpy).toHaveBeenCalled(); - }); - - it('should load comments when nodeId specified', () => { - const change = new SimpleChange(null, '123', true); - component.ngOnChanges({nodeId: change}); - - expect(getContentCommentsSpy).toHaveBeenCalled(); + expect(getCommentSpy).toHaveBeenCalled(); }); it('should emit an error when an error occurs loading comments', () => { const emitSpy = spyOn(component.error, 'emit'); - getProcessCommentsSpy.and.returnValue(throwError({})); + getCommentSpy.and.returnValue(throwError({})); const change = new SimpleChange(null, '123', true); - component.ngOnChanges({taskId: change}); + component.ngOnChanges({id: change}); expect(emitSpy).toHaveBeenCalled(); }); - it('should not load comments when no taskId is specified', () => { + it('should not load comments when no id is specified', () => { fixture.detectChanges(); - expect(getProcessCommentsSpy).not.toHaveBeenCalled(); + expect(getCommentSpy).not.toHaveBeenCalled(); }); - it('should display comments when the task has comments', async () => { + it('should display comments when the entity has comments', async () => { const change = new SimpleChange(null, '123', true); - component.ngOnChanges({taskId: change}); + component.ngOnChanges({id: change}); fixture.detectChanges(); await fixture.whenStable(); @@ -119,9 +93,9 @@ describe('CommentsComponent', () => { expect(fixture.nativeElement.querySelector('.adf-comment-message:empty')).toBeNull(); }); - it('should display comments count when the task has comments', async () => { + it('should display comments count when the entity has comments', async () => { const change = new SimpleChange(null, '123', true); - component.ngOnChanges({taskId: change}); + component.ngOnChanges({id: change}); fixture.detectChanges(); await fixture.whenStable(); @@ -130,9 +104,9 @@ describe('CommentsComponent', () => { expect(element.innerText).toBe('COMMENTS.HEADER'); }); - it('should not display comments when the task has no comments', async () => { - component.taskId = '123'; - getProcessCommentsSpy.and.returnValue(of([])); + it('should not display comments when the entity has no comments', async () => { + component.id = '123'; + getCommentSpy.and.returnValue(of([])); fixture.detectChanges(); await fixture.whenStable(); @@ -142,7 +116,7 @@ describe('CommentsComponent', () => { it('should display comments input by default', async () => { const change = new SimpleChange(null, '123', true); - component.ngOnChanges({taskId: change}); + component.ngOnChanges({id: change}); fixture.detectChanges(); await fixture.whenStable(); @@ -150,7 +124,7 @@ describe('CommentsComponent', () => { expect(fixture.nativeElement.querySelector('#comment-input')).not.toBeNull(); }); - it('should not display comments input when the task is readonly', async () => { + it('should not display comments input when the entity is readonly', async () => { component.readOnly = true; fixture.detectChanges(); @@ -159,60 +133,35 @@ describe('CommentsComponent', () => { expect(fixture.nativeElement.querySelector('#comment-input')).toBeNull(); }); - describe('change detection taskId', () => { + describe('Change detection id', () => { const change = new SimpleChange('123', '456', true); const nullChange = new SimpleChange('123', null, true); beforeEach(() => { - component.taskId = '123'; + component.id = '123'; fixture.detectChanges(); }); - it('should fetch new comments when taskId changed', () => { - component.ngOnChanges({taskId: change}); - expect(getProcessCommentsSpy).toHaveBeenCalledWith('456'); + it('should fetch new comments when id changed', () => { + component.ngOnChanges({id: change}); + expect(getCommentSpy).toHaveBeenCalledWith('456'); }); it('should not fetch new comments when empty changeset made', () => { component.ngOnChanges({}); - expect(getProcessCommentsSpy).not.toHaveBeenCalled(); + expect(getCommentSpy).not.toHaveBeenCalled(); }); - it('should not fetch new comments when taskId changed to null', () => { - component.ngOnChanges({taskId: nullChange}); - expect(getProcessCommentsSpy).not.toHaveBeenCalled(); + it('should not fetch new comments when id changed to null', () => { + component.ngOnChanges({id: nullChange}); + expect(getCommentSpy).not.toHaveBeenCalled(); }); }); - describe('change detection node', () => { - const change = new SimpleChange('123', '456', true); - const nullChange = new SimpleChange('123', null, true); + describe('Add comment', () => { beforeEach(() => { - component.nodeId = '123'; - fixture.detectChanges(); - }); - - 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(() => { - component.taskId = '123'; + component.id = '123'; fixture.detectChanges(); fixture.whenStable(); }); @@ -225,18 +174,18 @@ describe('CommentsComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - expect(addProcessCommentSpy).toHaveBeenCalledWith('123', 'action'); + expect(addCommentSpy).toHaveBeenCalledWith('123', 'action'); }); it('should normalize comment when user input contains spaces sequence', async () => { const element = fixture.nativeElement.querySelector('.adf-comments-input-add'); - component.message = 'test comment'; + component.message = 'test comment'; element.dispatchEvent(new Event('click')); fixture.detectChanges(); await fixture.whenStable(); - expect(addProcessCommentSpy).toHaveBeenCalledWith('123', 'test comment'); + expect(addCommentSpy).toHaveBeenCalledWith('123', 'test comment'); }); it('should add break lines to comment when user input contains new line characters', async () => { @@ -247,18 +196,21 @@ describe('CommentsComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - expect(addProcessCommentSpy).toHaveBeenCalledWith('123', 'these
are
paragraphs'); + expect(addCommentSpy).toHaveBeenCalledWith('123', 'these
are
paragraphs'); }); it('should call service to add a comment when add button is pressed', async () => { const element = fixture.nativeElement.querySelector('.adf-comments-input-add'); + component.message = 'Test Comment'; + addCommentSpy.and.returnValue(commentsResponseMock.addComment(component.message)); + element.dispatchEvent(new Event('click')); fixture.detectChanges(); await fixture.whenStable(); - expect(addProcessCommentSpy).toHaveBeenCalled(); + expect(addCommentSpy).toHaveBeenCalled(); const elements = fixture.nativeElement.querySelectorAll('.adf-comment-message'); expect(elements.length).toBe(1); expect(elements[0].innerText).toBe('Test Comment'); @@ -272,7 +224,7 @@ describe('CommentsComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - expect(addProcessCommentSpy).not.toHaveBeenCalled(); + expect(addCommentSpy).not.toHaveBeenCalled(); }); it('should clear comment when escape key is pressed', async () => { @@ -289,97 +241,34 @@ describe('CommentsComponent', () => { it('should emit an error when an error occurs adding the comment', () => { const emitSpy = spyOn(component.error, 'emit'); - addProcessCommentSpy.and.returnValue(throwError({})); + addCommentSpy.and.returnValue(throwError({})); component.message = 'Test comment'; - component.add(); + component.addComment(); expect(emitSpy).toHaveBeenCalled(); }); - }); - describe('Add comment node', () => { - - beforeEach(() => { - component.nodeId = '123'; - fixture.detectChanges(); - fixture.whenStable(); + it('should set beingAdded variable back to false when an error occurs adding the comment', () => { + addCommentSpy.and.returnValue(throwError({})); + component.addComment(); + expect(component.beingAdded).toBeFalse(); }); - it('should call service to add a comment when add button is pressed', async () => { - const element = fixture.nativeElement.querySelector('.adf-comments-input-add'); - component.message = 'Test Comment'; - element.dispatchEvent(new Event('click')); - - fixture.detectChanges(); - await fixture.whenStable(); - - expect(addContentCommentSpy).toHaveBeenCalled(); - const elements = fixture.nativeElement.querySelectorAll('.adf-comment-message'); - expect(elements.length).toBe(1); - expect(elements[0].innerText).toBe('Test Comment'); + it('should set beingAdded variable back to false on successful response when adding the comment', () => { + addCommentSpy.and.returnValue(commentsResponseMock.addComment()); + component.addComment(); + expect(component.beingAdded).toBeFalse(); }); - it('should sanitize comment when user input contains html elements', async () => { - const element = fixture.nativeElement.querySelector('.adf-comments-input-add'); - component.message = '
'; - element.dispatchEvent(new Event('click')); - - fixture.detectChanges(); - await fixture.whenStable(); - - expect(addContentCommentSpy).toHaveBeenCalledWith('123', 'action'); + it('should not add comment if id is not provided', () => { + component.id = ''; + component.addComment(); + expect(addCommentSpy).not.toHaveBeenCalled(); }); - it('should normalize comment when user input contains spaces sequence', async () => { - const element = fixture.nativeElement.querySelector('.adf-comments-input-add'); - component.message = 'test comment'; - element.dispatchEvent(new Event('click')); - - fixture.detectChanges(); - await fixture.whenStable(); - - expect(addContentCommentSpy).toHaveBeenCalledWith('123', 'test comment'); - }); - - it('should add break lines to comment when user input contains new line characters', async () => { - const element = fixture.nativeElement.querySelector('.adf-comments-input-add'); - component.message = 'these\nare\nparagraphs\n'; - element.dispatchEvent(new Event('click')); - - fixture.detectChanges(); - await fixture.whenStable(); - - expect(addContentCommentSpy).toHaveBeenCalledWith('123', 'these
are
paragraphs'); - }); - - it('should not call service to add a comment when comment is empty', async () => { - const element = fixture.nativeElement.querySelector('.adf-comments-input-add'); + it('should not add comment if message is empty', () => { component.message = ''; - element.dispatchEvent(new Event('click')); - - fixture.detectChanges(); - await fixture.whenStable(); - - expect(addContentCommentSpy).not.toHaveBeenCalled(); - }); - - it('should clear comment when escape key is pressed', async () => { - const event = new KeyboardEvent('keydown', {key: 'Escape'}); - let element = fixture.nativeElement.querySelector('#comment-input'); - element.dispatchEvent(event); - - fixture.detectChanges(); - await fixture.whenStable(); - - element = fixture.nativeElement.querySelector('#comment-input'); - expect(element.value).toBe(''); - }); - - it('should emit an error when an error occurs adding the comment', () => { - const emitSpy = spyOn(component.error, 'emit'); - addContentCommentSpy.and.returnValue(throwError({})); - component.message = 'Test comment'; - component.add(); - expect(emitSpy).toHaveBeenCalled(); + component.addComment(); + expect(addCommentSpy).not.toHaveBeenCalled(); }); }); }); diff --git a/lib/core/src/lib/comments/comments.component.stories.ts b/lib/core/src/lib/comments/comments.component.stories.ts index 91a7f4b9e6..7c8fac9745 100644 --- a/lib/core/src/lib/comments/comments.component.stories.ts +++ b/lib/core/src/lib/comments/comments.component.stories.ts @@ -16,13 +16,13 @@ */ import { Meta, moduleMetadata, Story } from '@storybook/angular'; -import { CommentContentService, CommentProcessService, EcmUserService } from '../services'; +import { EcmUserService } from '../services'; import { CoreStoryModule } from '../testing/core.story.module'; import { CommentsComponent } from './comments.component'; import { CommentsModule } from './comments.module'; -import { CommentContentServiceMock } from '../mock/comment-content-service.mock'; -import { CommentProcessServiceMock } from '../mock/comment-process-service.mock'; -import { commentsTaskData, commentsNodeData } from '../mock/comment-content.mock'; +import { ADF_COMMENTS_SERVICE } from './interfaces/comments.token'; +import { commentsStoriesData } from './mocks/comments.stories.mock'; +import { CommentsServiceStoriesMock } from './mocks/comments.service.stories.mock'; export default { component: CommentsComponent, @@ -31,17 +31,16 @@ export default { moduleMetadata({ imports: [CoreStoryModule, CommentsModule], providers: [ - { provide: CommentContentService, useClass: CommentContentServiceMock }, - { provide: CommentProcessService, useClass: CommentProcessServiceMock }, - { provide: EcmUserService, useValue: { getUserProfileImage: () => '../assets/images/logo.png' } } + { provide: EcmUserService, useValue: { getUserProfileImage: () => '../assets/images/logo.png' } }, + { provide: ADF_COMMENTS_SERVICE, useClass: CommentsServiceStoriesMock } ] }) ], parameters: { docs: { description: { - component: `Displays comments from users involved in a specified task or node. - Allows an involved user to add a comment to a task or a node.` + component: `Displays comments from users involved in a specified environment. + Allows an involved user to add a comment to a environment.` } } }, @@ -60,21 +59,12 @@ export default { defaultValue: { summary: 'false' } } }, - nodeId: { + id: { control: 'text', - description: 'Necessary in order to add a new Node comment', + description: 'Necessary in order to add a new comment', table: { type: { summary: 'string' } - }, - if: { arg: 'taskId', exists: false } - }, - taskId: { - control: 'text', - description: 'Necessary in order to add a new Task comment', - table: { - type: { summary: 'string' } - }, - if: { arg: 'nodeId', exists: false } + } }, error: { action: 'error', @@ -93,13 +83,13 @@ const template: Story = (args: CommentsComponent) => ({ export const singleCommentWithAvatar = template.bind({}); singleCommentWithAvatar.args = { - comments: [commentsNodeData[0]], + comments: [commentsStoriesData[0]], readOnly: true }; export const singleCommentWithoutAvatar = template.bind({}); singleCommentWithoutAvatar.args = { - comments: [commentsTaskData[1]], + comments: [commentsStoriesData[1]], readOnly: true }; @@ -109,14 +99,9 @@ noComments.args = { readOnly: true }; -export const nodeComments = template.bind({}); -nodeComments.args = { - comments: commentsNodeData, - nodeId: '-fake-' +export const comments = template.bind({}); +comments.args = { + comments: commentsStoriesData, + id: '-fake-' }; -export const taskComments = template.bind({}); -taskComments.args = { - comments: commentsTaskData, - taskId: '-fake-' -}; diff --git a/lib/core/src/lib/comments/comments.component.ts b/lib/core/src/lib/comments/comments.component.ts index 3cbaf6eb11..2edf3727f7 100644 --- a/lib/core/src/lib/comments/comments.component.ts +++ b/lib/core/src/lib/comments/comments.component.ts @@ -15,12 +15,21 @@ * 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, ViewEncapsulation } from '@angular/core'; +import { + Component, + EventEmitter, + Inject, + Input, + OnChanges, + Output, + SimpleChanges, + ViewEncapsulation +} from '@angular/core'; import { Observable, Observer } from 'rxjs'; import { share } from 'rxjs/operators'; +import { ADF_COMMENTS_SERVICE } from './interfaces/comments.token'; +import { CommentsService } from './interfaces/comments-service.interface'; @Component({ selector: 'adf-comments', @@ -32,11 +41,7 @@ export class CommentsComponent implements OnChanges { /** The numeric ID of the task. */ @Input() - taskId: string; - - /** The numeric ID of the node. */ - @Input() - nodeId: string; + id: string; /** Are the comments read only? */ @Input() @@ -46,143 +51,129 @@ export class CommentsComponent implements OnChanges { @Output() error: EventEmitter = new EventEmitter(); - comments: CommentModel [] = []; - - private commentObserver: Observer; - comment$: Observable; + comments: CommentModel[] = []; message: string; beingAdded: boolean = false; - constructor(private commentProcessService: CommentProcessService, - private commentContentService: CommentContentService) { + private commentObserver: Observer; + comment$: Observable; + + constructor(@Inject(ADF_COMMENTS_SERVICE) private commentsService: CommentsService) { this.comment$ = new Observable((observer) => this.commentObserver = observer) - .pipe(share()); + .pipe( + share() + ); + this.comment$.subscribe((comment: CommentModel) => { this.comments.push(comment); }); } - ngOnChanges(changes: SimpleChanges) { - this.taskId = null; - this.nodeId = null; + ngOnChanges(changes: SimpleChanges): void { + this.id = null; - this.taskId = changes['taskId'] ? changes['taskId'].currentValue : null; - this.nodeId = changes['nodeId'] ? changes['nodeId'].currentValue : null; + this.id = changes['id'] ? changes['id'].currentValue : null; - if (this.taskId || this.nodeId) { - this.getComments(); + if (this.id) { + this.loadComments(); } else { this.resetComments(); } } - private getComments(): void { + loadComments() { this.resetComments(); - if (this.isATask()) { - this.commentProcessService.getTaskComments(this.taskId).subscribe( - (comments: CommentModel[]) => { - if (comments && comments instanceof Array) { - comments = comments.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; - }); - comments.forEach((currentComment) => { - this.commentObserver.next(currentComment); - }); - } + if (!this.hasId()) { + return; + } + + this.commentsService.get(this.id).subscribe( + (comments: CommentModel[]) => { + if (!this.isArrayInstance(comments)) { + return; + } + + comments = this.sortedComments(comments); + this.addCommentsToObserver(comments); + + }, + (err) => { + this.error.emit(err); + } + ); + } + + addComment() { + if (!this.canAddComment()) { + return; + } + + const comment: string = this.sanitize(this.message); + + this.beingAdded = true; + + this.commentsService.add(this.id, comment) + .subscribe( + (res: CommentModel) => { + this.addToComments(res); + this.resetMessage(); }, (err) => { this.error.emit(err); - } - ); - } - - if (this.isANode()) { - this.commentContentService.getNodeComments(this.nodeId).subscribe( - (comments: CommentModel[]) => { - if (comments && comments instanceof Array) { - - comments = comments.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; - }); - comments.forEach((comment) => { - this.commentObserver.next(comment); - }); - } }, - (err) => { - this.error.emit(err); + () => { + this.beingAdded = false; } ); - } + } + + clearMessage(event: Event): void { + event.stopPropagation(); + this.resetMessage(); + } + + private addToComments(comment: CommentModel): void { + this.comments.unshift(comment); + } + + private resetMessage(): void { + this.message = ''; + } + + private canAddComment(): boolean { + return this.hasId() && this.message && this.message.trim() && !this.beingAdded; + } + + private hasId(): boolean { + return !!this.id; + } + + private isArrayInstance(entity: any): boolean { + return entity && entity instanceof Array; + } + + private sortedComments(comments: CommentModel[]): CommentModel[] { + return comments.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; + }); + } + + private addCommentsToObserver(comments: CommentModel[]): void { + comments.forEach((currentComment: CommentModel) => { + this.commentObserver.next(currentComment); + }); } private resetComments(): void { this.comments = []; } - add(): void { - if (this.message && this.message.trim() && !this.beingAdded) { - const comment = this.sanitize(this.message); - - this.beingAdded = true; - if (this.isATask()) { - this.commentProcessService.addTaskComment(this.taskId, comment) - .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, comment) - .subscribe( - (res: CommentModel) => { - this.comments.unshift(res); - this.message = ''; - this.beingAdded = false; - - }, - (err) => { - this.error.emit(err); - this.beingAdded = false; - } - ); - } - } - } - - clear(event: Event): void { - event.stopPropagation(); - this.message = ''; - } - - isReadOnly(): boolean { - return this.readOnly; - } - - isATask(): boolean { - return !!this.taskId; - } - - isANode(): boolean { - return !!this.nodeId; - } - private sanitize(input: string): string { return input.replace(/<[^>]+>/g, '') .replace(/^\s+|\s+$|\s+(?=\s)/g, '') diff --git a/lib/core/src/lib/comments/interfaces/comments-service.interface.ts b/lib/core/src/lib/comments/interfaces/comments-service.interface.ts new file mode 100644 index 0000000000..617208f5fa --- /dev/null +++ b/lib/core/src/lib/comments/interfaces/comments-service.interface.ts @@ -0,0 +1,24 @@ +/*! + * @license + * Copyright 2019 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 { Observable } from 'rxjs'; +import { CommentModel } from '../../models/comment.model'; + +export interface CommentsService { + get(id: string): Observable; + add(id: string, message: string): Observable; +} diff --git a/lib/core/src/lib/comments/interfaces/comments.token.ts b/lib/core/src/lib/comments/interfaces/comments.token.ts new file mode 100644 index 0000000000..83e13d60cc --- /dev/null +++ b/lib/core/src/lib/comments/interfaces/comments.token.ts @@ -0,0 +1,20 @@ +/*! + * @license + * Copyright 2019 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 { InjectionToken } from '@angular/core'; + +export const ADF_COMMENTS_SERVICE = new InjectionToken('ADF_COMMENTS_SERVICE'); diff --git a/lib/core/src/lib/comments/interfaces/index.ts b/lib/core/src/lib/comments/interfaces/index.ts new file mode 100644 index 0000000000..a7e30cc675 --- /dev/null +++ b/lib/core/src/lib/comments/interfaces/index.ts @@ -0,0 +1,18 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './public-api'; diff --git a/lib/core/src/lib/comments/interfaces/public-api.ts b/lib/core/src/lib/comments/interfaces/public-api.ts new file mode 100644 index 0000000000..1f14f4903e --- /dev/null +++ b/lib/core/src/lib/comments/interfaces/public-api.ts @@ -0,0 +1,19 @@ +/*! + * @license + * Copyright 2019 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 './comments-service.interface'; +export * from './comments.token'; diff --git a/lib/core/src/lib/comments/mocks/comments.service.mock.ts b/lib/core/src/lib/comments/mocks/comments.service.mock.ts new file mode 100644 index 0000000000..5704f48412 --- /dev/null +++ b/lib/core/src/lib/comments/mocks/comments.service.mock.ts @@ -0,0 +1,145 @@ +/*! + * @license + * Copyright 2019 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 { CommentModel, EcmUserModel } from '../../models'; +import { Observable, of } from 'rxjs'; +import { CommentsService } from '../interfaces'; + +export class CommentsServiceMock implements CommentsService { + + constructor() {} + + get(_id: string): Observable { + return commentsResponseMock.getComments(); + } + add(_id: string): Observable { + return commentsResponseMock.addComment(); + } +} + +export const commentsResponseMock = { + getComments: () => of([ + { + id: 1, + message: 'Test Comment', + created: new Date(), + createdBy: { + enabled: true, + firstName: 'hruser', + displayName: 'hruser', + quota: -1, + quotaUsed: 12, + emailNotificationsEnabled: true, + company: { + organization: 'test', + address1: 'test', + address2: 'test', + address3: 'test', + postcode: 'test', + telephone: 'test', + fax: 'test', + email: 'test' + }, + id: 'hruser', + email: 'test', + isAdmin: () => false + } as EcmUserModel, + isSelected: false + } as CommentModel, + { + id: 2, + message: 'Test Comment', + created: new Date(), + createdBy: { + enabled: true, + firstName: 'hruser', + displayName: 'hruser', + quota: -1, + quotaUsed: 12, + emailNotificationsEnabled: true, + company: { + organization: 'test', + address1: 'test', + address2: 'test', + address3: 'test', + postcode: 'test', + telephone: 'test', + fax: 'test', + email: 'test' + }, + id: 'hruser', + email: 'test', + isAdmin: () => false + } as EcmUserModel, + isSelected: false + } as CommentModel, + { + id: 3, + message: 'Test Comment', + created: new Date(), + createdBy: { + enabled: true, + firstName: 'hruser', + displayName: 'hruser', + quota: -1, + quotaUsed: 12, + emailNotificationsEnabled: true, + company: { + organization: 'test', + address1: 'test', + address2: 'test', + address3: 'test', + postcode: 'test', + telephone: 'test', + fax: 'test', + email: 'test' + }, + id: 'hruser', + email: 'test', + isAdmin: () => false + } as EcmUserModel, + isSelected: false + } as CommentModel + ]), + addComment: (message = 'test comment') => of({ + id: 1, + message, + created: new Date(), + createdBy: { + enabled: true, + firstName: 'hruser', + displayName: 'hruser', + quota: -1, + quotaUsed: 12, + emailNotificationsEnabled: true, + company: { + organization: 'test', + address1: 'test', + address2: 'test', + address3: 'test', + postcode: 'test', + telephone: 'test', + fax: 'test', + email: 'test' + }, + id: 'hruser', + email: 'test', + isAdmin: () => false + } as EcmUserModel, + isSelected: false + } as CommentModel) +}; diff --git a/lib/core/src/lib/comments/mocks/comments.service.stories.mock.ts b/lib/core/src/lib/comments/mocks/comments.service.stories.mock.ts new file mode 100644 index 0000000000..26aa8d1878 --- /dev/null +++ b/lib/core/src/lib/comments/mocks/comments.service.stories.mock.ts @@ -0,0 +1,126 @@ +/*! + * @license + * Copyright 2019 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 { CommentModel, EcmUserModel } from '../../models'; +import { Observable, of } from 'rxjs'; +import { CommentsService } from '../interfaces'; +import { testUser } from './comments.stories.mock'; + +export class CommentsServiceStoriesMock implements CommentsService { + + constructor() {} + + get(_id: string): Observable { + return commentsResponseMock.getComments(); + } + add(_id: string, message = 'test comment'): Observable { + return commentsResponseMock.addComment(message); + } +} + +export const commentsResponseMock = { + getComments: () => of([ + { + id: 1, + message: 'Test Comment', + created: new Date(), + createdBy: { + enabled: true, + firstName: 'hruser', + displayName: 'hruser', + quota: -1, + quotaUsed: 12, + emailNotificationsEnabled: true, + company: { + organization: 'test', + address1: 'test', + address2: 'test', + address3: 'test', + postcode: 'test', + telephone: 'test', + fax: 'test', + email: 'test' + }, + id: 'hruser', + email: 'test', + isAdmin: () => false + } as EcmUserModel, + isSelected: false + } as CommentModel, + { + id: 2, + message: 'Test Comment', + created: new Date(), + createdBy: { + enabled: true, + firstName: 'hruser', + displayName: 'hruser', + quota: -1, + quotaUsed: 12, + emailNotificationsEnabled: true, + company: { + organization: 'test', + address1: 'test', + address2: 'test', + address3: 'test', + postcode: 'test', + telephone: 'test', + fax: 'test', + email: 'test' + }, + id: 'hruser', + email: 'test', + isAdmin: () => false + } as EcmUserModel, + isSelected: false + } as CommentModel, + { + id: 3, + message: 'Test Comment', + created: new Date(), + createdBy: { + enabled: true, + firstName: 'hruser', + displayName: 'hruser', + quota: -1, + quotaUsed: 12, + emailNotificationsEnabled: true, + company: { + organization: 'test', + address1: 'test', + address2: 'test', + address3: 'test', + postcode: 'test', + telephone: 'test', + fax: 'test', + email: 'test' + }, + id: 'hruser', + email: 'test', + isAdmin: () => false + } as EcmUserModel, + isSelected: false + } as CommentModel + ]), + addComment: (message: string) => of({ + id: 1, + message, + created: new Date(), + createdBy: testUser, + isSelected: false + } as CommentModel) +}; diff --git a/lib/core/src/lib/comments/mocks/comments.stories.mock.ts b/lib/core/src/lib/comments/mocks/comments.stories.mock.ts new file mode 100644 index 0000000000..279ea3dc57 --- /dev/null +++ b/lib/core/src/lib/comments/mocks/comments.stories.mock.ts @@ -0,0 +1,105 @@ +/*! + * @license + * Copyright 2019 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 { CommentModel, EcmCompanyModel, EcmUserModel } from '../../models'; + +const fakeCompany: EcmCompanyModel = { + organization: '', + address1: '', + address2: '', + address3: '', + postcode: '', + telephone: '', + fax: '', + email: '' +}; + +export const getDateXMinutesAgo = (minutes: number) => new Date(new Date().getTime() - minutes * 60000); + +const johnDoe: EcmUserModel = { + id: '1', + email: 'john.doe@alfresco.com', + firstName: 'John', + lastName: 'Doe', + company: fakeCompany, + enabled: true, + isAdmin: undefined, + avatarId: '001' +}; + +const janeEod: EcmUserModel = { + id: '2', + email: 'jane.eod@alfresco.com', + firstName: 'Jane', + lastName: 'Eod', + company: fakeCompany, + enabled: true, + isAdmin: undefined +}; + +const robertSmith: EcmUserModel = { + id: '3', + email: 'robert.smith@alfresco.com', + firstName: 'Robert', + lastName: 'Smith', + company: fakeCompany, + enabled: true, + isAdmin: undefined +}; + +export const testUser: EcmUserModel = { + id: '44', + email: 'test.user@hyland.com', + firstName: 'Test', + lastName: 'User', + company: fakeCompany, + enabled: true, + isAdmin: undefined, + avatarId: '044' +}; + + +export const commentsStoriesData: CommentModel[] = [ + { + id: 1, + message: `I've done this task, what's next?`, + created: getDateXMinutesAgo(30), + createdBy: johnDoe, + isSelected: false + }, + { + id: 2, + message: `I've assigned you another one 🤠`, + created: getDateXMinutesAgo(15), + createdBy: janeEod, + isSelected: false + }, + { + id: 3, + message: '+1', + created: getDateXMinutesAgo(12), + createdBy: robertSmith, + isSelected: false + }, + { + id: 4, + message: 'Cheers', + created: new Date(), + createdBy: johnDoe, + isSelected: false + } +]; diff --git a/lib/core/src/lib/comments/public-api.ts b/lib/core/src/lib/comments/public-api.ts index 9161a88495..f29a0b67e0 100644 --- a/lib/core/src/lib/comments/public-api.ts +++ b/lib/core/src/lib/comments/public-api.ts @@ -18,4 +18,6 @@ export * from './comment-list.component'; export * from './comments.component'; +export * from './interfaces/index'; + export * from './comments.module'; diff --git a/lib/core/src/lib/models/public-api.ts b/lib/core/src/lib/models/public-api.ts index 7f75e92597..feaad21ebd 100644 --- a/lib/core/src/lib/models/public-api.ts +++ b/lib/core/src/lib/models/public-api.ts @@ -38,3 +38,4 @@ export * from './node-metadata.model'; export * from './application-access.model'; export * from './user-access.model'; export * from './general-user.model'; +export * from './comment.model'; diff --git a/lib/process-services/src/lib/process.module.ts b/lib/process-services/src/lib/process.module.ts index 12c6f3bed7..53a5a741f3 100644 --- a/lib/process-services/src/lib/process.module.ts +++ b/lib/process-services/src/lib/process.module.ts @@ -31,6 +31,7 @@ import { PeopleModule } from './people/people.module'; import { FormModule } from './form/form.module'; import { ProcessFormRenderingService } from './form/process-form-rendering.service'; import { ProcessServicesPipeModule } from './pipes/process-services-pipe.module'; +import { TaskCommentsModule } from './task-comments/task-comments.module'; @NgModule({ imports: [ @@ -42,6 +43,7 @@ import { ProcessServicesPipeModule } from './pipes/process-services-pipe.module' MaterialModule, ProcessListModule, TaskListModule, + TaskCommentsModule, AppsListModule, AttachmentModule, PeopleModule, @@ -65,6 +67,7 @@ import { ProcessServicesPipeModule } from './pipes/process-services-pipe.module' ReactiveFormsModule, ProcessListModule, TaskListModule, + TaskCommentsModule, AppsListModule, AttachmentModule, PeopleModule, diff --git a/lib/process-services/src/lib/task-comments/index.ts b/lib/process-services/src/lib/task-comments/index.ts new file mode 100644 index 0000000000..a7e30cc675 --- /dev/null +++ b/lib/process-services/src/lib/task-comments/index.ts @@ -0,0 +1,18 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './public-api'; diff --git a/lib/process-services/src/lib/task-comments/mocks/task-comments.mock.ts b/lib/process-services/src/lib/task-comments/mocks/task-comments.mock.ts new file mode 100644 index 0000000000..b275e4702c --- /dev/null +++ b/lib/process-services/src/lib/task-comments/mocks/task-comments.mock.ts @@ -0,0 +1,32 @@ +/*! + * @license + * Copyright 2019 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 const fakeUser1 = { id: 1, email: 'fake-email@dom.com', firstName: 'firstName', lastName: 'lastName' }; + +export const fakeUser2 = { id: 1001, email: 'some-one@somegroup.com', firstName: 'some', lastName: 'one' }; + +export const fakeTasksComment = { + size: 2, total: 2, start: 0, + data: [ + { + id: 1, message: 'fake-message-1', created: '', createdBy: fakeUser1 + }, + { + id: 2, message: 'fake-message-2', created: '', createdBy: fakeUser1 + } + ] +}; diff --git a/lib/process-services/src/lib/task-comments/public-api.ts b/lib/process-services/src/lib/task-comments/public-api.ts new file mode 100644 index 0000000000..f723c928ce --- /dev/null +++ b/lib/process-services/src/lib/task-comments/public-api.ts @@ -0,0 +1,22 @@ +/*! + * @license + * Copyright 2019 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 './task-comments.component'; + +export * from './services/task-comments.service'; + +export * from './task-comments.module'; diff --git a/lib/process-services/src/lib/task-comments/services/task-comments.service.spec.ts b/lib/process-services/src/lib/task-comments/services/task-comments.service.spec.ts new file mode 100644 index 0000000000..b89495f6f4 --- /dev/null +++ b/lib/process-services/src/lib/task-comments/services/task-comments.service.spec.ts @@ -0,0 +1,94 @@ +/*! + * @license + * Copyright 2019 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, setupTestBed, CoreTestingModule } from '@alfresco/adf-core'; +import { fakeTasksComment, fakeUser1 } from '../mocks/task-comments.mock'; +import { TranslateModule } from '@ngx-translate/core'; +import { TaskCommentsService } from './task-comments.service'; + +declare let jasmine: any; + +describe('TaskCommentsService', () => { + + let service: TaskCommentsService; + + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + CoreTestingModule + ] + }); + + beforeEach(() => { + service = TestBed.inject(TaskCommentsService); + }); + + beforeEach(() => { + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + describe('Task comments', () => { + + it('should add a comment task ', (done) => { + service.add('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({ + id: '111', message: 'fake-comment-message', + createdBy: fakeUser1, + created: '2016-07-15T11:19:17.440+0000' + }) + }); + }); + + it('should return the tasks comments ', (done) => { + service.get('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(fakeTasksComment) + }); + }); + }); +}); diff --git a/lib/process-services/src/lib/task-comments/services/task-comments.service.ts b/lib/process-services/src/lib/task-comments/services/task-comments.service.ts new file mode 100644 index 0000000000..aa1687e7ce --- /dev/null +++ b/lib/process-services/src/lib/task-comments/services/task-comments.service.ts @@ -0,0 +1,107 @@ +/*! + * @license + * Copyright 2019 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 { AlfrescoApiService, CommentModel, CommentsService, UserProcessModel } from '@alfresco/adf-core'; +import { ActivitiCommentsApi, CommentRepresentation } from '@alfresco/js-api'; +import { Injectable } from '@angular/core'; +import { from, Observable, throwError } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class TaskCommentsService implements CommentsService { + + private _commentsApi: ActivitiCommentsApi; + get commentsApi(): ActivitiCommentsApi { + this._commentsApi = this._commentsApi ?? new ActivitiCommentsApi(this.apiService.getInstance()); + return this._commentsApi; + } + + constructor( + private apiService: AlfrescoApiService + ) {} + + /** + * Gets all comments that have been added to a task. + * + * @param id ID of the target task + * @returns Details for each comment + */ + get(id: string): Observable { + return from(this.commentsApi.getTaskComments(id)) + .pipe( + map((response) => { + const comments: CommentModel[] = []; + + response.data.forEach((comment: CommentRepresentation) => { + this.addToComments(comments, comment); + }); + + return comments; + }), + catchError( + (err: any) => this.handleError(err) + ) + ); + } + + /** + * Adds a comment to a task. + * + * @param id ID of the target task + * @param message Text for the comment + * @returns Details about the comment + */ + add(id: string, message: string): Observable { + return from(this.commentsApi.addTaskComment({ message }, id)) + .pipe( + map( + (response: CommentRepresentation) => this.newCommentModel(response) + ), + catchError( + (err: any) => this.handleError(err) + ) + ); + } + + private addToComments(comments: CommentModel[], comment: CommentRepresentation): void { + const user = new UserProcessModel(comment.createdBy); + + const newComment: CommentRepresentation = { + id: comment.id, + message: comment.message, + created: comment.created, + createdBy: user + }; + + comments.push(this.newCommentModel(newComment)); + } + + private newCommentModel(representation: CommentRepresentation): CommentModel { + return new CommentModel({ + id: representation.id, + message: representation.message, + created: representation.created, + createdBy: representation.createdBy + }); + } + + private handleError(error: any) { + return throwError(error || 'Server error'); + } +} diff --git a/lib/process-services/src/lib/task-comments/task-comments.component.html b/lib/process-services/src/lib/task-comments/task-comments.component.html new file mode 100644 index 0000000000..fe2e1d05fd --- /dev/null +++ b/lib/process-services/src/lib/task-comments/task-comments.component.html @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/lib/process-services/src/lib/task-comments/task-comments.component.ts b/lib/process-services/src/lib/task-comments/task-comments.component.ts new file mode 100644 index 0000000000..d2eb095ba7 --- /dev/null +++ b/lib/process-services/src/lib/task-comments/task-comments.component.ts @@ -0,0 +1,31 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, Input, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'adf-task-comments', + templateUrl: './task-comments.component.html', + encapsulation: ViewEncapsulation.None +}) +export class TaskCommentsComponent { + @Input() + taskId: string; + + @Input() + readOnly: boolean; +} diff --git a/lib/process-services/src/lib/task-comments/task-comments.module.ts b/lib/process-services/src/lib/task-comments/task-comments.module.ts new file mode 100644 index 0000000000..f34ccff9a8 --- /dev/null +++ b/lib/process-services/src/lib/task-comments/task-comments.module.ts @@ -0,0 +1,38 @@ +/*! + * @license + * Copyright 2019 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 { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TaskCommentsComponent } from './task-comments.component'; +import { TaskCommentsService } from './services/task-comments.service'; +import { ADF_COMMENTS_SERVICE, CoreModule } from '@alfresco/adf-core'; + +@NgModule({ + imports: [ + CommonModule, + CoreModule + ], + declarations: [TaskCommentsComponent], + exports: [TaskCommentsComponent], + providers: [ + { + provide: ADF_COMMENTS_SERVICE, + useClass: TaskCommentsService + } + ] +}) +export class TaskCommentsModule {} diff --git a/lib/process-services/src/lib/task-list/components/task-details.component.html b/lib/process-services/src/lib/task-list/components/task-details.component.html index 9fa2cd0e07..55b3ed899c 100644 --- a/lib/process-services/src/lib/task-list/components/task-details.component.html +++ b/lib/process-services/src/lib/task-list/components/task-details.component.html @@ -82,10 +82,12 @@ - - + + diff --git a/lib/process-services/src/lib/task-list/task-list.module.ts b/lib/process-services/src/lib/task-list/task-list.module.ts index c9ba630ba2..3fb4582edd 100644 --- a/lib/process-services/src/lib/task-list/task-list.module.ts +++ b/lib/process-services/src/lib/task-list/task-list.module.ts @@ -40,6 +40,7 @@ import { AttachFormComponent } from './components/attach-form.component'; import { FormModule } from '../form/form.module'; import { ClaimTaskDirective } from './components/task-form/claim-task.directive'; import { UnclaimTaskDirective } from './components/task-form/unclaim-task.directive'; +import { TaskCommentsModule } from '../task-comments/task-comments.module'; @NgModule({ imports: [ @@ -52,7 +53,8 @@ import { UnclaimTaskDirective } from './components/task-form/unclaim-task.direct CoreModule, PeopleModule, ProcessCommentsModule, - ContentWidgetModule + ContentWidgetModule, + TaskCommentsModule ], declarations: [ NoTaskDetailsTemplateDirective, diff --git a/lib/process-services/src/public-api.ts b/lib/process-services/src/public-api.ts index b77b107f8c..689dbd62ce 100644 --- a/lib/process-services/src/public-api.ts +++ b/lib/process-services/src/public-api.ts @@ -23,6 +23,7 @@ export * from './lib/process-comments/index'; export * from './lib/people/index'; export * from './lib/content-widget/index'; export * from './lib/form/index'; +export * from './lib/task-comments/index'; export * from './lib/pipes/process-name.pipe'; export * from './lib/pipes/process-services-pipe.module';