Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New: version history in details tab #220

Merged
merged 8 commits into from
Mar 20, 2018
Merged
Show file tree
Hide file tree
Changes from 2 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
31 changes: 30 additions & 1 deletion src/api/APIFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ import FileAPI from './File';
import WebLinkAPI from './WebLink';
import SearchAPI from './Search';
import RecentsAPI from './Recents';
import { DEFAULT_HOSTNAME_API, DEFAULT_HOSTNAME_UPLOAD, TYPE_FOLDER, TYPE_FILE, TYPE_WEBLINK } from '../constants';
import VersionsAPI from './Versions';
import {
DEFAULT_HOSTNAME_API,
DEFAULT_HOSTNAME_UPLOAD,
TYPE_FOLDER,
TYPE_FILE,
TYPE_WEBLINK,
TYPE_VERSIONS
} from '../constants';
import type { Options, ItemType, ItemAPI } from '../flowTypes';

class APIFactory {
Expand Down Expand Up @@ -56,6 +64,11 @@ class APIFactory {
*/
recentsAPI: RecentsAPI;

/**
* @property {VersionsAPI}
*/
versionsAPI: VersionsAPI;

/**
* [constructor]
*
Expand Down Expand Up @@ -111,6 +124,10 @@ class APIFactory {
this.recentsAPI.destroy();
delete this.recentsAPI;
}
if (this.versionsAPI) {
this.versionsAPI.destroy();
delete this.versionsAPI;
}
if (destroyCache) {
this.options.cache = new Cache();
}
Expand Down Expand Up @@ -145,6 +162,9 @@ class APIFactory {
case TYPE_WEBLINK:
api = this.getWebLinkAPI();
break;
case TYPE_VERSIONS:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not be here, as thats for item type only

api = this.getVersionsAPI();
break;
default:
throw new Error('Unknown Type!');
}
Expand Down Expand Up @@ -228,6 +248,15 @@ class APIFactory {
this.recentsAPI = new RecentsAPI(this.options);
return this.recentsAPI;
}

/**
* API for versions
*/
getVersionsAPI(): VersionsAPI {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may have to extend/improve these get*API calls to optionally take a flag that prevents calling this.destroy(). Until now it wasn't really needed because most API calls were not really called in parallel. Called one at a time. But versions will be called in parallel to say the File api call. So calling destroy will destroy the other. Likewise I didn't want two API calls to muck with the same cached object, but in versions case its a diff cache object, so not a big deal from that perspective.

Either that, or the call to getVersionAPI() should come in the fetch success callback of the file, so that its a serial call and not parallel.

this.destroy();
this.versionsAPI = new VersionsAPI(this.options);
return this.versionsAPI;
}
}

export default APIFactory;
13 changes: 1 addition & 12 deletions src/api/File.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import Item from './Item';
import { getFieldsAsString } from '../util/fields';
import { FIELD_DOWNLOAD_URL, CACHE_PREFIX_FILE, X_REP_HINTS, TYPED_ID_FILE_PREFIX } from '../constants';
import { FIELD_DOWNLOAD_URL, CACHE_PREFIX_FILE, X_REP_HINTS } from '../constants';
import type Cache from '../util/Cache';
import { getBadItemError, getBadPermissionsError } from '../util/error';
import type { BoxItem } from '../flowTypes';
Expand All @@ -22,17 +22,6 @@ class File extends Item {
return `${CACHE_PREFIX_FILE}${id}`;
}

/**
* Returns typed id for file. Useful for when
* making file based XHRs where auth token
* can be per file as used by Preview.
*
* @return {string} typed id for file
*/
getTypedFileId(id: string): string {
return `${TYPED_ID_FILE_PREFIX}${id}`;
}

/**
* API URL for files
*
Expand Down
13 changes: 12 additions & 1 deletion src/api/Item.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import noop from 'lodash/noop';
import Base from './Base';
import { getBadItemError } from '../util/error';
import { ACCESS_NONE, CACHE_PREFIX_SEARCH, CACHE_PREFIX_FOLDER, TYPE_FOLDER } from '../constants';
import { ACCESS_NONE, CACHE_PREFIX_SEARCH, CACHE_PREFIX_FOLDER, TYPE_FOLDER, TYPED_ID_FILE_PREFIX } from '../constants';
import type Cache from '../util/Cache';
import type { BoxItem, FlattenedBoxItem, FlattenedBoxItemCollection, BoxItemPermission } from '../flowTypes';

Expand Down Expand Up @@ -309,6 +309,17 @@ class Item extends Base {
.then(this.shareSuccessHandler)
.catch(this.errorHandler);
}

/**
* Returns typed id for file. Useful for when
* making file based XHRs where auth token
* can be per file as used by Preview.
*
* @return {string} typed id for file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in File.js as before

*/
getTypedFileId(id: string): string {
return `${TYPED_ID_FILE_PREFIX}${id}`;
}
}

export default Item;
51 changes: 51 additions & 0 deletions src/api/Versions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @flow
* @file Helper for the box versions API
* @author Box
*/

import Item from './Item';
import type { FileVersions } from '../flowTypes';

class Versions extends Item {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not extend Item

/**
* API URL for versions
*
* @param {string} [id] - a box file id
* @return {string} base url for files
*/
getUrl(id: string): string {
if (!id) {
throw new Error('Missing file id!');
}
return `${this.getBaseApiUrl()}/files/${id}/versions`;
}

/**
* Gets the versions for a box file
*
* @param {string} id - a box file id
* @param {Function} successCallback - Function to call with results
* @param {Function} errorCallback - Function to call with errors
* @return {Promise}
*/
versions(id: string, successCallback: Function, errorCallback: Function): Promise<void> {
if (this.isDestroyed()) {
return Promise.reject();
}

// Make the XHR request
// We only need the total_count for now
return this.xhr
.get({
id: this.getTypedFileId(id),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be referenced from FileAPI.
Can maybe make it a static function and update other places calling it.

url: this.getUrl(id)
})
.then(({ data }: { data: FileVersions }) => {
successCallback(data);
})
.catch(errorCallback);
}
}

export default Versions;
18 changes: 18 additions & 0 deletions src/api/__tests__/APIFactory-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import FileAPI from '../File';
import WebLinkAPI from '../WebLink';
import SearchAPI from '../Search';
import RecentsAPI from '../Recents';
import VersionsAPI from '../Versions';
import { DEFAULT_HOSTNAME_API, DEFAULT_HOSTNAME_UPLOAD } from '../../constants';

let factory;
Expand All @@ -33,6 +34,7 @@ describe('api/APIFactory', () => {
factory.plainUploadAPI = { destroy: jest.fn() };
factory.chunkedUploadAPI = { destroy: jest.fn() };
factory.recentsAPI = { destroy: jest.fn() };
factory.versionsAPI = { destroy: jest.fn() };
factory.destroy();
expect(factory.fileAPI).toBeUndefined();
expect(factory.folderAPI).toBeUndefined();
Expand All @@ -41,6 +43,7 @@ describe('api/APIFactory', () => {
expect(factory.plainUploadAPI).toBeUndefined();
expect(factory.chunkedUploadAPI).toBeUndefined();
expect(factory.recentsAPI).toBeUndefined();
expect(factory.versionsAPI).toBeUndefined();
});
test('should not destroy cache by default', () => {
const { cache } = factory.options;
Expand Down Expand Up @@ -68,6 +71,9 @@ describe('api/APIFactory', () => {
test('should return web link api when type is web_link', () => {
expect(factory.getAPI('web_link')).toBeInstanceOf(WebLinkAPI);
});
test('should return versions api when type is versions', () => {
expect(factory.getAPI('versions')).toBeInstanceOf(VersionsAPI);
});
test('should throw error when type is incorrect', () => {
expect(factory.getAPI.bind(factory, 'foo')).toThrow(Error, /Unknown Type/);
});
Expand Down Expand Up @@ -156,4 +162,16 @@ describe('api/APIFactory', () => {
expect(recentsAPI.options.uploadHost).toBe(DEFAULT_HOSTNAME_UPLOAD);
});
});

describe('getVersionsAPI()', () => {
test('should call destroy and return versions API', () => {
const spy = jest.spyOn(factory, 'destroy');
const versionsAPI = factory.getVersionsAPI();
expect(spy).toBeCalled();
expect(versionsAPI).toBeInstanceOf(VersionsAPI);
expect(versionsAPI.options.cache).toBeInstanceOf(Cache);
expect(versionsAPI.options.apiHost).toBe(DEFAULT_HOSTNAME_API);
expect(versionsAPI.options.uploadHost).toBe(DEFAULT_HOSTNAME_UPLOAD);
});
});
});
6 changes: 0 additions & 6 deletions src/api/__tests__/File-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ describe('api/File', () => {
});
});

describe('getTypedFileId()', () => {
test('should return correct typed id', () => {
expect(file.getTypedFileId('foo')).toBe('file_foo');
});
});

describe('getUrl()', () => {
test('should return correct file api url without id', () => {
expect(file.getUrl()).toBe('https://api.box.com/2.0/files');
Expand Down
6 changes: 6 additions & 0 deletions src/api/__tests__/Item-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -523,4 +523,10 @@ describe('api/Item', () => {
expect(item.getParentCacheKey).toHaveBeenCalledWith('parentId');
});
});

describe('getTypedFileId()', () => {
test('should return correct typed id', () => {
expect(item.getTypedFileId('foo')).toBe('file_foo');
});
});
});
71 changes: 71 additions & 0 deletions src/api/__tests__/Versions-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Versions from '../Versions';

let versions;
const versionsResponse = { total_count: 0, entries: [] };

describe('api/Versions', () => {
beforeEach(() => {
versions = new Versions({});
});

describe('getUrl()', () => {
test('should throw when version api url without id', () => {
expect(() => {
versions.getUrl();
}).toThrow();
});
test('should return correct version api url with id', () => {
expect(versions.getUrl('foo')).toBe('https://api.box.com/2.0/files/foo/versions');
});
});

describe('file()', () => {
test('should not do anything if destroyed', () => {
versions.isDestroyed = jest.fn().mockReturnValueOnce(true);
versions.xhr = null;

const successCb = jest.fn();
const errorCb = jest.fn();

return versions.versions('id', successCb, errorCb).catch(() => {
expect(successCb).not.toHaveBeenCalled();
expect(errorCb).not.toHaveBeenCalled();
});
});

test('should make xhr to get versions and call success callback', () => {
versions.xhr = {
get: jest.fn().mockReturnValueOnce(Promise.resolve({ data: versionsResponse }))
};

const successCb = jest.fn();

return versions.versions('id', successCb).then(() => {
expect(successCb).toHaveBeenCalledWith(versionsResponse);
expect(versions.xhr.get).toHaveBeenCalledWith({
id: 'file_id',
url: 'https://api.box.com/2.0/files/id/versions'
});
});
});

test('should call error callback when xhr fails', () => {
const error = new Error('error');
versions.xhr = {
get: jest.fn().mockReturnValueOnce(Promise.reject(error))
};

const successCb = jest.fn();
const errorCb = jest.fn();

return versions.versions('id', successCb, errorCb).then(() => {
expect(successCb).not.toHaveBeenCalled();
expect(errorCb).toHaveBeenCalledWith(error);
expect(versions.xhr.get).toHaveBeenCalledWith({
id: 'file_id',
url: 'https://api.box.com/2.0/files/id/versions'
});
});
});
});
});
Loading