Multiple files upload refactor (#1755)
- failed uploaded objects progress bar shows in red color - fixed bug in where failed uploaded objects cannot be removed from listed objects in ObjectManager - display delete button for failed upload objects - display setErrorSnackMessage component after done uploading all objects with number of failed objects - fixed race condition bug during multiple objects upload, now we are using Promise.allSettled to handle synchronization between uploads Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
@@ -763,135 +763,135 @@ const ListObjects = ({
|
|||||||
path: string,
|
path: string,
|
||||||
folderPath: string
|
folderPath: string
|
||||||
) => {
|
) => {
|
||||||
if (files.length > 0) {
|
let uploadPromise = (file: File) => {
|
||||||
openList();
|
return new Promise((resolve, reject) => {
|
||||||
let nextFile = files.pop();
|
let uploadUrl = `api/v1/buckets/${bucketName}/objects/upload`;
|
||||||
let uploadPromise = (file: File) => {
|
const fileName = file.name;
|
||||||
return new Promise((resolve, reject) => {
|
const blobFile = new Blob([file], { type: file.type });
|
||||||
let uploadUrl = `api/v1/buckets/${bucketName}/objects/upload`;
|
|
||||||
const fileName = file.name;
|
|
||||||
const blobFile = new Blob([file], { type: file.type });
|
|
||||||
|
|
||||||
let encodedPath = "";
|
let encodedPath = "";
|
||||||
const relativeFolderPath =
|
const relativeFolderPath =
|
||||||
get(file, "webkitRelativePath", "") !== ""
|
get(file, "webkitRelativePath", "") !== ""
|
||||||
? get(file, "webkitRelativePath", "")
|
? get(file, "webkitRelativePath", "")
|
||||||
: folderPath;
|
: folderPath;
|
||||||
|
|
||||||
if (path !== "" || relativeFolderPath !== "") {
|
if (path !== "" || relativeFolderPath !== "") {
|
||||||
const finalFolderPath = relativeFolderPath
|
const finalFolderPath = relativeFolderPath
|
||||||
.split("/")
|
.split("/")
|
||||||
.slice(0, -1)
|
.slice(0, -1)
|
||||||
.join("/");
|
.join("/");
|
||||||
|
|
||||||
encodedPath = encodeFileName(
|
encodedPath = encodeFileName(
|
||||||
`${path}${finalFolderPath}${
|
`${path}${finalFolderPath}${
|
||||||
!finalFolderPath.endsWith("/") ? "/" : ""
|
!finalFolderPath.endsWith("/") ? "/" : ""
|
||||||
}`
|
}`
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (encodedPath !== "") {
|
|
||||||
uploadUrl = `${uploadUrl}?prefix=${encodedPath}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const identity = encodeFileName(
|
|
||||||
`${bucketName}-${encodedPath}-${new Date().getTime()}-${Math.random()}`
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
setNewObject({
|
if (encodedPath !== "") {
|
||||||
bucketName,
|
uploadUrl = `${uploadUrl}?prefix=${encodedPath}`;
|
||||||
done: false,
|
}
|
||||||
instanceID: identity,
|
|
||||||
percentage: 0,
|
|
||||||
prefix: `${decodeFileName(encodedPath)}${fileName}`,
|
|
||||||
type: "upload",
|
|
||||||
waitingForFile: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let xhr = new XMLHttpRequest();
|
const identity = encodeFileName(
|
||||||
xhr.open("POST", uploadUrl, true);
|
`${bucketName}-${encodedPath}-${new Date().getTime()}-${Math.random()}`
|
||||||
|
);
|
||||||
|
|
||||||
const areMultipleFiles = files.length > 1;
|
setNewObject({
|
||||||
const errorMessage = `An error occurred while uploading the file${
|
bucketName,
|
||||||
areMultipleFiles ? "s" : ""
|
done: false,
|
||||||
}.`;
|
instanceID: identity,
|
||||||
const okMessage = `Object${
|
percentage: 0,
|
||||||
areMultipleFiles ? "s" : ``
|
prefix: `${decodeFileName(encodedPath)}${fileName}`,
|
||||||
} uploaded successfully.`;
|
type: "upload",
|
||||||
|
waitingForFile: false,
|
||||||
xhr.withCredentials = false;
|
|
||||||
xhr.onload = function (event) {
|
|
||||||
if (
|
|
||||||
xhr.status === 401 ||
|
|
||||||
xhr.status === 403 ||
|
|
||||||
xhr.status === 400 ||
|
|
||||||
xhr.status === 500
|
|
||||||
) {
|
|
||||||
if (xhr.response) {
|
|
||||||
const err = JSON.parse(xhr.response);
|
|
||||||
setSnackBarMessage(err.detailedMessage);
|
|
||||||
} else {
|
|
||||||
setSnackBarMessage(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (xhr.status === 413) {
|
|
||||||
setSnackBarMessage("Error - File size too large");
|
|
||||||
}
|
|
||||||
if (xhr.status === 200) {
|
|
||||||
completeObject(identity);
|
|
||||||
if (files.length === 0) {
|
|
||||||
setSnackBarMessage(okMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolve(xhr.status);
|
|
||||||
if (files.length > 0) {
|
|
||||||
let nFile = files.pop();
|
|
||||||
if (nFile) {
|
|
||||||
return uploadPromise(nFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.upload.addEventListener("error", (event) => {
|
|
||||||
setSnackBarMessage(errorMessage);
|
|
||||||
});
|
|
||||||
|
|
||||||
xhr.upload.addEventListener("progress", (event) => {
|
|
||||||
const progress = Math.floor((event.loaded * 100) / event.total);
|
|
||||||
|
|
||||||
updateProgress(identity, progress);
|
|
||||||
});
|
|
||||||
|
|
||||||
xhr.onerror = () => {
|
|
||||||
setSnackBarMessage(errorMessage);
|
|
||||||
reject(errorMessage);
|
|
||||||
};
|
|
||||||
xhr.onloadend = () => {
|
|
||||||
if (files.length === 0) {
|
|
||||||
setLoading(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
if (file.size !== undefined) {
|
|
||||||
formData.append(file.size.toString(), blobFile, fileName);
|
|
||||||
|
|
||||||
xhr.send(formData);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
if (nextFile) {
|
let xhr = new XMLHttpRequest();
|
||||||
uploadPromise(nextFile!)
|
xhr.open("POST", uploadUrl, true);
|
||||||
.then(() => {
|
|
||||||
console.info("done uploading file");
|
const areMultipleFiles = files.length > 1;
|
||||||
})
|
let errorMessage = `An error occurred while uploading the file${
|
||||||
.catch((err) => {
|
areMultipleFiles ? "s" : ""
|
||||||
console.error("error uploading file,", err);
|
}.`;
|
||||||
});
|
|
||||||
}
|
const errorMessages: any = {
|
||||||
|
413: "Error - File size too large",
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.withCredentials = false;
|
||||||
|
xhr.onload = function (event) {
|
||||||
|
// resolve promise only when HTTP code is ok
|
||||||
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
|
completeObject(identity);
|
||||||
|
resolve({ status: xhr.status });
|
||||||
|
} else {
|
||||||
|
// reject promise if there was a server error
|
||||||
|
if (errorMessages[xhr.status]) {
|
||||||
|
errorMessage = errorMessages[xhr.status];
|
||||||
|
} else if (xhr.response) {
|
||||||
|
try {
|
||||||
|
const err = JSON.parse(xhr.response);
|
||||||
|
errorMessage = err.detailedMessage;
|
||||||
|
} catch (e) {
|
||||||
|
errorMessage = "something went wrong";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reject({ status: xhr.status, message: errorMessage });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.upload.addEventListener("error", (event) => {
|
||||||
|
reject(errorMessage);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.upload.addEventListener("progress", (event) => {
|
||||||
|
const progress = Math.floor((event.loaded * 100) / event.total);
|
||||||
|
|
||||||
|
updateProgress(identity, progress);
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.onerror = () => {
|
||||||
|
reject(errorMessage);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
xhr.onloadend = () => {
|
||||||
|
if (files.length === 0) {
|
||||||
|
setLoading(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
if (file.size !== undefined) {
|
||||||
|
formData.append(file.size.toString(), blobFile, fileName);
|
||||||
|
xhr.send(formData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadFilePromises: any = [];
|
||||||
|
// open object manager
|
||||||
|
openList();
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = files[i];
|
||||||
|
uploadFilePromises.push(uploadPromise(file));
|
||||||
}
|
}
|
||||||
|
Promise.allSettled(uploadFilePromises).then((results: Array<any>) => {
|
||||||
|
const errors = results.filter(
|
||||||
|
(result) => result.status === "rejected"
|
||||||
|
);
|
||||||
|
if (errors.length > 0) {
|
||||||
|
const totalFiles = uploadFilePromises.length;
|
||||||
|
const successUploadedFiles =
|
||||||
|
uploadFilePromises.length - errors.length;
|
||||||
|
const err: ErrorResponseHandler = {
|
||||||
|
errorMessage: "There were some errors during file upload",
|
||||||
|
detailedError: `Uploaded files ${successUploadedFiles}/${totalFiles}`,
|
||||||
|
};
|
||||||
|
console.log("upload results", results);
|
||||||
|
setErrorSnackMessage(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
upload(files, bucketName, pathPrefix, folderPath);
|
upload(files, bucketName, pathPrefix, folderPath);
|
||||||
@@ -902,7 +902,7 @@ const ListObjects = ({
|
|||||||
internalPaths,
|
internalPaths,
|
||||||
openList,
|
openList,
|
||||||
setNewObject,
|
setNewObject,
|
||||||
setSnackBarMessage,
|
setErrorSnackMessage,
|
||||||
updateProgress,
|
updateProgress,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const ObjectHandled = ({
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<div
|
<div
|
||||||
className={`${classes.container} ${
|
className={`${classes.container} ${
|
||||||
!objectToDisplay.done ? "inProgress" : ""
|
objectToDisplay.percentage !== 100 ? "inProgress" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className={classes.clearListIcon}>
|
<div className={classes.clearListIcon}>
|
||||||
@@ -129,9 +129,9 @@ const ObjectHandled = ({
|
|||||||
deleteFromList(objectToDisplay.instanceID);
|
deleteFromList(objectToDisplay.instanceID);
|
||||||
}}
|
}}
|
||||||
className={`${classes.closeButton} hideOnProgress showOnHover`}
|
className={`${classes.closeButton} hideOnProgress showOnHover`}
|
||||||
disabled={!objectToDisplay.done}
|
disabled={objectToDisplay.percentage !== 100}
|
||||||
>
|
>
|
||||||
<span className={classes.closeIcon}></span>
|
<span className={classes.closeIcon} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.objectDetails}>
|
<div className={classes.objectDetails}>
|
||||||
|
|||||||
@@ -68,10 +68,18 @@ const ProgressBarWrapper = ({
|
|||||||
withLabel,
|
withLabel,
|
||||||
size = "regular",
|
size = "regular",
|
||||||
}: IProgressBarWrapper) => {
|
}: IProgressBarWrapper) => {
|
||||||
|
let color: any;
|
||||||
|
if (value === 100 && ready) {
|
||||||
|
color = "success";
|
||||||
|
} else if (value === 100 && !ready) {
|
||||||
|
color = "error";
|
||||||
|
} else {
|
||||||
|
color = "primary";
|
||||||
|
}
|
||||||
const propsComponent: LinearProgressProps = {
|
const propsComponent: LinearProgressProps = {
|
||||||
variant: indeterminate && !ready ? "indeterminate" : "determinate",
|
variant: indeterminate && !ready ? "indeterminate" : "determinate",
|
||||||
value: ready ? 100 : value,
|
value: ready ? 100 : value,
|
||||||
color: ready ? "success" : "primary",
|
color: color,
|
||||||
};
|
};
|
||||||
if (withLabel) {
|
if (withLabel) {
|
||||||
return <LinearProgressWithLabel {...propsComponent} />;
|
return <LinearProgressWithLabel {...propsComponent} />;
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ export function objectBrowserReducer(
|
|||||||
};
|
};
|
||||||
case OBJECT_MANAGER_CLEAN_LIST:
|
case OBJECT_MANAGER_CLEAN_LIST:
|
||||||
const nonCompletedList = state.objectManager.objectsToManage.filter(
|
const nonCompletedList = state.objectManager.objectsToManage.filter(
|
||||||
(item) => !item.done
|
(item) => item.percentage !== 100
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user