Skip to content

Commit

Permalink
Merge pull request hms-dbmi-cellenics#949 from biomage-org/add-upload…
Browse files Browse the repository at this point in the history
…-cancellation

Add upload cancellation
  • Loading branch information
cosa65 authored Nov 28, 2023
2 parents 2ac56d0 + 8b2fdbd commit 83d3ddd
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 20 deletions.
14 changes: 14 additions & 0 deletions src/__test__/redux/reducers/samplesReducer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,4 +398,18 @@ describe('samplesReducer', () => {

expect(newState).toMatchSnapshot();
});

it('Sample file update doesnt change anything if the sample no longer exists', () => {
const newState = samplesReducer(initialState, {
type: SAMPLES_FILE_UPDATE,
payload: {
sampleUuid: mockUuid1,
fileName,
fileDiff: mockFile,
lastModified: 'newLastModified',
},
});

expect(newState).toEqual(initialState);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ exports[`processUpload Uploads and updates redux correctly when there are no err
},
"method": "put",
"onUploadProgress": [Function],
"signal": AbortSignal {},
"url": "theSignedUrl",
},
{
Expand All @@ -243,6 +244,7 @@ exports[`processUpload Uploads and updates redux correctly when there are no err
},
"method": "put",
"onUploadProgress": [Function],
"signal": AbortSignal {},
"url": "theSignedUrl",
},
{
Expand All @@ -252,6 +254,7 @@ exports[`processUpload Uploads and updates redux correctly when there are no err
},
"method": "put",
"onUploadProgress": [Function],
"signal": AbortSignal {},
"url": "theSignedUrl",
},
]
Expand Down Expand Up @@ -401,6 +404,7 @@ exports[`processUpload Uploads and updates redux correctly when there are no err
},
"method": "put",
"onUploadProgress": [Function],
"signal": AbortSignal {},
"url": "theSignedUrl",
},
{
Expand All @@ -410,6 +414,7 @@ exports[`processUpload Uploads and updates redux correctly when there are no err
},
"method": "put",
"onUploadProgress": [Function],
"signal": AbortSignal {},
"url": "theSignedUrl",
},
{
Expand All @@ -419,6 +424,7 @@ exports[`processUpload Uploads and updates redux correctly when there are no err
},
"method": "put",
"onUploadProgress": [Function],
"signal": AbortSignal {},
"url": "theSignedUrl",
},
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ exports[`processUpload Uploads and updates redux correctly when there are no err
},
"method": "put",
"onUploadProgress": [Function],
"signal": AbortSignal {},
"url": "theSignedUrl1",
},
{
Expand All @@ -18,6 +19,7 @@ exports[`processUpload Uploads and updates redux correctly when there are no err
},
"method": "put",
"onUploadProgress": [Function],
"signal": AbortSignal {},
"url": "theSignedUrl2",
},
]
Expand Down
5 changes: 4 additions & 1 deletion src/redux/actions/samples/createSampleFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const createSampleFile = (
sampleId,
type,
fileForApiV1,
abortController,
) => async (dispatch) => {
const updatedAt = dayjs().toISOString();

Expand All @@ -30,8 +31,10 @@ const createSampleFile = (
lastModified: updatedAt,
fileName: fileNameForApiV1[type],
fileDiff: {
upload: { status: UploadStatus.UPLOADING },
...fileForApiV1,
upload: {
status: UploadStatus.UPLOADING, progress: 0, abortController,
},
},
},
});
Expand Down
13 changes: 10 additions & 3 deletions src/redux/actions/samples/createSamples.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { defaultSampleOptions, sampleTemplate } from 'redux/reducers/samples/ini
import { sampleTech } from 'utils/constants';
import UploadStatus from 'utils/upload/UploadStatus';

import fileNameForApiV1 from 'utils/upload/fileNameForApiV1';
import getFileTypeV2 from 'utils/getFileTypeV2';

// If the sample name of new samples coincides with already existing
// ones we should not create new samples,
// just reuse their sampleIds and upload the new files
Expand Down Expand Up @@ -115,9 +118,13 @@ const createSamples = (
options,
metadata: experiment?.metadataKeys
.reduce((acc, curr) => ({ ...acc, [curr]: METADATA_DEFAULT_VALUE }), {}) || {},
files: Object.values(files).reduce(((acc, curr) => (
{ ...acc, [curr.name]: { upload: { status: UploadStatus.UPLOADING } } }
)), {}),
files: Object.values(files).reduce(((acc, curr) => {
const fileType = fileNameForApiV1[getFileTypeV2(curr.name, sampleTechnology)];

return (
{ ...acc, [fileType]: { upload: { status: UploadStatus.UPLOADING } } }
);
}), {}),
}));

dispatch({
Expand Down
14 changes: 5 additions & 9 deletions src/redux/actions/samples/deleteSamples.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,11 @@ import endUserMessages from 'utils/endUserMessages';
import fetchAPI from 'utils/http/fetchAPI';
import handleError from 'utils/http/handleError';

const cancelUploads = async (files) => {
const promises = Object.values(files).map(({ upload }) => {
if (upload?.amplifyPromise) {
// return Storage.cancel(upload.amplifyPromise);
}
return Promise.resolve();
const cancelUploads = (files) => {
Object.values(files).forEach((file) => {
// eslint-disable-next-line no-unused-expressions
file.upload?.abortController?.abort();
});

return Promise.all(promises);
};

const deleteSamples = (
Expand All @@ -34,7 +30,7 @@ const deleteSamples = (
acc[samples[sampleUuid].experimentId] = [];
}

await cancelUploads(files);
cancelUploads(files);

return {
...acc,
Expand Down
6 changes: 6 additions & 0 deletions src/redux/reducers/samples/samplesFileUpdate.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ const samplesFileUpdate = (state, action) => {
sampleUuid, fileName, fileDiff, lastModified,
} = action.payload;

// There's a possible race condition where a file update can reach this place
// after a sample is deleted and there's a crash. This check is in place to avoid that error.
if (_.isNil(state[sampleUuid])) {
return state;
}

const oldFile = state[sampleUuid].files?.[fileName];
let newFile = fileDiff;

Expand Down
18 changes: 15 additions & 3 deletions src/utils/upload/processMultipartUpload.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import axios from 'axios';
const FILE_CHUNK_SIZE = 10000000;
const MAX_RETRIES = 2;

const putPartInS3 = async (blob, signedUrl, onUploadProgress, currentRetry = 0) => {
const putPartInS3 = async (
blob, signedUrl, onUploadProgress, currentRetry = 0, abortController = null,
) => {
try {
return await axios.request({
method: 'put',
data: blob,
url: signedUrl,
signal: abortController.signal,
headers: {
'Content-Type': 'application/octet-stream',
},
Expand All @@ -23,7 +26,9 @@ const putPartInS3 = async (blob, signedUrl, onUploadProgress, currentRetry = 0)
}
};

const processMultipartUpload = async (file, signedUrls, createOnUploadProgressForPart) => {
const processMultipartUpload = async (
file, signedUrls, createOnUploadProgressForPart, abortController,
) => {
const promises = [];

signedUrls.forEach((signedUrl, index) => {
Expand All @@ -33,7 +38,14 @@ const processMultipartUpload = async (file, signedUrls, createOnUploadProgressFo
? file.fileObject.slice(start, end)
: file.fileObject.slice(start);

const req = putPartInS3(blob, signedUrl, createOnUploadProgressForPart(index));
const req = putPartInS3(
blob,
signedUrl,
createOnUploadProgressForPart(index),
0,
abortController,
);

promises.push(req);
});

Expand Down
12 changes: 8 additions & 4 deletions src/utils/upload/processUpload.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import endUserMessages from 'utils/endUserMessages';
import pushNotificationMessage from 'utils/pushNotificationMessage';

const prepareAndUploadFileToS3 = async (
file, uploadUrlParams, type, onStatusUpdate = () => { },
file, uploadUrlParams, type, onStatusUpdate = () => { }, abortController = null,
) => {
let parts = null;
const { signedUrls, uploadId, fileId } = uploadUrlParams;
Expand All @@ -34,7 +34,9 @@ const prepareAndUploadFileToS3 = async (
onStatusUpdate(UploadStatus.UPLOADING, percentProgress);
};
try {
parts = await processMultipartUpload(file, signedUrls, createOnUploadProgressForPart);
parts = await processMultipartUpload(
file, signedUrls, createOnUploadProgressForPart, abortController,
);
} catch (e) {
onStatusUpdate(UploadStatus.UPLOAD_ERROR);
return;
Expand Down Expand Up @@ -64,6 +66,8 @@ const prepareAndUploadFileToS3 = async (
const createAndUploadSampleFile = async (file, experimentId, sampleId, dispatch, selectedTech) => {
const fileType = getFileTypeV2(file.name, selectedTech);

const abortController = new AbortController();

let sampleFileId;

try {
Expand All @@ -73,6 +77,7 @@ const createAndUploadSampleFile = async (file, experimentId, sampleId, dispatch,
sampleId,
fileType,
file,
abortController,
),
);
} catch (e) {
Expand Down Expand Up @@ -114,8 +119,7 @@ const createAndUploadSampleFile = async (file, experimentId, sampleId, dispatch,
);

const uploadUrlParams = { signedUrls, uploadId, fileId: sampleFileId };

await prepareAndUploadFileToS3(file, uploadUrlParams, 'sample', updateSampleFileUploadProgress);
await prepareAndUploadFileToS3(file, uploadUrlParams, 'sample', updateSampleFileUploadProgress, abortController);
} catch (e) {
dispatch(updateSampleFileUpload(
experimentId, sampleId, fileType, UploadStatus.UPLOAD_ERROR,
Expand Down

0 comments on commit 83d3ddd

Please sign in to comment.