Skip to content

Commit 0f5e0d3

Browse files
committed
✨(front) trigger upload-ended endpoint after classroom document upload
Trigger the `upload-ended` endpoint after classroom document uploads. This ensures that the `upload_state` is properly updated. Since this logic is similar across file types, it has been generalized in the `lib-components` package for reuse across all packages.
1 parent 6a90624 commit 0f5e0d3

File tree

20 files changed

+235
-486
lines changed

20 files changed

+235
-486
lines changed

src/frontend/apps/standalone_site/src/features/Contents/features/Video/components/Manage/VideoCreateForm.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@ import {
88
UploadManagerStatus,
99
Video,
1010
modelName,
11+
uploadEnded,
1112
useResponsive,
1213
useUploadManager,
1314
} from 'lib-components';
14-
import {
15-
LicenseSelect,
16-
UploadVideoForm,
17-
uploadEnded,
18-
useCreateVideo,
19-
} from 'lib-video';
15+
import { LicenseSelect, UploadVideoForm, useCreateVideo } from 'lib-video';
2016
import { Fragment, useEffect, useState } from 'react';
2117
import { defineMessages, useIntl } from 'react-intl';
2218
import { useNavigate } from 'react-router-dom';
@@ -93,7 +89,7 @@ const VideoCreateForm = () => {
9389
video.videoFile,
9490
undefined,
9591
(presignedPost) => {
96-
uploadEnded(data.id, presignedPost.fields['key']);
92+
uploadEnded(modelName.VIDEOS, data.id, presignedPost.fields['key']);
9793
},
9894
);
9995
setIsUploading(true);

src/frontend/packages/lib_classroom/src/components/ClassroomWidgetProvider/widgets/SupportSharing/UploadDocuments/index.spec.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ describe('<UploadDocuments />', () => {
132132
classroomDocument.id,
133133
file,
134134
'1',
135+
expect.any(Function),
135136
);
136137
});
137138

src/frontend/packages/lib_classroom/src/components/ClassroomWidgetProvider/widgets/SupportSharing/UploadDocuments/index.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
formatSizeErrorScale,
1010
ClassroomModelName as modelName,
1111
report,
12+
uploadEnded,
1213
useUploadManager,
1314
} from 'lib-components';
1415
import React, { useCallback, useEffect, useRef, useState } from 'react';
@@ -124,7 +125,20 @@ export const UploadDocuments = ({ classroomId }: UploadDocumentsProps) => {
124125
size: file.size,
125126
classroom_id: classroomId,
126127
});
127-
addUpload(modelName.CLASSROOM_DOCUMENTS, document.id, file, classroomId);
128+
addUpload(
129+
modelName.CLASSROOM_DOCUMENTS,
130+
document.id,
131+
file,
132+
classroomId,
133+
(presignedPost) => {
134+
uploadEnded(
135+
modelName.CLASSROOM_DOCUMENTS,
136+
document.id,
137+
presignedPost.fields['key'],
138+
classroomId,
139+
);
140+
},
141+
);
128142
refreshClassroomDocuments();
129143
} catch (error) {
130144
if ((error as object).hasOwnProperty('size') && metadata.data) {

src/frontend/packages/lib_components/src/data/sideEffects/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export * from './fetchJitsiInfo';
22
export * from './getResource';
33
export * from './refreshToken';
44
export * from './updateResource';
5+
export * from './uploadEnded';
56
export * from './uploadFile';
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import fetchMock from 'fetch-mock';
2+
import {
3+
modelName,
4+
uploadState,
5+
uploadableModelName,
6+
useJwt,
7+
} from 'lib-components';
8+
import {
9+
documentMockFactory,
10+
timedTextMockFactory,
11+
videoMockFactory,
12+
} from 'lib-components/tests';
13+
14+
import { uploadEnded } from '.';
15+
16+
describe('sideEffects/uploadEnded', () => {
17+
beforeEach(() => {
18+
useJwt.getState().setJwt('some token');
19+
});
20+
21+
afterEach(() => fetchMock.restore());
22+
23+
const video = videoMockFactory({
24+
upload_state: uploadState.READY,
25+
});
26+
27+
const timedText = timedTextMockFactory({
28+
upload_state: uploadState.READY,
29+
video: video.id,
30+
});
31+
32+
const document = documentMockFactory({
33+
upload_state: uploadState.READY,
34+
});
35+
36+
it('makes a POST request on the upload-ended route & returns the updated object for simple path', async () => {
37+
fetchMock.mock(
38+
`/api/videos/${video.id}/upload-ended/`,
39+
{
40+
...video,
41+
upload_state: uploadState.PROCESSING,
42+
},
43+
{ method: 'POST' },
44+
);
45+
46+
const updatedVideo = await uploadEnded<typeof video>(
47+
modelName.VIDEOS,
48+
video.id,
49+
`tmp/${video.id}/video/56456454`,
50+
);
51+
52+
expect(updatedVideo).toEqual({
53+
...video,
54+
upload_state: uploadState.PROCESSING,
55+
});
56+
57+
expect(fetchMock.lastCall()![1]!.headers).toEqual({
58+
Authorization: 'Bearer some token',
59+
'Content-Type': 'application/json',
60+
'Accept-Language': 'en',
61+
});
62+
63+
expect(fetchMock.lastCall()![1]!.body).toEqual(
64+
JSON.stringify({ file_key: `tmp/${video.id}/video/56456454` }),
65+
);
66+
});
67+
68+
it('makes a POST request on the upload-ended route with parent path', async () => {
69+
fetchMock.mock(
70+
`/api/videos/${video.id}/${modelName.TIMEDTEXTTRACKS}/${timedText.id}/upload-ended/`,
71+
{
72+
...timedText,
73+
upload_state: uploadState.PROCESSING,
74+
},
75+
{ method: 'POST' },
76+
);
77+
78+
const updatedTimedText = await uploadEnded<typeof video>(
79+
modelName.TIMEDTEXTTRACKS,
80+
timedText.id,
81+
`tmp/${video.id}/video/timedtexttracks/${timedText.id}/56456454`,
82+
video.id,
83+
);
84+
85+
expect(updatedTimedText).toEqual({
86+
...timedText,
87+
upload_state: uploadState.PROCESSING,
88+
});
89+
90+
expect(fetchMock.lastUrl()).toEqual(
91+
`/api/videos/${video.id}/timedtexttracks/${timedText.id}/upload-ended/`,
92+
);
93+
});
94+
95+
it('throws when it fails to trigger the upload-ended (request failure)', async () => {
96+
fetchMock.mock(
97+
`/api/videos/${video.id}/upload-ended/`,
98+
Promise.reject(new Error('Failed to perform the request')),
99+
);
100+
101+
await expect(
102+
uploadEnded(
103+
modelName.VIDEOS as uploadableModelName,
104+
video.id,
105+
`tmp/${video.id}/video/56456454`,
106+
),
107+
).rejects.toThrow('Failed to perform the request');
108+
});
109+
110+
it('throws when it fails to trigger the upload-ended (API error)', async () => {
111+
fetchMock.mock(`/api/videos/${video.id}/upload-ended/`, 400);
112+
113+
await expect(
114+
uploadEnded(
115+
modelName.VIDEOS as uploadableModelName,
116+
video.id,
117+
`tmp/${video.id}/video/56456454`,
118+
),
119+
).rejects.toThrow(`Failed to end the upload for videos/${video.id}.`);
120+
});
121+
122+
it('works with different object types', async () => {
123+
fetchMock.mock(
124+
`/api/documents/${document.id}/upload-ended/`,
125+
{
126+
...document,
127+
upload_state: uploadState.PROCESSING,
128+
},
129+
{ method: 'POST' },
130+
);
131+
132+
const updatedDocument = await uploadEnded(
133+
modelName.DOCUMENTS,
134+
document.id,
135+
`document/${document.id}/987654`,
136+
);
137+
138+
expect(updatedDocument).toEqual({
139+
...document,
140+
upload_state: uploadState.PROCESSING,
141+
});
142+
143+
expect(fetchMock.lastUrl()).toEqual(
144+
`/api/documents/${document.id}/upload-ended/`,
145+
);
146+
});
147+
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Maybe } from '@lib-common/types';
2+
import {
3+
API_ENDPOINT,
4+
UploadableObject,
5+
fetchResponseHandler,
6+
fetchWrapper,
7+
getParentType,
8+
uploadableModelName,
9+
useJwt,
10+
} from 'lib-components';
11+
12+
/**
13+
* Post to an action endpoint to inform the end of the object upload.
14+
* Returns the updated object
15+
* @param objectType The kind of object for which we're ending the upload (model name).
16+
* @param objectId The ID of the object for which we're ending the upload.
17+
* @param fileKey The file key that was uploaded to S3.
18+
* @param parentId The ID of the parent object for which we're ending the upload (nullable).
19+
*/
20+
export const uploadEnded = async <T = UploadableObject>(
21+
objectType: uploadableModelName,
22+
objectId: UploadableObject['id'],
23+
fileKey: string,
24+
parentId?: Maybe<string>,
25+
): Promise<T> => {
26+
let input = `${API_ENDPOINT}/${objectType}/${objectId}/upload-ended/`;
27+
const parentType = getParentType(objectType);
28+
if (parentId && parentType) {
29+
input = `${API_ENDPOINT}/${parentType}/${parentId}/${objectType}/${objectId}/upload-ended/`;
30+
}
31+
32+
const body = {
33+
file_key: fileKey,
34+
};
35+
36+
const response = await fetchWrapper(input, {
37+
body: JSON.stringify(body),
38+
headers: {
39+
Authorization: `Bearer ${useJwt.getState().getJwt() ?? ''}`,
40+
'Content-Type': 'application/json',
41+
},
42+
method: 'POST',
43+
});
44+
45+
return (await fetchResponseHandler(response, {
46+
errorMessage: `Failed to end the upload for ${objectType}/${objectId}.`,
47+
})) as T;
48+
};

src/frontend/packages/lib_video/src/api/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,3 @@ export * from './useStopSharingMedia';
2828
export * from './useTimedTextMetadata';
2929
export * from './useVideoMetadata';
3030
export * from './useVideos';
31-
export * from './uploadEnded';

src/frontend/packages/lib_video/src/api/sharedLiveMediaUploadEnded/index.spec.ts

Lines changed: 0 additions & 81 deletions
This file was deleted.

src/frontend/packages/lib_video/src/api/sharedLiveMediaUploadEnded/index.ts

Lines changed: 0 additions & 43 deletions
This file was deleted.

0 commit comments

Comments
 (0)