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

feat: add resolve annotations functionality #308

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
7 changes: 7 additions & 0 deletions __mocks__/comments.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,12 @@ export const MOCK_ANNOTATION = {
text: 'any_text',
createdAt: new Date().toISOString(),
},
{
uuid: 'any_uuid 2',
username: 'any_username 2',
avatar: 'any_avatar 2',
text: 'any_text 2',
createdAt: new Date().toISOString(),
},
],
};
104 changes: 81 additions & 23 deletions src/components/comments/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,40 @@ import { MOCK_CONFIG } from '../../../__mocks__/config.mock';
import { EVENT_BUS_MOCK } from '../../../__mocks__/event-bus.mock';
import { MOCK_GROUP, MOCK_LOCAL_PARTICIPANT } from '../../../__mocks__/participants.mock';
import { ABLY_REALTIME_MOCK } from '../../../__mocks__/realtime.mock';
import ApiService from '../../services/api';

import { CommentsComponent } from './index';

jest.mock('../../services/api', () => ({
fetchAnnotation: jest.fn().mockImplementation((): any => []),
createAnnotations: jest.fn().mockImplementation(() => []),
createComment: jest.fn().mockImplementation(() => []),
resolveAnnotation: jest.fn().mockImplementation(() => []),
}));

describe('CommentsComponent', () => {
let commentsComponent: CommentsComponent;

beforeEach(() => {
jest.clearAllMocks();
commentsComponent = new CommentsComponent();

commentsComponent.attach({
realtime: ABLY_REALTIME_MOCK,
localParticipant: MOCK_LOCAL_PARTICIPANT,
group: MOCK_GROUP,
config: {
...MOCK_CONFIG,
apiUrl: 'https://dev.nodeapi.superviz.com',
},
eventBus: EVENT_BUS_MOCK,
});

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

afterEach(() => {
commentsComponent.detach();
});

it('should create a new instance of CommentsComponent', () => {
Expand All @@ -25,45 +51,77 @@ describe('CommentsComponent', () => {
});

it('should have an element property', () => {
expect(commentsComponent['element']).toBeUndefined();
expect(commentsComponent['element']).toBeDefined();
});

it('should create a new element when start() is called', () => {
commentsComponent.attach({
realtime: ABLY_REALTIME_MOCK,
localParticipant: MOCK_LOCAL_PARTICIPANT,
group: MOCK_GROUP,
config: MOCK_CONFIG,
eventBus: EVENT_BUS_MOCK,
});
commentsComponent.detach();
expect(commentsComponent['element']).toBeUndefined();
});

it('should add the element to the document body when start() is called', () => {
commentsComponent.attach({
realtime: ABLY_REALTIME_MOCK,
localParticipant: MOCK_LOCAL_PARTICIPANT,
group: MOCK_GROUP,
config: MOCK_CONFIG,
eventBus: EVENT_BUS_MOCK,
});

expect(document.body.contains(commentsComponent['element'])).toBe(true);
});

it('should remove the element from the document body when destroy() is called', async () => {
commentsComponent.attach({
realtime: ABLY_REALTIME_MOCK,
localParticipant: MOCK_LOCAL_PARTICIPANT,
group: MOCK_GROUP,
config: MOCK_CONFIG,
eventBus: EVENT_BUS_MOCK,
});
expect(commentsComponent['element']).toBeDefined();

commentsComponent.detach();

expect(document.body.contains(commentsComponent['element'])).toBe(false);
});

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

expect(result).toHaveBeenCalledWith(
expect.any(String),
expect.any(String),
{
roomId: expect.any(String),
url: expect.any(String),
},
);
});

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

commentsComponent['element'].dispatchEvent(new CustomEvent('create-annotation', {
detail: {
text: 'test',
position: {
x: 0,
y: 0,
},
},
}));

expect(result).toHaveBeenCalledWith(
expect.any(String),
expect.any(String),
{
roomId: expect.any(String),
url: expect.any(String),
userId: expect.any(String),
position: expect.any(String),
},
);
});

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

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

expect(result).toHaveBeenCalledWith(
expect.any(String),
expect.any(String),
'test',
);
});
});
75 changes: 54 additions & 21 deletions src/components/comments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class CommentsComponent extends BaseComponent {
*/
private addListeners(): void {
this.element.addEventListener('create-annotation', this.createAnnotation);
this.element.addEventListener('resolve-annotation', this.resolveAnnotation);
}

/**
Expand All @@ -63,6 +64,7 @@ export class CommentsComponent extends BaseComponent {
*/
private destroyListeners(): void {
this.element.removeEventListener('create-annotation', this.createAnnotation);
this.element.removeEventListener('resolve-annotation', this.createAnnotation);
}

/**
Expand All @@ -72,22 +74,27 @@ export class CommentsComponent extends BaseComponent {
* @returns {Promise<void>}
*/
private createAnnotation = async (e: CustomEvent): Promise<void> => {
const { text, position } = e.detail;
const { url } = this;

const annotation: Annotation = await ApiService.createAnnotations(config.get<string>('apiUrl'), config.get<string>('apiKey'), {
roomId: config.get<string>('roomId'),
position: JSON.stringify(position),
url,
userId: this.localParticipant.id,
});

const comment = await this.createComment(annotation.uuid, text);

this.addAnnotation([{
...annotation,
comments: [comment],
}]);
try {
const { text, position } = e.detail;
const { url } = this;

const annotation: Annotation = await ApiService.createAnnotations(config.get<string>('apiUrl'), config.get<string>('apiKey'), {
roomId: config.get<string>('roomId'),
position: JSON.stringify(position),
url,
userId: this.localParticipant.id,
});

const comment = await this.createComment(annotation.uuid, text);

this.addAnnotation([{
...annotation,
comments: [comment],
}]);
} catch (error) {
this.logger.log('error when creating annotation', error);
throw error;
}
};

/**
Expand All @@ -98,11 +105,16 @@ export class CommentsComponent extends BaseComponent {
* @returns {Promise<Comment>} - A promise that resolves with the created comment object
*/
private async createComment(annotationId: string, text: string): Promise<Comment> {
return ApiService.createComment(config.get<string>('apiUrl'), config.get<string>('apiKey'), {
annotationId,
userId: this.localParticipant.id,
text,
});
try {
return await ApiService.createComment(config.get<string>('apiUrl'), config.get<string>('apiKey'), {
annotationId,
userId: this.localParticipant.id,
text,
});
} catch (error) {
this.logger.log('error when creating comment', error);
throw error;
}
}

/**
Expand Down Expand Up @@ -135,6 +147,27 @@ export class CommentsComponent extends BaseComponent {
this.addAnnotation(annotations);
} catch (error) {
this.logger.log('error when fetching annotations', error);
throw error;
}
}

/**
* @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
* @returns {Promise<void>}
*/
private async resolveAnnotation(e: CustomEvent): Promise<void> {
try {
const { uuid } = e.detail;
await ApiService.resolveAnnotation(
config.get('apiUrl'),
config.get('apiKey'),
uuid,
);
} catch (error) {
this.logger.log('error when fetching annotations', error);
throw error;
}
}
}
3 changes: 3 additions & 0 deletions src/components/comments/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ export type Comment = {
avatar: string;
text: string;
createdAt: string;

resolvable?: boolean;
resolved?: boolean;
};
15 changes: 15 additions & 0 deletions src/services/api/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ jest.mock('../../common/utils', () => {
if (url.includes('/comments') && method === 'POST') {
return Promise.resolve({});
}

if (url.includes('/annotations/resolve/any_annotation_id') && method === 'POST') {
return Promise.resolve({});
}
}),
};
});
Expand Down Expand Up @@ -128,5 +132,16 @@ describe('ApiService', () => {

expect(response).toEqual([]);
});

test('should resolve an annotation', async () => {
const baseUrl = 'https://dev.nodeapi.superviz.com';
const response = await ApiService.resolveAnnotation(
baseUrl,
VALID_API_KEY,
'any_annotation_id',
);

expect(response).toEqual({});
});
});
});
7 changes: 7 additions & 0 deletions src/services/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,11 @@ export default class ApiService {
});
return doRequest(url, 'GET', undefined, { apikey: apiKey });
}

static async resolveAnnotation(baseUrl: string, apiKey: string, annotationId: string) {
const path = `/annotations/resolve/${annotationId}`;
const url = this.createUrl(baseUrl, path);

return doRequest(url, 'POST', {}, { apikey: apiKey });
}
}
2 changes: 1 addition & 1 deletion src/web-components/base/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LitElement, css } from 'lit';
import { LitElement } from 'lit';

import { variableStyle, typography, svHr } from './styles';
import { Constructor, WebComponentsBaseInterface } from './types';
Expand Down
4 changes: 4 additions & 0 deletions src/web-components/comments/comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ export class Comments extends WebComponentsBaseElement {
];
}

updateAnnotations(data: Annotation[]) {
this.annotations = data;
}

toggle() {
this.open = !this.open;
}
Expand Down
Loading
Loading