Skip to content

Commit d5396bd

Browse files
authored
feat(versions): Add support for file version restore actions (#1184)
1 parent 69f329b commit d5396bd

File tree

8 files changed

+167
-67
lines changed

8 files changed

+167
-67
lines changed

src/api/Versions.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
ERROR_CODE_DELETE_VERSION,
1414
ERROR_CODE_FETCH_VERSIONS,
1515
ERROR_CODE_PROMOTE_VERSION,
16+
ERROR_CODE_RESTORE_VERSION,
1617
PERMISSION_CAN_DELETE,
1718
PERMISSION_CAN_UPLOAD,
1819
VERSION_DELETE_ACTION,
@@ -251,6 +252,53 @@ class Versions extends OffsetBasedAPI {
251252
errorCallback,
252253
});
253254
}
255+
256+
/**
257+
* API for restoring a deleted version of a file
258+
*
259+
* @param {Object} options - the request options
260+
* @param {string} options.fileId - a box file id
261+
* @param {string} options.versionId - a box file version id
262+
* @param {BoxItemPermission} options.permissions - the permissions for the file
263+
* @param {Function} options.successCallback - the success callback
264+
* @param {Function} options.errorCallback - the error callback
265+
* @returns {void}
266+
*/
267+
restoreVersion({
268+
errorCallback,
269+
fileId,
270+
permissions,
271+
successCallback,
272+
versionId,
273+
}: {
274+
errorCallback: ElementsErrorCallback,
275+
fileId: string,
276+
permissions: BoxItemPermission,
277+
successCallback: BoxItemVersion => any,
278+
versionId: string,
279+
}): void {
280+
this.errorCode = ERROR_CODE_RESTORE_VERSION;
281+
282+
try {
283+
this.checkApiCallValidity(PERMISSION_CAN_UPLOAD, permissions, fileId);
284+
} catch (e) {
285+
errorCallback(e, this.errorCode);
286+
return;
287+
}
288+
289+
this.post({
290+
id: fileId,
291+
data: {
292+
data: {
293+
id: versionId,
294+
type: 'file_version',
295+
},
296+
},
297+
url: this.getVersionUrl(fileId, versionId),
298+
successCallback,
299+
errorCallback,
300+
});
301+
}
254302
}
255303

256304
export default Versions;

src/api/__tests__/Versions-test.js

Lines changed: 49 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -122,27 +122,54 @@ describe('api/Versions', () => {
122122
versions.checkApiCallValidity = jest.fn(() => true);
123123
versions.delete = jest.fn();
124124
versions.get = jest.fn();
125-
versions.getUrl = jest.fn(() => 'https://www.foo.com/versions');
126125
versions.offsetGet = jest.fn();
127126
versions.post = jest.fn();
128127
});
129128

130-
describe('promoteVersion()', () => {
129+
describe('deleteVersion()', () => {
131130
const permissions = {
132-
[PERMISSION_CAN_UPLOAD]: true,
131+
[PERMISSION_CAN_DELETE]: true,
133132
};
134133

135-
test('should check for valid version promote permissions', () => {
136-
versions.promoteVersion({
134+
test('should delete a version from the versions endpoint', () => {
135+
versions.deleteVersion({
137136
fileId,
138137
versionId,
139138
permissions,
140139
successCallback,
141140
errorCallback,
142141
});
143142

144-
expect(versions.checkApiCallValidity).toBeCalledWith(PERMISSION_CAN_UPLOAD, permissions, fileId);
143+
expect(versions.checkApiCallValidity).toBeCalledWith(PERMISSION_CAN_DELETE, permissions, fileId);
144+
expect(versions.delete).toBeCalledWith({
145+
id: fileId,
146+
url: `https://api.box.com/2.0/files/${fileId}/versions/${versionId}`,
147+
successCallback,
148+
errorCallback,
149+
});
145150
});
151+
});
152+
153+
describe('getVersions()', () => {
154+
test('should return a list of versions from the versions endpoint', () => {
155+
versions.getVersions(fileId, successCallback, errorCallback);
156+
157+
expect(versions.offsetGet).toBeCalledWith(
158+
fileId,
159+
successCallback,
160+
errorCallback,
161+
0,
162+
1000,
163+
FILE_VERSIONS_FIELDS_TO_FETCH,
164+
true,
165+
);
166+
});
167+
});
168+
169+
describe('promoteVersion()', () => {
170+
const permissions = {
171+
[PERMISSION_CAN_UPLOAD]: true,
172+
};
146173

147174
test('should post a well formed version promote request to the versions endpoint', () => {
148175
const requestData = {
@@ -160,65 +187,47 @@ describe('api/Versions', () => {
160187
errorCallback,
161188
});
162189

190+
expect(versions.checkApiCallValidity).toBeCalledWith(PERMISSION_CAN_UPLOAD, permissions, fileId);
163191
expect(versions.post).toBeCalledWith({
164192
id: fileId,
165-
url: versions.getVersionUrl(versionId, 'current'),
193+
url: `https://api.box.com/2.0/files/${fileId}/versions/current`,
166194
data: requestData,
167195
successCallback,
168196
errorCallback,
169197
});
170198
});
171199
});
172200

173-
describe('deleteVersion()', () => {
201+
describe('restoreVersion()', () => {
174202
const permissions = {
175-
[PERMISSION_CAN_DELETE]: true,
203+
[PERMISSION_CAN_UPLOAD]: true,
176204
};
177205

178-
test('should check for valid version delete permissions', () => {
179-
versions.deleteVersion({
180-
fileId,
181-
versionId,
182-
permissions,
183-
successCallback,
184-
errorCallback,
185-
});
186-
187-
expect(versions.checkApiCallValidity).toBeCalledWith(PERMISSION_CAN_DELETE, permissions, fileId);
188-
});
206+
test('should post a well formed version restore request to the versions endpoint', () => {
207+
const requestData = {
208+
data: {
209+
id: versionId,
210+
type: 'file_version',
211+
},
212+
};
189213

190-
test('should delete a version from the versions endpoint', () => {
191-
versions.deleteVersion({
214+
versions.restoreVersion({
192215
fileId,
193216
versionId,
194217
permissions,
195218
successCallback,
196219
errorCallback,
197220
});
198221

199-
expect(versions.delete).toBeCalledWith({
222+
expect(versions.checkApiCallValidity).toBeCalledWith(PERMISSION_CAN_UPLOAD, permissions, fileId);
223+
expect(versions.post).toBeCalledWith({
200224
id: fileId,
201-
url: versions.getVersionUrl(fileId, versionId),
225+
url: `https://api.box.com/2.0/files/${fileId}/versions/${versionId}`,
226+
data: requestData,
202227
successCallback,
203228
errorCallback,
204229
});
205230
});
206231
});
207-
208-
describe('getVersions()', () => {
209-
test('should return a list of versions from the versions endpoint', () => {
210-
versions.getVersions(fileId, successCallback, errorCallback);
211-
212-
expect(versions.offsetGet).toBeCalledWith(
213-
fileId,
214-
successCallback,
215-
errorCallback,
216-
0,
217-
1000,
218-
FILE_VERSIONS_FIELDS_TO_FETCH,
219-
true,
220-
);
221-
});
222-
});
223232
});
224233
});

src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ export const ERROR_CODE_DELETE_ITEM = 'delete_item_error';
230230
export const ERROR_CODE_DELETE_METADATA = 'delete_metadata_error';
231231
export const ERROR_CODE_DELETE_VERSION = 'delete_version_error';
232232
export const ERROR_CODE_PROMOTE_VERSION = 'promote_version_error';
233+
export const ERROR_CODE_RESTORE_VERSION = 'restore_version_error';
233234
export const ERROR_CODE_UPDATE_TASK = 'update_task_error';
234235
export const ERROR_CODE_UPDATE_TASK_ASSIGNMENT = 'update_task_assignment_error';
235236
export const ERROR_CODE_UPDATE_TASK_COLLABORATOR = 'update_task_collaborator_error';

src/elements/content-sidebar/versions/VersionsList.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ type Props = {
1818
onDownload: VersionActionCallback,
1919
onPreview: VersionActionCallback,
2020
onPromote: VersionActionCallback,
21+
onRestore: VersionActionCallback,
2122
permissions: BoxItemPermission,
2223
versions: Array<BoxItemVersion>,
2324
};
2425

25-
const VersionsList = ({ isLoading, onDelete, onDownload, onPreview, onPromote, permissions, versions }: Props) => {
26+
const VersionsList = ({ isLoading, versions, ...rest }: Props) => {
2627
if (!isLoading && !versions.length) {
2728
return (
2829
<div className="bcs-VersionsList bcs-is-empty">
@@ -40,12 +41,8 @@ const VersionsList = ({ isLoading, onDelete, onDownload, onPreview, onPromote, p
4041
<VersionsItem
4142
isCurrent={index === 0}
4243
isSelected={match.params.versionId === version.id}
43-
onDelete={onDelete}
44-
onDownload={onDownload}
45-
onPreview={onPreview}
46-
onPromote={onPromote}
47-
permissions={permissions}
4844
version={version}
45+
{...rest}
4946
/>
5047
)}
5148
/>

src/elements/content-sidebar/versions/VersionsSidebar.js

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,13 @@ type Props = {
2323
onDownload: VersionActionCallback,
2424
onPreview: VersionActionCallback,
2525
onPromote: VersionActionCallback,
26+
onRestore: VersionActionCallback,
2627
parentName: string,
2728
permissions: BoxItemPermission,
2829
versions: Array<BoxItemVersion>,
2930
};
3031

31-
const VersionsSidebar = ({
32-
error,
33-
isLoading,
34-
onDelete,
35-
onDownload,
36-
onPreview,
37-
onPromote,
38-
parentName,
39-
permissions,
40-
versions,
41-
}: Props) => (
32+
const VersionsSidebar = ({ error, isLoading, parentName, ...rest }: Props) => (
4233
<SidebarContent
4334
className="bcs-Versions"
4435
title={
@@ -52,15 +43,7 @@ const VersionsSidebar = ({
5243
{error ? (
5344
<InlineError title={<FormattedMessage {...messagesCommon.error} />}>{error}</InlineError>
5445
) : (
55-
<VersionsList
56-
isLoading={isLoading}
57-
onDelete={onDelete}
58-
onDownload={onDownload}
59-
onPreview={onPreview}
60-
onPromote={onPromote}
61-
permissions={permissions}
62-
versions={versions}
63-
/>
46+
<VersionsList isLoading={isLoading} {...rest} />
6447
)}
6548
</LoadingIndicatorWrapper>
6649
</SidebarContent>

src/elements/content-sidebar/versions/VersionsSidebarContainer.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ class VersionsSidebarContainer extends React.Component<Props, State> {
101101
});
102102
};
103103

104+
handleActionRestore = (versionId: string): void => {
105+
this.setState({ isLoading: true }, () => {
106+
this.restoreVersion(versionId)
107+
.then(this.fetchData)
108+
.catch(this.handleActionError);
109+
});
110+
};
111+
104112
handleDeleteSuccess = (versionId: string) => {
105113
const { versionId: selectedVersionId } = this.props;
106114

@@ -214,6 +222,21 @@ class VersionsSidebarContainer extends React.Component<Props, State> {
214222
);
215223
};
216224

225+
restoreVersion = (versionId: string): Promise<any> => {
226+
const { api, fileId } = this.props;
227+
const { permissions } = this.state;
228+
229+
return new Promise((successCallback, errorCallback) =>
230+
api.getVersionsAPI(false).restoreVersion({
231+
fileId,
232+
permissions,
233+
successCallback,
234+
errorCallback,
235+
versionId,
236+
}),
237+
);
238+
};
239+
217240
updateVersion = (versionId?: ?string): void => {
218241
const { history, match } = this.props;
219242
return history.push(generatePath(match.path, { ...match.params, versionId }));
@@ -234,6 +257,7 @@ class VersionsSidebarContainer extends React.Component<Props, State> {
234257
onDownload={this.handleActionDownload}
235258
onPreview={this.handleActionPreview}
236259
onPromote={this.handleActionPromote}
260+
onRestore={this.handleActionRestore}
237261
parentName={parentName}
238262
{...this.state}
239263
/>

src/elements/content-sidebar/versions/__tests__/VersionsSidebarContainer-test.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe('elements/content-sidebar/versions/VersionsSidebarContainer', () => {
2929
deleteVersion: jest.fn(),
3030
getVersions: jest.fn(),
3131
promoteVersion: jest.fn(),
32+
restoreVersion: jest.fn(),
3233
};
3334
const api = {
3435
getFileAPI: () => fileAPI,
@@ -108,7 +109,7 @@ describe('elements/content-sidebar/versions/VersionsSidebarContainer', () => {
108109
});
109110
});
110111

111-
describe('handleActionPRomote', () => {
112+
describe('handleActionPromote', () => {
112113
test('should set state and call api endpoint helpers', () => {
113114
const wrapper = getWrapper({ versionId: '123' });
114115
const instance = wrapper.instance();
@@ -125,6 +126,22 @@ describe('elements/content-sidebar/versions/VersionsSidebarContainer', () => {
125126
});
126127
});
127128

129+
describe('handleActionRestore', () => {
130+
test('should set state and call api endpoint helpers', () => {
131+
const wrapper = getWrapper({ versionId: '123' });
132+
const instance = wrapper.instance();
133+
const versionId = '456';
134+
135+
instance.restoreVersion = jest.fn().mockResolvedValueOnce();
136+
instance.fetchData = jest.fn().mockResolvedValueOnce();
137+
138+
instance.handleActionRestore(versionId);
139+
140+
expect(wrapper.state('isLoading')).toBe(true);
141+
expect(instance.restoreVersion).toHaveBeenCalledWith(versionId);
142+
});
143+
});
144+
128145
describe('handleFetchError', () => {
129146
test('should set state to default values with error message', () => {
130147
const wrapper = getWrapper();
@@ -271,6 +288,26 @@ describe('elements/content-sidebar/versions/VersionsSidebarContainer', () => {
271288
});
272289
});
273290

291+
describe('restoreVersion', () => {
292+
test('should call restoreVersion', () => {
293+
const wrapper = getWrapper();
294+
const permissions = { can_upload: true };
295+
296+
wrapper.setState({ permissions });
297+
298+
const promotePromise = wrapper.instance().restoreVersion('123');
299+
300+
expect(promotePromise).toBeInstanceOf(Promise);
301+
expect(versionsAPI.restoreVersion).toBeCalledWith({
302+
fileId: defaultId,
303+
permissions,
304+
errorCallback: expect.any(Function),
305+
successCallback: expect.any(Function),
306+
versionId: '123',
307+
});
308+
});
309+
});
310+
274311
describe('render', () => {
275312
test('should match its snapshot', () => {
276313
const wrapper = getWrapper({ parentName: 'activity' });

0 commit comments

Comments
 (0)