Skip to content
This repository has been archived by the owner on Oct 18, 2024. It is now read-only.

feat: add comment on annotation #310

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 53 additions & 18 deletions src/components/comments/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,12 @@ describe('CommentsComponent', () => {
realtime: ABLY_REALTIME_MOCK,
localParticipant: MOCK_LOCAL_PARTICIPANT,
group: MOCK_GROUP,
config: {
...MOCK_CONFIG,
apiUrl: 'https://dev.nodeapi.superviz.com',
},
config: MOCK_CONFIG,
eventBus: EVENT_BUS_MOCK,
});

commentsComponent['element'].addAnnotation = jest.fn().mockImplementation(() => []);
commentsComponent['element'].addComment = jest.fn().mockImplementation(() => []);
});

afterEach(() => {
Expand Down Expand Up @@ -72,20 +70,20 @@ describe('CommentsComponent', () => {
});

it('should call apiService when fetch annotation', async () => {
const result = jest.spyOn(ApiService, 'fetchAnnotation');
const spy = jest.spyOn(ApiService, 'fetchAnnotation');

expect(result).toHaveBeenCalledWith(
expect.any(String),
expect.any(String),
expect(spy).toHaveBeenCalledWith(
MOCK_CONFIG.apiUrl,
MOCK_CONFIG.apiKey,
{
roomId: expect.any(String),
roomId: MOCK_CONFIG.roomId,
url: expect.any(String),
},
);
});

it('should call apiService when create annotation', async () => {
const result = jest.spyOn(ApiService, 'createAnnotations');
const spy = jest.spyOn(ApiService, 'createAnnotations');

commentsComponent['element'].dispatchEvent(new CustomEvent('create-annotation', {
detail: {
Expand All @@ -97,11 +95,11 @@ describe('CommentsComponent', () => {
},
}));

expect(result).toHaveBeenCalledWith(
expect.any(String),
expect.any(String),
expect(spy).toHaveBeenCalledWith(
MOCK_CONFIG.apiUrl,
MOCK_CONFIG.apiKey,
{
roomId: expect.any(String),
roomId: MOCK_CONFIG.roomId,
url: expect.any(String),
userId: expect.any(String),
position: expect.any(String),
Expand All @@ -110,18 +108,55 @@ describe('CommentsComponent', () => {
});

it('should call apiService when resolve annotation', async () => {
const result = jest.spyOn(ApiService, 'resolveAnnotation');
const spy = jest.spyOn(ApiService, 'resolveAnnotation');

commentsComponent['element'].dispatchEvent(new CustomEvent('resolve-annotation', {
detail: {
uuid: 'test',
},
}));

expect(spy).toHaveBeenCalledWith(
MOCK_CONFIG.apiUrl,
MOCK_CONFIG.apiKey,
'test',
);
});

it('should call apiService when resolve annotation', async () => {
const spy = jest.spyOn(ApiService, 'resolveAnnotation');

commentsComponent['element'].dispatchEvent(new CustomEvent('resolve-annotation', {
detail: {
uuid: 'test',
},
}));

expect(result).toHaveBeenCalledWith(
expect.any(String),
expect.any(String),
expect(spy).toHaveBeenCalledWith(
MOCK_CONFIG.apiUrl,
MOCK_CONFIG.apiKey,
'test',
);
});

it('should call apiService when create a new comment', async () => {
const spy = jest.spyOn(ApiService, 'createComment');

commentsComponent['element'].dispatchEvent(new CustomEvent('create-comment', {
detail: {
uuid: 'uuid-test',
text: 'text-test',
},
}));

expect(spy).toHaveBeenCalledWith(
MOCK_CONFIG.apiUrl,
MOCK_CONFIG.apiKey,
{
annotationId: 'uuid-test',
userId: expect.any(String),
text: 'text-test',
},
);
});
});
39 changes: 30 additions & 9 deletions src/components/comments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export class CommentsComponent extends BaseComponent {
private addListeners(): void {
this.element.addEventListener('create-annotation', this.createAnnotation);
this.element.addEventListener('resolve-annotation', this.resolveAnnotation);
this.element.addEventListener('create-comment', ({ detail }: CustomEvent) => this.createComment(detail.uuid, detail.text, true));
}

/**
Expand All @@ -65,17 +66,18 @@ export class CommentsComponent extends BaseComponent {
private destroyListeners(): void {
this.element.removeEventListener('create-annotation', this.createAnnotation);
this.element.removeEventListener('resolve-annotation', this.createAnnotation);
this.element.removeEventListener('create-comment', ({ detail }: CustomEvent) => this.createComment(detail.uuid, detail.text, true));
}

/**
* @function createAnnotation
* @description Creates a new annotation and comment and adds them to the Comments component
* @param {CustomEvent} e - The event object containing the annotation text and position
* @param {CustomEvent} event - The event object containing the annotation text and position
* @returns {Promise<void>}
*/
private createAnnotation = async (e: CustomEvent): Promise<void> => {
private createAnnotation = async ({ detail }: CustomEvent): Promise<void> => {
try {
const { text, position } = e.detail;
const { text, position } = detail;
const { url } = this;

const annotation: Annotation = await ApiService.createAnnotations(config.get<string>('apiUrl'), config.get<string>('apiKey'), {
Expand Down Expand Up @@ -104,13 +106,21 @@ export class CommentsComponent extends BaseComponent {
* @param {string} text - The text content of the comment
* @returns {Promise<Comment>} - A promise that resolves with the created comment object
*/
private async createComment(annotationId: string, text: string): Promise<Comment> {
private async createComment(
annotationId: string,
text: string,
addComment = false,
): Promise<Comment> {
try {
return await ApiService.createComment(config.get<string>('apiUrl'), config.get<string>('apiKey'), {
const comment: Comment = await ApiService.createComment(config.get<string>('apiUrl'), config.get<string>('apiKey'), {
annotationId,
userId: this.localParticipant.id,
text,
});

if (addComment) this.addComment(annotationId, comment);

return comment;
} catch (error) {
this.logger.log('error when creating comment', error);
throw error;
Expand All @@ -123,10 +133,21 @@ export class CommentsComponent extends BaseComponent {
* @param {Annotation[]} annotation - An array of annotation objects to add to the component
* @returns {void}
*/
private addAnnotation(annotation: Annotation[]): void {
addAnnotation(annotation: Annotation[]): void {
this.element.addAnnotation(annotation);
}

/**
* @function addComment
* @description Adds a new comment to an annotation in the Comments component
* @param {string} annotationId - The ID of the annotation to add the comment to
* @param {Comment} comment - The comment object to add to the annotation
* @returns {void}
*/
addComment(annotationId: string, comment: Comment): void {
this.element.addComment(annotationId, comment);
}

/**
* @function fetchAnnotations
* @description Fetches annotations from the API and adds them to the Comments component
Expand Down Expand Up @@ -154,12 +175,12 @@ export class CommentsComponent extends BaseComponent {
/**
* @function resolveAnnotation
* @description Resolves an annotation by UUID using the API
* @param {CustomEvent} e - The custom event containing the UUID of the annotation to resolve
* @param {CustomEvent} event - The custom event containing the UUID of the annotation to resolve
* @returns {Promise<void>}
*/
private async resolveAnnotation(e: CustomEvent): Promise<void> {
private async resolveAnnotation({ detail }: CustomEvent): Promise<void> {
try {
const { uuid } = e.detail;
const { uuid } = detail;
await ApiService.resolveAnnotation(
config.get('apiUrl'),
config.get('apiKey'),
Expand Down
22 changes: 21 additions & 1 deletion src/web-components/comments/comments.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CSSResultGroup, LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';

import { Annotation } from '../../components/comments/types';
import { Annotation, Comment } from '../../components/comments/types';
import { WebComponentsBase } from '../base';

import { commentsStyle } from './css';
Expand Down Expand Up @@ -33,6 +33,26 @@ export class Comments extends WebComponentsBaseElement {
];
}

addComment(annotationId: string, comment: Comment) {
const annotationIndex = this.annotations
.findIndex((annotation) => annotation.uuid === annotationId);

if (annotationIndex === -1) return;

const annotation = this.annotations[annotationIndex];

annotation.comments = [
...annotation.comments,
comment,
];

this.annotations = [
...this.annotations.slice(0, annotationIndex),
annotation,
...this.annotations.slice(annotationIndex + 1),
];
}

updateAnnotations(data: Annotation[]) {
this.annotations = data;
}
Expand Down
14 changes: 12 additions & 2 deletions src/web-components/comments/components/annotation-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ export class CommentsAnnotationItem extends WebComponentsBaseElement {
this.emitEvent('select-annotation', { uuid });
};

private createComment({ detail }: CustomEvent) {
const { text } = detail;

this.emitEvent('create-comment', {
uuid: this.annotation.uuid,
text,
});
}

protected render() {
const replies = this.annotation.comments.length;

Expand Down Expand Up @@ -84,9 +93,9 @@ export class CommentsAnnotationItem extends WebComponentsBaseElement {
`;
};

const resolveAnnotation = (e: CustomEvent) => {
const resolveAnnotation = ({ detail }: CustomEvent) => {
const { uuid } = this.annotation;
const { resolved } = e.detail;
const { resolved } = detail;

this.emitEvent('resolve-annotation', {
uuid,
Expand Down Expand Up @@ -119,6 +128,7 @@ export class CommentsAnnotationItem extends WebComponentsBaseElement {
<div class="comments-container ${shouldExpandComments}">
${this.annotation.comments.map(expandedComments)}
<superviz-comments-comment-input
@create-comment=${this.createComment}
eventType="create-comment"
></superviz-comments-comment-input>
</div>
Expand Down
8 changes: 4 additions & 4 deletions src/web-components/comments/components/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export class CommentsAnnotations extends WebComponentsBaseElement {
open: { type: Boolean },
};

private createAnnotation(e: CustomEvent) {
private createAnnotation({ detail }: CustomEvent) {
this.emitEvent('create-annotation', {
position: {},
text: e.detail.text,
text: detail.text,
});
}

Expand All @@ -29,8 +29,8 @@ export class CommentsAnnotations extends WebComponentsBaseElement {
<div class="annotations">
<span class="text text-big text-bold add-comment-btn">Click anywhere to add a comment</span>
<superviz-comments-comment-input
@create-comment=${this.createAnnotation}
eventType="create-comment"
@create-annotation=${this.createAnnotation}
eventType="create-annotation"
>
</superviz-comments-comment-input>
</div>
Expand Down
9 changes: 8 additions & 1 deletion src/web-components/comments/components/comment-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,14 @@ export class CommentsCommentInput extends WebComponentsBaseElement {
const sendBtn = this.getSendBtn();
const text = input.value;

this.emitEvent(this.eventType, { text });
this.emitEvent(
this.eventType,
{ text },
{
composed: false,
bubbles: false,
},
);

input.value = '';
sendBtn.disabled = true;
Expand Down
4 changes: 2 additions & 2 deletions src/web-components/comments/components/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export class CommentsContent extends WebComponentsBaseElement {
return this.annotations.length === index + 1 ? 'hidden' : '';
};

const selectAnnotation = (e: CustomEvent) => {
const { uuid } = e.detail;
const selectAnnotation = ({ detail }: CustomEvent) => {
const { uuid } = detail;
this.selectedAnnotation = uuid;
};

Expand Down
44 changes: 44 additions & 0 deletions src/web-components/comments/tests/comments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,48 @@ describe('comments', () => {

expect(element['annotations']).toEqual([annotationUpdated]);
});

test('should add comment', async () => {
const annotation = {
...MOCK_ANNOTATION,
position: 'any_position',
};

element['addAnnotation']([annotation]);

const comment = {
uuid: 'teste',
username: 'any_username',
avatar: 'any_avatar',
text: 'any_text',
createdAt: new Date().toISOString(),
};

element['addComment'](annotation.uuid, comment);

const lastComment = element['annotations'][0].comments.at(-1);

expect(lastComment).toEqual(comment);
});

test('should return void when annotation is not found', async () => {
const annotation = {
...MOCK_ANNOTATION,
position: 'any_position',
};

element['addAnnotation']([annotation]);

const comment = {
uuid: 'teste',
username: 'any_username',
avatar: 'any_avatar',
text: 'any_text',
createdAt: new Date().toISOString(),
};

element['addComment']('other_annotation_id', comment);

expect(element['annotations'][0].comments.length).toEqual(2);
});
});
Loading
Loading