Skip to content

Commit 2e00347

Browse files
committed
Bring file uploads and new file notifications through the API client
1 parent 39674e0 commit 2e00347

File tree

3 files changed

+87
-57
lines changed

3 files changed

+87
-57
lines changed

src/APIInterface.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ export class APIInterface {
2424
throw new Error("subscribeToFilenameChanges function not implemented");
2525
}
2626

27+
// Upload a file.
28+
// fileType is a track type like "graph" or "read".
29+
// file is the file data (Blob or File).
30+
// cancelSignal is an AbortSignal that can be used to cancel the upload.
31+
// Resolves with the file name that can be used to refer to the uploaded file.
32+
async putFile(fileType, file, cancelSignal) {
33+
throw new Error("putFile function not implemented");
34+
}
35+
2736
// Takes in a bedfile path or a url pointing to a raw bed file.
2837
// Returns object with key: bedRegions.
2938
// bedRegions contains information extrapolated from each line of the bedfile.

src/ServerAPI.mjs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ export class ServerAPI extends APIInterface {
3737

3838
subscribeToFilenameChanges(handler, cancelSignal) {
3939
// We need something to hold the one currently active websocket.
40-
subscription = {};
40+
let subscription = {};
4141

4242
// We make a function to connect the websocket, which we can call to reconnect.
43-
function connect() {
43+
let connect = () => {
4444
subscription.ws = new WebSocket(this.apiUrl.replace(/^http/, "ws"));
4545
subscription.ws.onmessage = (message) => {
4646
if (!cancelSignal.aborted) {
@@ -70,6 +70,66 @@ export class ServerAPI extends APIInterface {
7070
return subscription;
7171
}
7272

73+
async putFile(fileType, file, cancelSignal) {
74+
75+
// Prepare the form data for upload
76+
const formData = new FormData();
77+
// If the file is anything other than a Blob, it will be turned into a
78+
// string and added as a normal form value. If it is a Blob it will
79+
// become a file upload. Note that a File is a kind of Blob. See
80+
// <https://developer.mozilla.org/en-US/docs/Web/API/FormData/append#value>
81+
//
82+
// But in jsdom in the test environment there are two Blob types: Node's
83+
// and jdsom's, and only jsdom's will work. Node's will turn into a
84+
// string. And it seems hard to get at both types in a way that makes
85+
// sense in a browser. So we will add the file and make sure it added OK
86+
// and didn't stringify.
87+
88+
// According to <https://stackoverflow.com/a/43914175>, we *must* set a filename for uploads.
89+
// In jsdom it turns on jsdom's own type checking support.
90+
let fileName = file.name || "upload.dat";
91+
formData.append("trackFile", file, fileName);
92+
if (typeof formData.get("trackFile") == "string") {
93+
// Catch stringification in case jsdom didn't.
94+
console.error(
95+
"Cannot upload file because it is not the appropriate type:",
96+
file
97+
);
98+
throw new Error("File is not an appropriate type to upload");
99+
}
100+
// Make sure server can identify a Read file
101+
formData.append("fileType", fileType);
102+
103+
return new Promise((resolve, reject) => {
104+
const xhr = new XMLHttpRequest();
105+
xhr.responseType = "json";
106+
xhr.onreadystatechange = () => {
107+
if (cancelSignal.aborted && xhr.readyState != 0) {
108+
// First time we have noticed we are aborted. Stop the request.
109+
xhr.abort();
110+
reject(new Error("Upload aborted"));
111+
return
112+
}
113+
114+
if (xhr.readyState === 4) {
115+
if (xhr.status === 200 && xhr.response.path) {
116+
// Every thing ok, file uploaded, and we got a path.
117+
resolve(xhr.response.path);
118+
} else {
119+
// Something weird happened.
120+
reject(new Error("Failed to upload file: status " + xhr.status + " and response: " + xhr.response));
121+
}
122+
}
123+
};
124+
125+
console.log("Uploading file", file);
126+
console.log("Sending form data", formData);
127+
console.log("Form file is a " + typeof formData.get("trackFile"));
128+
xhr.open("POST", `${this.apiUrl}/trackFileSubmission`, true);
129+
xhr.send(formData);
130+
});
131+
}
132+
73133
async getBedRegions(bedFile, cancelSignal) {
74134
const json = await fetchAndParse(`${this.apiUrl}/getBedRegions`, {
75135
signal: cancelSignal,

src/components/HeaderForm.js

Lines changed: 16 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -645,62 +645,23 @@ class HeaderForm extends Component {
645645

646646
// Sends uploaded file to server and returns a path to the file
647647
handleFileUpload = async (fileType, file) => {
648-
return new Promise(
649-
function (resolve, reject) {
650-
if (file.size > config.MAXUPLOADSIZE) {
651-
this.showFileSizeAlert();
652-
return;
653-
}
654-
655-
this.setUploadInProgress(true);
656-
657-
const formData = new FormData();
658-
// If the file is anything other than a Blob, it will be turned into a
659-
// string and added as a normal form value. If it is a Blob it will
660-
// become a file upload. Note that a File is a kind of Blob. See
661-
// <https://developer.mozilla.org/en-US/docs/Web/API/FormData/append#value>
662-
//
663-
// But in jsdom in the test environment there are two Blob types: Node's
664-
// and jdsom's, and only jsdom's will work. Node's will turn into a
665-
// string. And it seems hard to get at both types in a way that makes
666-
// sense in a browser. So we will add the file and make sure it added OK
667-
// and didn't stringify.
668-
669-
// According to <https://stackoverflow.com/a/43914175>, we *must* set a filename for uploads.
670-
// In jsdom it turns on jsdom's own type checking support.
671-
let fileName = file.name || "upload.dat";
672-
formData.append("trackFile", file, fileName);
673-
if (typeof formData.get("trackFile") == "string") {
674-
// Catch stringification in case jsdom didn't.
675-
console.error(
676-
"Cannot upload file because it is not the appropriate type:",
677-
file
678-
);
679-
throw new Error("File is not an appropriate type to upload");
680-
}
681-
// Make sure server can identify a Read file
682-
formData.append("fileType", fileType);
683-
const xhr = new XMLHttpRequest();
684-
xhr.responseType = "json";
685-
xhr.onreadystatechange = () => {
686-
if (xhr.readyState === 4 && xhr.status === 200) {
687-
// Every thing ok, file uploaded
688-
this.setUploadInProgress(false);
689-
if (fileType === "graph") {
690-
this.getPathNames(xhr.response.path);
691-
}
692-
693-
resolve(xhr.response.path);
694-
}
695-
};
648+
if (file.size > config.MAXUPLOADSIZE) {
649+
this.showFileSizeAlert();
650+
return;
651+
}
696652

697-
console.log("Uploading file", file);
698-
console.log("Sending form data", formData);
699-
console.log("Form file is a " + typeof formData.get("trackFile"));
700-
xhr.open("POST", `${this.props.apiUrl}/trackFileSubmission`, true);
701-
xhr.send(formData);
702-
}.bind(this)
703-
);
653+
this.setUploadInProgress(true);
654+
655+
try {
656+
let fileName = await this.api.putFile(fileType, file, this.cancelSignal);
657+
this.setUploadInProgress(false);
658+
return fileName;
659+
} catch (e) {
660+
if (!this.cancelSignal.aborted) {
661+
// Only pass along errors if we haven't canceled our fetches.
662+
throw e;
663+
}
664+
}
704665
};
705666

706667
setUpWebsocket = () => {

0 commit comments

Comments
 (0)