Skip to content

Commit aefef0b

Browse files
feat(api): Add initial annotations api module and factory (#2089)
* feat(api): Add initial annotations api module and factory * feat(api): Update file_version type property value Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 13a5519 commit aefef0b

File tree

8 files changed

+345
-44
lines changed

8 files changed

+345
-44
lines changed

src/api/APIFactory.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import MetadataAPI from './Metadata';
2323
import FileCollaboratorsAPI from './FileCollaborators';
2424
import FeedAPI from './Feed';
2525
import AppIntegrationsAPI from './AppIntegrations';
26+
import AnnotationsAPI from './Annotations';
2627
import OpenWithAPI from './OpenWith';
2728
import MetadataQueryAPI from './MetadataQuery';
2829
import BoxEditAPI from './box-edit';
@@ -144,6 +145,11 @@ class APIFactory {
144145
*/
145146
boxEditAPI: BoxEditAPI;
146147

148+
/**
149+
* @property {AnnotationsAPI}
150+
*/
151+
annotationsAPI: AnnotationsAPI;
152+
147153
/**
148154
* [constructor]
149155
*
@@ -268,6 +274,11 @@ class APIFactory {
268274
delete this.openWithAPI;
269275
}
270276

277+
if (this.annotationsAPI) {
278+
this.annotationsAPI.destroy();
279+
delete this.annotationsAPI;
280+
}
281+
271282
if (destroyCache) {
272283
this.options.cache = new Cache();
273284
}
@@ -592,6 +603,20 @@ class APIFactory {
592603
this.boxEditAPI = new BoxEditAPI();
593604
return this.boxEditAPI;
594605
}
606+
607+
/**
608+
* API for Annotations
609+
*
610+
* @return {AnnotationsAPI} AnnotationsAPI instance
611+
*/
612+
getAnnotationsAPI(shouldDestroy: boolean): AnnotationsAPI {
613+
if (shouldDestroy) {
614+
this.destroy();
615+
}
616+
617+
this.annotationsAPI = new AnnotationsAPI(this.options);
618+
return this.annotationsAPI;
619+
}
595620
}
596621

597622
export default APIFactory;

src/api/Annotations.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// @flow
2+
import merge from 'lodash/merge';
3+
import MarkerBasedApi from './MarkerBasedAPI';
4+
import type { Annotation, NewAnnotation } from '../common/types/annotations';
5+
import type { ElementsXhrError } from '../common/types/api';
6+
7+
export default class Annotations extends MarkerBasedApi {
8+
getUrl() {
9+
return `${this.getBaseApiUrl()}/internal_annotations`;
10+
}
11+
12+
getUrlForId(annotationId: string) {
13+
return `${this.getUrl()}/${annotationId}`;
14+
}
15+
16+
createAnnotation(
17+
fileId: string,
18+
fileVersionId: string,
19+
payload: NewAnnotation,
20+
successCallback: (annotation: Annotation) => void,
21+
errorCallback: (e: ElementsXhrError, code: string) => void,
22+
): Promise<void> {
23+
const defaults = {
24+
description: {
25+
type: 'reply',
26+
},
27+
file_version: {
28+
id: fileVersionId,
29+
type: 'file_version',
30+
},
31+
status: 'open',
32+
type: 'annotation',
33+
};
34+
35+
return this.post({
36+
id: fileId,
37+
data: {
38+
data: merge(defaults, payload),
39+
},
40+
errorCallback,
41+
successCallback,
42+
url: this.getUrl(),
43+
});
44+
}
45+
46+
deleteAnnotation(
47+
fileId: string,
48+
annotationId: string,
49+
successCallback: () => void,
50+
errorCallback: (e: ElementsXhrError, code: string) => void,
51+
): Promise<void> {
52+
return this.delete({
53+
id: fileId,
54+
errorCallback,
55+
successCallback,
56+
url: this.getUrlForId(annotationId),
57+
});
58+
}
59+
60+
getAnnotation(
61+
fileId: string,
62+
annotationId: string,
63+
successCallback: (annotation: Annotation) => void,
64+
errorCallback: (e: ElementsXhrError, code: string) => void,
65+
): Promise<void> {
66+
return this.get({
67+
id: fileId,
68+
errorCallback,
69+
successCallback,
70+
url: this.getUrlForId(annotationId),
71+
});
72+
}
73+
74+
getAnnotations(
75+
fileId: string,
76+
fileVersionId?: string,
77+
successCallback: (annotations: Annotation[]) => void,
78+
errorCallback: (e: ElementsXhrError, code: string) => void,
79+
limit?: number,
80+
shouldFetchAll?: boolean,
81+
): Promise<void> {
82+
return this.markerGet({
83+
id: fileId,
84+
errorCallback,
85+
limit,
86+
requestData: {
87+
file_id: fileId,
88+
file_version_id: fileVersionId,
89+
},
90+
shouldFetchAll,
91+
successCallback,
92+
});
93+
}
94+
}

src/api/__tests__/APIFactory.test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Cache from '../../utils/Cache';
2+
import AnnotationsAPI from '../Annotations';
23
import APIFactory from '../APIFactory';
34
import ChunkedUploadAPI from '../uploads/MultiputUpload';
45
import PlainUploadAPI from '../uploads/PlainUpload';
@@ -50,6 +51,7 @@ describe('api/APIFactory', () => {
5051
factory.taskCollaboratorsAPI = { destroy: jest.fn() };
5152
factory.boxEditAPI = { destroy: jest.fn() };
5253
factory.metadataQueryAPI = { destroy: jest.fn() };
54+
factory.annotationsAPI = { destroy: jest.fn() };
5355
factory.destroy();
5456
expect(factory.fileAPI).toBeUndefined();
5557
expect(factory.folderAPI).toBeUndefined();
@@ -65,6 +67,7 @@ describe('api/APIFactory', () => {
6567
expect(factory.tasksNewAPI).toBeUndefined();
6668
expect(factory.usersAPI).toBeUndefined();
6769
expect(factory.metadataQueryAPI).toBeUndefined();
70+
expect(factory.annotationsAPI).toBeUndefined();
6871
});
6972
test('should not destroy cache by default', () => {
7073
const { cache } = factory.options;
@@ -407,4 +410,26 @@ describe('api/APIFactory', () => {
407410
expect(metadataQueryAPI.options.uploadHost).toBe(DEFAULT_HOSTNAME_UPLOAD);
408411
});
409412
});
413+
414+
describe('getAnnotationsAPI', () => {
415+
test('should call destroy and return annotations API', () => {
416+
const spy = jest.spyOn(factory, 'destroy');
417+
const annotationsAPI = factory.getAnnotationsAPI(true);
418+
expect(spy).toBeCalled();
419+
expect(annotationsAPI).toBeInstanceOf(AnnotationsAPI);
420+
expect(annotationsAPI.options.cache).toBeInstanceOf(Cache);
421+
expect(annotationsAPI.options.apiHost).toBe(DEFAULT_HOSTNAME_API);
422+
expect(annotationsAPI.options.uploadHost).toBe(DEFAULT_HOSTNAME_UPLOAD);
423+
});
424+
425+
test('should not call destroy and return annotations API', () => {
426+
const spy = jest.spyOn(factory, 'destroy');
427+
const annotationsAPI = factory.getAnnotationsAPI();
428+
expect(spy).not.toHaveBeenCalled();
429+
expect(annotationsAPI).toBeInstanceOf(AnnotationsAPI);
430+
expect(annotationsAPI.options.cache).toBeInstanceOf(Cache);
431+
expect(annotationsAPI.options.apiHost).toBe(DEFAULT_HOSTNAME_API);
432+
expect(annotationsAPI.options.uploadHost).toBe(DEFAULT_HOSTNAME_UPLOAD);
433+
});
434+
});
410435
});
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import Annotations from '../Annotations';
2+
3+
describe('api/Annotations', () => {
4+
let annotations;
5+
6+
beforeEach(() => {
7+
annotations = new Annotations({});
8+
annotations.delete = jest.fn();
9+
annotations.get = jest.fn();
10+
annotations.markerGet = jest.fn();
11+
annotations.post = jest.fn();
12+
annotations.put = jest.fn();
13+
});
14+
15+
afterEach(() => {
16+
annotations.destroy();
17+
annotations = null;
18+
});
19+
20+
describe('getUrl()', () => {
21+
test('should the return correct url for annotations', () => {
22+
expect(annotations.getUrl()).toBe('https://api.box.com/2.0/internal_annotations');
23+
});
24+
});
25+
26+
describe('getUrlForId()', () => {
27+
test('should return the correct url for a given annotation id', () => {
28+
expect(annotations.getUrlForId('test')).toBe('https://api.box.com/2.0/internal_annotations/test');
29+
});
30+
});
31+
32+
describe('createAnnotation()', () => {
33+
test('should format its parameters and call the post method', () => {
34+
const payload = {
35+
description: {
36+
message: 'This is a test message.',
37+
},
38+
target: {
39+
location: {
40+
type: 'page',
41+
value: 1,
42+
},
43+
shape: {
44+
height: 50,
45+
type: 'rect',
46+
width: 50,
47+
x: 10,
48+
y: 10,
49+
},
50+
type: 'region',
51+
},
52+
};
53+
const errorCallback = jest.fn();
54+
const successCallback = jest.fn();
55+
56+
annotations.createAnnotation('12345', '67890', payload, successCallback, errorCallback);
57+
58+
expect(annotations.post).toBeCalledWith({
59+
id: '12345',
60+
data: {
61+
data: {
62+
description: {
63+
message: 'This is a test message.',
64+
type: 'reply',
65+
},
66+
file_version: {
67+
id: '67890',
68+
type: 'file_version',
69+
},
70+
status: 'open',
71+
target: {
72+
location: {
73+
type: 'page',
74+
value: 1,
75+
},
76+
shape: {
77+
height: 50,
78+
type: 'rect',
79+
width: 50,
80+
x: 10,
81+
y: 10,
82+
},
83+
type: 'region',
84+
},
85+
type: 'annotation',
86+
},
87+
},
88+
errorCallback,
89+
successCallback,
90+
url: 'https://api.box.com/2.0/internal_annotations',
91+
});
92+
});
93+
});
94+
95+
describe('deleteAnnotation()', () => {
96+
test('should format its parameters and call the delete method for a given id', () => {
97+
const errorCallback = jest.fn();
98+
const successCallback = jest.fn();
99+
100+
annotations.deleteAnnotation('12345', 'abc', successCallback, errorCallback);
101+
102+
expect(annotations.delete).toBeCalledWith({
103+
id: '12345',
104+
errorCallback,
105+
successCallback,
106+
url: 'https://api.box.com/2.0/internal_annotations/abc',
107+
});
108+
});
109+
});
110+
111+
describe('getAnnotation()', () => {
112+
test('should format its parameters and call the get method', () => {
113+
const successCallback = jest.fn();
114+
const errorCallback = jest.fn();
115+
116+
annotations.getAnnotation('12345', 'abc', successCallback, errorCallback);
117+
118+
expect(annotations.get).toBeCalledWith({
119+
id: '12345',
120+
errorCallback,
121+
successCallback,
122+
url: 'https://api.box.com/2.0/internal_annotations/abc',
123+
});
124+
});
125+
});
126+
127+
describe('getAnnotations()', () => {
128+
test('should format its parameters and call the underlying markerGet method', () => {
129+
const successCallback = jest.fn();
130+
const errorCallback = jest.fn();
131+
132+
annotations.getAnnotations('12345', '67890', successCallback, errorCallback);
133+
134+
expect(annotations.markerGet).toBeCalledWith({
135+
id: '12345',
136+
errorCallback,
137+
requestData: {
138+
file_id: '12345',
139+
file_version_id: '67890',
140+
},
141+
successCallback,
142+
});
143+
});
144+
});
145+
});

0 commit comments

Comments
 (0)