Skip to content

Commit dfdf503

Browse files
authored
New: Preview Sidebar and Box Skills (#51)
Has subbed out metadata API. Cleanup of API code making function signatures consistent. Adding unit tests for API
1 parent 4fefce1 commit dfdf503

73 files changed

Lines changed: 2423 additions & 406 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.flowconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ module.name_mapper.extension='css' -> '<PROJECT_ROOT>/flow/SCSSFlowStub.js.flow'
2121
module.name_mapper='i18n-locale-data' -> '<PROJECT_ROOT>/flow/WebpackI18N.js.flow'
2222
module.name_mapper='react-virtualized/dist/es/Table' -> '<PROJECT_ROOT>/flow/ReactVirtualizedStub.js.flow'
2323
module.name_mapper='react-virtualized/dist/es/AutoSizer' -> '<PROJECT_ROOT>/flow/ReactVirtualizedStub.js.flow'
24+
module.name_mapper='react-virtualized/dist/es/CellMeasurer' -> '<PROJECT_ROOT>/flow/ReactVirtualizedStub.js.flow'
2425
suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe

flow/ReactVirtualizedStub.js.flow

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
import { Component } from 'react'
33
declare export var Column: typeof Component
44
declare export var Table: typeof Component
5+
declare export var CellMeasurer: typeof Component
6+
declare export var CellMeasurerCache: any
57
declare export default typeof Component

src/api/APIFactory.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import FileAPI from './File';
1212
import WebLinkAPI from './WebLink';
1313
import SearchAPI from './Search';
1414
import RecentsAPI from './Recents';
15+
import MetadataAPI from './Metadata';
1516
import { DEFAULT_HOSTNAME_API, DEFAULT_HOSTNAME_UPLOAD, TYPE_FOLDER, TYPE_FILE, TYPE_WEBLINK } from '../constants';
1617
import type { Options, ItemType, ItemAPI } from '../flowTypes';
1718

@@ -56,6 +57,11 @@ class APIFactory {
5657
*/
5758
recentsAPI: RecentsAPI;
5859

60+
/**
61+
* @property {MetadataAPI}
62+
*/
63+
metadataAPI: MetadataAPI;
64+
5965
/**
6066
* [constructor]
6167
*
@@ -72,7 +78,7 @@ class APIFactory {
7278
this.options = Object.assign({}, options, {
7379
apiHost: options.apiHost || DEFAULT_HOSTNAME_API,
7480
uploadHost: options.uploadHost || DEFAULT_HOSTNAME_UPLOAD,
75-
cache: new Cache()
81+
cache: options.cache || new Cache()
7682
});
7783
}
7884

@@ -111,11 +117,24 @@ class APIFactory {
111117
this.recentsAPI.destroy();
112118
delete this.recentsAPI;
113119
}
120+
if (this.metadataAPI) {
121+
this.metadataAPI.destroy();
122+
delete this.metadataAPI;
123+
}
114124
if (destroyCache) {
115125
this.options.cache = new Cache();
116126
}
117127
}
118128

129+
/**
130+
* Gets the cache instance
131+
*
132+
* @return {Cache} cache instance
133+
*/
134+
getCache(): ?Cache {
135+
return this.options.cache;
136+
}
137+
119138
/**
120139
* Returns the API based on type of item
121140
*
@@ -219,6 +238,17 @@ class APIFactory {
219238
this.recentsAPI = new RecentsAPI(this.options);
220239
return this.recentsAPI;
221240
}
241+
242+
/**
243+
* API for file meta data
244+
*
245+
* @return {MetadataAPI} MetadataAPI instance
246+
*/
247+
getMetadataAPI(): MetadataAPI {
248+
this.destroy();
249+
this.metadataAPI = new MetadataAPI(this.options);
250+
return this.metadataAPI;
251+
}
222252
}
223253

224254
export default APIFactory;

src/api/Base.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ class Base {
9494
/**
9595
* Gets the cache instance
9696
*
97-
* @param {string} key map key
9897
* @return {Cache} cache instance
9998
*/
10099
getCache(): Cache {

src/api/Chunk.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const UPLOAD_RETRY_INTERVAL_MS = 1000;
1212

1313
class Chunk extends Base {
1414
cancelled: boolean;
15-
chunk: ?Object;
15+
chunk: ?Blob;
1616
data: Object = {};
1717
progress: number = 0;
1818
retry: number;

src/api/ChunkedUpload.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class ChunkedUpload extends Base {
130130
}
131131

132132
this.xhr
133-
.post(uploadSessionUrl, postData)
133+
.post({ url: uploadSessionUrl, data: postData })
134134
.then(this.uploadSessionSuccessHandler)
135135
.catch(this.uploadSessionErrorHandler);
136136
}
@@ -295,7 +295,7 @@ class ChunkedUpload extends Base {
295295
};
296296

297297
this.xhr
298-
.post(this.getUploadSessionUrl(this.sessionId, 'commit'), postData, headers)
298+
.post({ url: this.getUploadSessionUrl(this.sessionId, 'commit'), data: postData, headers })
299299
.then(this.handleCommitSuccess)
300300
.catch(this.handleUploadError);
301301
});
@@ -467,7 +467,7 @@ class ChunkedUpload extends Base {
467467
this.chunks = [];
468468

469469
// Abort upload session
470-
this.xhr.delete(this.getUploadSessionUrl(this.sessionId));
470+
this.xhr.delete({ url: this.getUploadSessionUrl(this.sessionId) });
471471
this.destroy();
472472
}
473473
}

src/api/File.js

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@
55
*/
66

77
import Item from './Item';
8-
import { FIELD_DOWNLOAD_URL, CACHE_PREFIX_FILE } from '../constants';
8+
import {
9+
FIELD_DOWNLOAD_URL,
10+
CACHE_PREFIX_FILE,
11+
FIELDS_TO_FETCH,
12+
X_REP_HINTS,
13+
TYPED_ID_FILE_PREFIX
14+
} from '../constants';
15+
import Cache from '../util/Cache';
916
import type { BoxItem } from '../flowTypes';
1017

1118
class File extends Item {
@@ -19,6 +26,17 @@ class File extends Item {
1926
return `${CACHE_PREFIX_FILE}${id}`;
2027
}
2128

29+
/**
30+
* Returns typed id for file. Useful for when
31+
* making file based XHRs where auth token
32+
* can be per file as used by Preview.
33+
*
34+
* @return {string} typed id for file
35+
*/
36+
getTypedFileId(id: string): string {
37+
return `${TYPED_ID_FILE_PREFIX}${id}`;
38+
}
39+
2240
/**
2341
* API URL for files
2442
*
@@ -33,19 +51,69 @@ class File extends Item {
3351
/**
3452
* API for getting download URL for files
3553
*
36-
* @param {string} [id] optional file id
54+
* @param {string} id - file id
3755
* @return {void}
3856
*/
3957
getDownloadUrl(id: string, successCallback: Function, errorCallback: Function): Promise<void> {
4058
return this.xhr
41-
.get(this.getUrl(id), {
42-
fields: FIELD_DOWNLOAD_URL
59+
.get({
60+
url: this.getUrl(id),
61+
params: {
62+
fields: FIELD_DOWNLOAD_URL
63+
}
4364
})
4465
.then((data: BoxItem) => {
4566
successCallback(data[FIELD_DOWNLOAD_URL]);
4667
})
4768
.catch(errorCallback);
4869
}
70+
71+
/**
72+
* Gets a box file
73+
*
74+
* @param {string} id File id
75+
* @param {Function} successCallback Function to call with results
76+
* @param {Function} errorCallback Function to call with errors
77+
* @param {boolean} forceFetch Bypasses the cache
78+
* @return {Promise}
79+
*/
80+
file(id: string, successCallback: Function, errorCallback: Function, forceFetch: boolean = false): Promise<void> {
81+
if (this.isDestroyed()) {
82+
return Promise.reject();
83+
}
84+
85+
const cache: Cache = this.getCache();
86+
const key = this.getCacheKey(id);
87+
88+
// Clear the cache if needed
89+
if (forceFetch) {
90+
cache.unset(key);
91+
}
92+
93+
// Return the Cache value if it exists
94+
if (cache.has(key)) {
95+
successCallback(cache.get(key));
96+
return Promise.resolve();
97+
}
98+
99+
// Make the XHR request
100+
// We use per file auth tokens for file
101+
// as thats what needed by preview.
102+
return this.xhr
103+
.get({
104+
id: this.getTypedFileId(id),
105+
url: this.getUrl(id),
106+
params: {
107+
fields: FIELDS_TO_FETCH
108+
},
109+
headers: { 'X-Rep-Hints': X_REP_HINTS }
110+
})
111+
.then((file: BoxItem) => {
112+
cache.set(key, file);
113+
successCallback(file);
114+
})
115+
.catch(errorCallback);
116+
}
49117
}
50118

51119
export default File;

src/api/Folder.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import sort from '../util/sorter';
1111
import FileAPI from '../api/File';
1212
import WebLinkAPI from '../api/WebLink';
1313
import Cache from '../util/Cache';
14-
import { FIELDS_TO_FETCH, CACHE_PREFIX_FOLDER } from '../constants';
14+
import { FIELDS_TO_FETCH, CACHE_PREFIX_FOLDER, X_REP_HINTS } from '../constants';
1515
import getBadItemError from '../util/error';
1616
import type {
1717
BoxItem,
@@ -212,10 +212,14 @@ class Folder extends Item {
212212
}
213213

214214
return this.xhr
215-
.get(this.getUrl(this.id), {
216-
offset: this.offset,
217-
limit: LIMIT_ITEM_FETCH,
218-
fields: FIELDS_TO_FETCH
215+
.get({
216+
url: this.getUrl(this.id),
217+
params: {
218+
offset: this.offset,
219+
limit: LIMIT_ITEM_FETCH,
220+
fields: FIELDS_TO_FETCH
221+
},
222+
headers: { 'X-Rep-Hints': X_REP_HINTS }
219223
})
220224
.then(this.folderSuccessHandler)
221225
.catch(this.folderErrorHandler);
@@ -314,10 +318,13 @@ class Folder extends Item {
314318

315319
const url = `${this.getUrl()}?fields=${FIELDS_TO_FETCH}`;
316320
return this.xhr
317-
.post(url, {
318-
name,
319-
parent: {
320-
id: this.id
321+
.post({
322+
url,
323+
data: {
324+
name,
325+
parent: {
326+
id: this.id
327+
}
321328
}
322329
})
323330
.then(this.createSuccessHandler)

src/api/Item.js

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,16 @@ class Item extends Base {
4949
* @param {Response} error.response - error response
5050
* @return {Function} Function that handles response
5151
*/
52-
errorHandler = ({ response }: { response: Response }): void => {
52+
errorHandler = (error: any): void => {
5353
if (this.isDestroyed()) {
5454
return;
5555
}
56+
const { response } = error;
5657
if (response) {
5758
response.json().then(this.errorCallback);
59+
} else if (error instanceof Error) {
60+
this.errorCallback();
61+
throw error;
5862
}
5963
};
6064

@@ -193,7 +197,7 @@ class Item extends Base {
193197
this.errorCallback = errorCallback;
194198

195199
const url = `${this.getUrl(id)}${type === TYPE_FOLDER ? '?recursive=true' : ''}`;
196-
return this.xhr.delete(url).then(this.deleteSuccessHandler).catch(this.errorHandler);
200+
return this.xhr.delete({ url }).then(this.deleteSuccessHandler).catch(this.errorHandler);
197201
}
198202

199203
/**
@@ -211,22 +215,37 @@ class Item extends Base {
211215
/**
212216
* API to rename an Item
213217
*
214-
* @param {string} id - item id
218+
* @param {Object} item - item to rename
215219
* @param {string} name - item new name
216220
* @param {Function} successCallback - success callback
217221
* @param {Function} errorCallback - error callback
218222
* @return {void}
219223
*/
220-
rename(id: string, name: string, successCallback: Function, errorCallback: Function = noop): Promise<void> {
224+
rename(item: BoxItem, name: string, successCallback: Function, errorCallback: Function = noop): Promise<void> {
221225
if (this.isDestroyed()) {
222226
return Promise.reject();
223227
}
224228

229+
const { id, permissions }: BoxItem = item;
230+
if (!id || !permissions) {
231+
errorCallback();
232+
return Promise.reject();
233+
}
234+
235+
const { can_rename }: BoxItemPermission = permissions;
236+
if (!can_rename) {
237+
errorCallback();
238+
return Promise.reject();
239+
}
240+
225241
this.id = id;
226242
this.successCallback = successCallback;
227243
this.errorCallback = errorCallback;
228244

229-
return this.xhr.put(`${this.getUrl(id)}`, { name }).then(this.renameSuccessHandler).catch(this.errorHandler);
245+
return this.xhr
246+
.put({ url: `${this.getUrl(id)}`, data: { name } })
247+
.then(this.renameSuccessHandler)
248+
.catch(this.errorHandler);
230249
}
231250

232251
/**
@@ -242,24 +261,41 @@ class Item extends Base {
242261
/**
243262
* Api to create or remove a shared link
244263
*
245-
* @param {string} id - item id
264+
* @param {Object} item - item to share
246265
* @param {string} access - shared access level
247266
* @param {Function} successCallback - success callback
248267
* @param {Function|void} errorCallback - error callback
249268
* @return {void}
250269
*/
251-
share(id: string, access: string, successCallback: Function, errorCallback: Function = noop): Promise<void> {
270+
share(item: BoxItem, access: string, successCallback: Function, errorCallback: Function = noop): Promise<void> {
252271
if (this.isDestroyed()) {
253272
return Promise.reject();
254273
}
255274

275+
const { id, permissions }: BoxItem = item;
276+
if (!id || !permissions) {
277+
errorCallback();
278+
return Promise.reject();
279+
}
280+
281+
const { can_share, can_set_share_access }: BoxItemPermission = permissions;
282+
if (!can_share || !can_set_share_access) {
283+
errorCallback();
284+
return Promise.reject();
285+
}
286+
256287
this.id = id;
257288
this.successCallback = successCallback;
258289
this.errorCallback = errorCallback;
259290

291+
// We use the parent folder's auth token since use case involves
292+
// only content explorer or picker which works onf folder tokens
260293
return this.xhr
261-
.put(this.getUrl(this.id), {
262-
shared_link: access === ACCESS_NONE ? null : { access }
294+
.put({
295+
url: this.getUrl(this.id),
296+
data: {
297+
shared_link: access === ACCESS_NONE ? null : { access }
298+
}
263299
})
264300
.then(this.shareSuccessHandler)
265301
.catch(this.errorHandler);

0 commit comments

Comments
 (0)