Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎨 Frontend: Improve UX for "Download of Study Data" #3963

Merged
merged 18 commits into from
Mar 13, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ qx.Class.define("osparc.Application", {
// from osparc. unfortunately it is not possible
// to provide our own message here
window.addEventListener("beforeunload", e => {
if (webSocket.isConnected()) {
const downloadLinkTracker = osparc.DownloadLinkTracker.getInstance();
// The downloadLinkTracker uses an external link for downloading files.
// When it starts (click), triggers an unload event. This condition avoids the false positive
if (!downloadLinkTracker.isDownloading() && webSocket.isConnected()) {
// Cancel the event as stated by the standard.
e.preventDefault();
// Chrome requires returnValue to be set.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* ************************************************************************

osparc - the simcore frontend

https://osparc.io

Copyright:
2023 IT'IS Foundation, https://itis.swiss

License:
MIT: https://opensource.org/licenses/MIT

Authors:
* Odei Maiz (odeimaiz)

************************************************************************ */

qx.Class.define("osparc.DownloadLinkTracker", {
extend: qx.core.Object,
type: "singleton",

properties: {
downloading: {
check: "Boolean",
init: false,
nullable: true
}
},

members: {
downloadLinkUnattended: function(url, fileName) {
const downloadAnchorNode = document.createElement("a");
downloadAnchorNode.setAttribute("href", url);
downloadAnchorNode.setAttribute("download", fileName);
downloadAnchorNode.setAttribute("osparc", "downloadFile");
this.setDownloading(true);
downloadAnchorNode.click();
this.setDownloading(false);
downloadAnchorNode.remove();
}
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -877,11 +877,11 @@ qx.Class.define("osparc.dashboard.StudyBrowser", {
osparc.component.message.FlashMessenger.getInstance().logAs(text, "INFO");

const url = window.location.href + "v0/projects/" + studyData["uuid"] + ":xport";
const downloadStartedCB = () => {
const progressCB = () => {
const textSuccess = this.tr("Download started");
exportTask.setSubtitle(textSuccess);
};
osparc.utils.Utils.downloadLink(url, "POST", null, downloadStartedCB)
osparc.utils.Utils.downloadLink(url, "POST", null, progressCB)
.catch(e => {
const msg = osparc.data.Resources.getErrorMsg(JSON.parse(e.response)) || this.tr("Something went wrong Exporting the study");
osparc.component.message.FlashMessenger.logAs(msg, "ERROR");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,12 @@ qx.Class.define("osparc.file.FileLabelWithActions", {
if (selection) {
const fileId = selection.getFileId();
const locationId = selection.getLocation();
osparc.utils.Utils.retrieveURLAndDownload(locationId, fileId);
osparc.utils.Utils.retrieveURLAndDownload(locationId, fileId)
.then(data => {
if (data) {
osparc.DownloadLinkTracker.getInstance().downloadLinkUnattended(data.link, data.fileName);
}
});
}
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,20 +217,27 @@ qx.Class.define("osparc.file.FilePicker", {
}
},

downloadOutput: function(node) {
downloadOutput: function(node, downloadFileBtn) {
const progressCb = () => downloadFileBtn.setFetching(true);
const loadedCb = () => downloadFileBtn.setFetching(false);
if (osparc.file.FilePicker.isOutputFromStore(node.getOutputs())) {
this.self().getOutputFileMetadata(node)
.then(fileMetadata => {
if ("location_id" in fileMetadata && "file_id" in fileMetadata) {
const locationId = fileMetadata["location_id"];
const fileId = fileMetadata["file_id"];
osparc.utils.Utils.retrieveURLAndDownload(locationId, fileId);
osparc.utils.Utils.retrieveURLAndDownload(locationId, fileId)
.then(data => {
if (data) {
osparc.utils.Utils.downloadLink(data.link, "GET", data.fileName, progressCb, loadedCb);
}
});
}
});
} else if (osparc.file.FilePicker.isOutputDownloadLink(node.getOutputs())) {
const outFileValue = osparc.file.FilePicker.getOutput(node.getOutputs());
if (osparc.utils.Utils.isObject(outFileValue) && "downloadLink" in outFileValue) {
osparc.utils.Utils.downloadLink(outFileValue["downloadLink"], "GET");
osparc.utils.Utils.downloadLink(outFileValue["downloadLink"], "GET", null, progressCb, loadedCb);
}
}
},
Expand Down Expand Up @@ -347,10 +354,10 @@ qx.Class.define("osparc.file.FilePicker", {

__getDownloadFileButton: function() {
const node = this.getNode();
const downloadFileBtn = new qx.ui.form.Button(this.tr("Download"), "@FontAwesome5Solid/cloud-download-alt/14").set({
const downloadFileBtn = new osparc.ui.form.FetchButton(this.tr("Download"), "@FontAwesome5Solid/cloud-download-alt/14").set({
allowGrowX: false
});
downloadFileBtn.addListener("execute", () => osparc.file.FilePicker.downloadOutput(node));
downloadFileBtn.addListener("execute", () => osparc.file.FilePicker.downloadOutput(node, downloadFileBtn));
return downloadFileBtn;
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,28 +433,35 @@ qx.Class.define("osparc.utils.Utils", {
},

retrieveURLAndDownload: function(locationId, fileId) {
let fileName = fileId.split("/");
fileName = fileName[fileName.length-1];
const download = true;
const dataStore = osparc.store.Data.getInstance();
dataStore.getPresignedLink(download, locationId, fileId)
.then(presignedLinkData => {
if (presignedLinkData.resp) {
const link = presignedLinkData.resp.link;
const fileNameFromLink = this.fileNameFromPresignedLink(link);
fileName = fileNameFromLink ? fileNameFromLink : fileName;
this.downloadLink(link, "GET", fileName);
}
});
return new Promise((resolve, reject) => {
let fileName = fileId.split("/");
fileName = fileName[fileName.length-1];
const download = true;
const dataStore = osparc.store.Data.getInstance();
dataStore.getPresignedLink(download, locationId, fileId)
.then(presignedLinkData => {
if (presignedLinkData.resp) {
const link = presignedLinkData.resp.link;
const fileNameFromLink = this.fileNameFromPresignedLink(link);
fileName = fileNameFromLink ? fileNameFromLink : fileName;
resolve({
link,
fileName
});
} else {
resolve(null);
}
})
.catch(err => reject(err));
});
},

downloadLink: function(url, method, fileName, downloadStartedCB) {
downloadLink: function(url, method, fileName, progressCb, loadedCb) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.responseType = "blob";
xhr.addEventListener("readystatechange", () => {
// xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
// The responseType value can be changed at any time before the readyState reaches 3.
// When the readyState reaches 2, we have access to the response headers to make that decision with.
Expand All @@ -466,19 +473,22 @@ qx.Class.define("osparc.utils.Utils", {
}
}
});
xhr.addEventListener("progress", () => {
xhr.addEventListener("progress", e => {
if (xhr.readyState === XMLHttpRequest.LOADING) {
if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 400)) {
if (downloadStartedCB) {
downloadStartedCB();
if (e["type"] === "progress" && progressCb) {
progressCb(e.loaded / e.total);
}
}
}
});
xhr.addEventListener("load", () => {
if (xhr.status == 200) {
let blob = new Blob([xhr.response]);
let urlBlob = window.URL.createObjectURL(blob);
if (loadedCb) {
loadedCb();
}
const blob = new Blob([xhr.response]);
const urlBlob = window.URL.createObjectURL(blob);
if (!fileName) {
fileName = this.self().filenameFromContentDisposition(xhr);
}
Expand Down
1 change: 0 additions & 1 deletion tests/e2e/utils/auto.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,6 @@ async function deleteFirstStudy(page, studyName) {
const firstChildId = '[osparc-test-id="' + studyCardId + '"]';
const studyCardStyle = await utils.getStyle(page, firstChildId);
if (studyCardStyle.cursor === "not-allowed") {
console.log("Andrei you were wrong");
return false;
}
await utils.waitAndClick(page, firstChildId + ' > [osparc-test-id="studyItemMenuButton"]');
Expand Down