Skip to content

Commit bc1cb82

Browse files
authored
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>
1 parent 8772c15 commit bc1cb82

File tree

4 files changed

+131
-123
lines changed

4 files changed

+131
-123
lines changed

portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx

Lines changed: 118 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -763,135 +763,135 @@ const ListObjects = ({
763763
path: string,
764764
folderPath: string
765765
) => {
766-
if (files.length > 0) {
767-
openList();
768-
let nextFile = files.pop();
769-
let uploadPromise = (file: File) => {
770-
return new Promise((resolve, reject) => {
771-
let uploadUrl = `api/v1/buckets/${bucketName}/objects/upload`;
772-
const fileName = file.name;
773-
const blobFile = new Blob([file], { type: file.type });
774-
775-
let encodedPath = "";
776-
const relativeFolderPath =
777-
get(file, "webkitRelativePath", "") !== ""
778-
? get(file, "webkitRelativePath", "")
779-
: folderPath;
780-
781-
if (path !== "" || relativeFolderPath !== "") {
782-
const finalFolderPath = relativeFolderPath
783-
.split("/")
784-
.slice(0, -1)
785-
.join("/");
786-
787-
encodedPath = encodeFileName(
788-
`${path}${finalFolderPath}${
789-
!finalFolderPath.endsWith("/") ? "/" : ""
790-
}`
791-
);
792-
}
766+
let uploadPromise = (file: File) => {
767+
return new Promise((resolve, reject) => {
768+
let uploadUrl = `api/v1/buckets/${bucketName}/objects/upload`;
769+
const fileName = file.name;
770+
const blobFile = new Blob([file], { type: file.type });
771+
772+
let encodedPath = "";
773+
const relativeFolderPath =
774+
get(file, "webkitRelativePath", "") !== ""
775+
? get(file, "webkitRelativePath", "")
776+
: folderPath;
777+
778+
if (path !== "" || relativeFolderPath !== "") {
779+
const finalFolderPath = relativeFolderPath
780+
.split("/")
781+
.slice(0, -1)
782+
.join("/");
783+
784+
encodedPath = encodeFileName(
785+
`${path}${finalFolderPath}${
786+
!finalFolderPath.endsWith("/") ? "/" : ""
787+
}`
788+
);
789+
}
793790

794-
if (encodedPath !== "") {
795-
uploadUrl = `${uploadUrl}?prefix=${encodedPath}`;
796-
}
791+
if (encodedPath !== "") {
792+
uploadUrl = `${uploadUrl}?prefix=${encodedPath}`;
793+
}
797794

798-
const identity = encodeFileName(
799-
`${bucketName}-${encodedPath}-${new Date().getTime()}-${Math.random()}`
800-
);
795+
const identity = encodeFileName(
796+
`${bucketName}-${encodedPath}-${new Date().getTime()}-${Math.random()}`
797+
);
798+
799+
setNewObject({
800+
bucketName,
801+
done: false,
802+
instanceID: identity,
803+
percentage: 0,
804+
prefix: `${decodeFileName(encodedPath)}${fileName}`,
805+
type: "upload",
806+
waitingForFile: false,
807+
});
808+
809+
let xhr = new XMLHttpRequest();
810+
xhr.open("POST", uploadUrl, true);
801811

802-
setNewObject({
803-
bucketName,
804-
done: false,
805-
instanceID: identity,
806-
percentage: 0,
807-
prefix: `${decodeFileName(encodedPath)}${fileName}`,
808-
type: "upload",
809-
waitingForFile: false,
810-
});
811-
812-
let xhr = new XMLHttpRequest();
813-
xhr.open("POST", uploadUrl, true);
814-
815-
const areMultipleFiles = files.length > 1;
816-
const errorMessage = `An error occurred while uploading the file${
817-
areMultipleFiles ? "s" : ""
818-
}.`;
819-
const okMessage = `Object${
820-
areMultipleFiles ? "s" : ``
821-
} uploaded successfully.`;
822-
823-
xhr.withCredentials = false;
824-
xhr.onload = function (event) {
825-
if (
826-
xhr.status === 401 ||
827-
xhr.status === 403 ||
828-
xhr.status === 400 ||
829-
xhr.status === 500
830-
) {
831-
if (xhr.response) {
812+
const areMultipleFiles = files.length > 1;
813+
let errorMessage = `An error occurred while uploading the file${
814+
areMultipleFiles ? "s" : ""
815+
}.`;
816+
817+
const errorMessages: any = {
818+
413: "Error - File size too large",
819+
};
820+
821+
xhr.withCredentials = false;
822+
xhr.onload = function (event) {
823+
// resolve promise only when HTTP code is ok
824+
if (xhr.status >= 200 && xhr.status < 300) {
825+
completeObject(identity);
826+
resolve({ status: xhr.status });
827+
} else {
828+
// reject promise if there was a server error
829+
if (errorMessages[xhr.status]) {
830+
errorMessage = errorMessages[xhr.status];
831+
} else if (xhr.response) {
832+
try {
832833
const err = JSON.parse(xhr.response);
833-
setSnackBarMessage(err.detailedMessage);
834-
} else {
835-
setSnackBarMessage(errorMessage);
836-
}
837-
}
838-
if (xhr.status === 413) {
839-
setSnackBarMessage("Error - File size too large");
840-
}
841-
if (xhr.status === 200) {
842-
completeObject(identity);
843-
if (files.length === 0) {
844-
setSnackBarMessage(okMessage);
845-
}
846-
}
847-
resolve(xhr.status);
848-
if (files.length > 0) {
849-
let nFile = files.pop();
850-
if (nFile) {
851-
return uploadPromise(nFile);
834+
errorMessage = err.detailedMessage;
835+
} catch (e) {
836+
errorMessage = "something went wrong";
852837
}
853838
}
854-
};
855-
856-
xhr.upload.addEventListener("error", (event) => {
857-
setSnackBarMessage(errorMessage);
858-
});
859-
860-
xhr.upload.addEventListener("progress", (event) => {
861-
const progress = Math.floor((event.loaded * 100) / event.total);
839+
reject({ status: xhr.status, message: errorMessage });
840+
}
841+
};
862842

863-
updateProgress(identity, progress);
864-
});
843+
xhr.upload.addEventListener("error", (event) => {
844+
reject(errorMessage);
845+
return;
846+
});
865847

866-
xhr.onerror = () => {
867-
setSnackBarMessage(errorMessage);
868-
reject(errorMessage);
869-
};
870-
xhr.onloadend = () => {
871-
if (files.length === 0) {
872-
setLoading(true);
873-
}
874-
};
848+
xhr.upload.addEventListener("progress", (event) => {
849+
const progress = Math.floor((event.loaded * 100) / event.total);
875850

876-
const formData = new FormData();
877-
if (file.size !== undefined) {
878-
formData.append(file.size.toString(), blobFile, fileName);
851+
updateProgress(identity, progress);
852+
});
879853

880-
xhr.send(formData);
854+
xhr.onerror = () => {
855+
reject(errorMessage);
856+
return;
857+
};
858+
xhr.onloadend = () => {
859+
if (files.length === 0) {
860+
setLoading(true);
881861
}
882-
});
883-
};
884-
885-
if (nextFile) {
886-
uploadPromise(nextFile!)
887-
.then(() => {
888-
console.info("done uploading file");
889-
})
890-
.catch((err) => {
891-
console.error("error uploading file,", err);
892-
});
893-
}
862+
};
863+
864+
const formData = new FormData();
865+
if (file.size !== undefined) {
866+
formData.append(file.size.toString(), blobFile, fileName);
867+
xhr.send(formData);
868+
}
869+
});
870+
};
871+
872+
const uploadFilePromises: any = [];
873+
// open object manager
874+
openList();
875+
for (let i = 0; i < files.length; i++) {
876+
const file = files[i];
877+
uploadFilePromises.push(uploadPromise(file));
894878
}
879+
Promise.allSettled(uploadFilePromises).then((results: Array<any>) => {
880+
const errors = results.filter(
881+
(result) => result.status === "rejected"
882+
);
883+
if (errors.length > 0) {
884+
const totalFiles = uploadFilePromises.length;
885+
const successUploadedFiles =
886+
uploadFilePromises.length - errors.length;
887+
const err: ErrorResponseHandler = {
888+
errorMessage: "There were some errors during file upload",
889+
detailedError: `Uploaded files ${successUploadedFiles}/${totalFiles}`,
890+
};
891+
console.log("upload results", results);
892+
setErrorSnackMessage(err);
893+
}
894+
});
895895
};
896896

897897
upload(files, bucketName, pathPrefix, folderPath);
@@ -902,7 +902,7 @@ const ListObjects = ({
902902
internalPaths,
903903
openList,
904904
setNewObject,
905-
setSnackBarMessage,
905+
setErrorSnackMessage,
906906
updateProgress,
907907
]
908908
);

portal-ui/src/screens/Console/Common/ObjectManager/ObjectHandled.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ const ObjectHandled = ({
120120
<Fragment>
121121
<div
122122
className={`${classes.container} ${
123-
!objectToDisplay.done ? "inProgress" : ""
123+
objectToDisplay.percentage !== 100 ? "inProgress" : ""
124124
}`}
125125
>
126126
<div className={classes.clearListIcon}>
@@ -129,9 +129,9 @@ const ObjectHandled = ({
129129
deleteFromList(objectToDisplay.instanceID);
130130
}}
131131
className={`${classes.closeButton} hideOnProgress showOnHover`}
132-
disabled={!objectToDisplay.done}
132+
disabled={objectToDisplay.percentage !== 100}
133133
>
134-
<span className={classes.closeIcon}></span>
134+
<span className={classes.closeIcon} />
135135
</button>
136136
</div>
137137
<div className={classes.objectDetails}>

portal-ui/src/screens/Console/Common/ProgressBarWrapper/ProgressBarWrapper.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,18 @@ const ProgressBarWrapper = ({
6868
withLabel,
6969
size = "regular",
7070
}: IProgressBarWrapper) => {
71+
let color: any;
72+
if (value === 100 && ready) {
73+
color = "success";
74+
} else if (value === 100 && !ready) {
75+
color = "error";
76+
} else {
77+
color = "primary";
78+
}
7179
const propsComponent: LinearProgressProps = {
7280
variant: indeterminate && !ready ? "indeterminate" : "determinate",
7381
value: ready ? 100 : value,
74-
color: ready ? "success" : "primary",
82+
color: color,
7583
};
7684
if (withLabel) {
7785
return <LinearProgressWithLabel {...propsComponent} />;

portal-ui/src/screens/Console/ObjectBrowser/reducers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export function objectBrowserReducer(
202202
};
203203
case OBJECT_MANAGER_CLEAN_LIST:
204204
const nonCompletedList = state.objectManager.objectsToManage.filter(
205-
(item) => !item.done
205+
(item) => item.percentage !== 100
206206
);
207207

208208
return {

0 commit comments

Comments
 (0)