Skip to content

Commit

Permalink
Fix error handling and successful/error callbacks with custom logging…
Browse files Browse the repository at this point in the history
… for each upload stage
  • Loading branch information
corovcam committed May 22, 2024

Verified

This commit was signed with the committer’s verified signature.
btjanaka Bryon Tjanaka
1 parent f4d3e15 commit bb0fce5
Showing 4 changed files with 95 additions and 51 deletions.
33 changes: 22 additions & 11 deletions .storybook/msw-mock.js
Original file line number Diff line number Diff line change
@@ -135,18 +135,9 @@ const handlers = [
// Invalid file name being a trigger for server error
if (body.some((obj) => obj.key.startsWith("invalid"))) {
return res(
ctx.status(500),
ctx.status(400),
ctx.json({
key: body[0].key,
updated: "2020-11-27 11:17:11.002624",
created: "2020-11-27 11:17:10.998919",
metadata: null,
status: "pending",
links: {
content: `/api/records/${query.id}/draft/files/invalid.sh/content`,
self: `/api/records/${query.id}/draft/files/invalid.sh`,
commit: `/api/records/${query.id}/draft/files/invalid.sh/commit`,
},
"message": `File with key ${body[0].key} already exists.`
})
);
}
@@ -182,6 +173,26 @@ const handlers = [
async (req, res, ctx) => {
const body = await req.json();
const query = req.params;

// Invalid file name being a trigger for server error
// if (query.fileName.startsWith("invalid")) {
// return res(
// ctx.status(500),
// ctx.json({
// key: query.fileName,
// updated: "2020-11-27 11:17:11.002624",
// created: "2020-11-27 11:17:10.998919",
// metadata: null,
// status: "pending",
// links: {
// content: `/api/records/${query.id}/draft/files/invalid.sh/content`,
// self: `/api/records/${query.id}/draft/files/invalid.sh`,
// commit: `/api/records/${query.id}/draft/files/invalid.sh/commit`,
// },
// })
// );
// }

return res(
ctx.json({
key: query.fileName,
Original file line number Diff line number Diff line change
@@ -102,7 +102,8 @@ const UppyDashboardDialog = ({
strings: Object.assign(cs_CZ.strings, czechLocale.strings)
} : {
strings: Object.assign(en_US.strings, englishLocale.strings)
}
};

uppy.setOptions({
...extraUppyCoreSettings,
debug: debug,
@@ -162,10 +163,12 @@ const UppyDashboardDialog = ({
}
: () => true,
});

uppy.on('complete', (result) => {
result.successful && result.successful.length > 0 && onSuccessfulUpload(result.successful);
result.failed && result.failed.length > 0 && onFailedUpload(result.failed);
});

if (startEvent?.event == "edit-file") {
uppy.on("dashboard:file-edit-complete", (file) => {
if (file) {
4 changes: 4 additions & 0 deletions src/utils/helpers.js
Original file line number Diff line number Diff line change
@@ -17,3 +17,7 @@ export function waitForElement(selector) {
});
});
}

export function isString(value) {
return typeof value === 'string' || value instanceof String;
}
104 changes: 65 additions & 39 deletions src/utils/uppy-plugins/oarepo-upload/index.js
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import { filterNonFailedFiles, filterFilesToEmitUploadStarted } from '@uppy/util

import packageJson from './package.json'
import locale from './locale.js'
import { isString } from '../../helpers'

function buildResponseError(xhr, err) {
let error = err
@@ -22,8 +23,9 @@ function buildResponseError(xhr, err) {
}

if (isNetworkError(xhr)) {
error = new NetworkError(error, xhr)
return error
const networkError = new NetworkError(error, xhr)
if (error?.message) networkError.message = error.message
return networkError
}

error.request = xhr
@@ -89,11 +91,25 @@ export default class OARepoUpload extends BasePlugin {
* @param {string} _ the response body string
* @param {XMLHttpRequest | respObj} response the response object (XHR or similar)
*/
getResponseError(_, response) {
let error = new Error('Upload error')
getResponseError(responseText, response) {
let error;
try {
const json = JSON.parse(responseText)
if ("message" in json) {
error = new Error(isString(json.message) ? json.message : JSON.stringify(json.message))
}
// NOTE: Not displaying the server error message to the user
// else {
// error = new Error(JSON.stringify(json))
// }
} catch (e) {
error = new Error((responseText && isString(responseText) && responseText !== "") ? responseText : 'Upload error')
}

if (isNetworkError(response)) {
error = new NetworkError(error, response)
const networkError = new NetworkError(error, response)
if (error?.message) networkError.message = error.message
return networkError
}

return error
@@ -158,6 +174,33 @@ export default class OARepoUpload extends BasePlugin {
return opts
}

async #startFileUpload(file, opts, uploadId) {
return fetch(this.opts.endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify([{
key: file.name,
}]),
})
.then(async (response) => {
const responseText = await response.text()
const body = opts.getResponseData(responseText, response)
const uppyResponse = {
status: response.status,
body,
}
if (!opts.validateStatus(response.status)) {
const error = buildResponseError(response, opts.getResponseError(responseText, response))
this.uppy.emit('upload-error', file, error, uppyResponse)
throw error
}
file.response = uppyResponse
this.uppy.log(`[OARepoUpload] [UploadID: ${uploadId}] File ${file.name} upload started. Server response:\n${JSON.stringify(body, null, 2)}`);
})
}

async #uploadFileMetadata(file, metadata, opts, uploadId) {
return fetch(`${opts.endpoint}/${file.name}`, {
method: "PUT",
@@ -178,15 +221,19 @@ export default class OARepoUpload extends BasePlugin {
}),
})
.then(async (response) => {
if (!this.opts.validateStatus(response.status)) {
const error = buildResponseError(response, opts.getResponseError(await response.text(), response));
this.uppy.emit('upload-error', file, error);
throw error;
const responseText = await response.text()
const body = opts.getResponseData(responseText, response)
const uppyResponse = {
status: response.status,
body,
}
return response.json();
})
.then((data) => {
this.uppy.log(`[OARepoUpload] ${uploadId} file metadata uploaded. Server response:\n${JSON.stringify(data, null, 2)}`);
if (!opts.validateStatus(response.status)) {
const error = buildResponseError(response, opts.getResponseError(responseText, response))
this.uppy.emit('upload-error', file, error, uppyResponse)
throw error
}
file.response = uppyResponse
this.uppy.log(`[OARepoUpload] [UploadID: ${uploadId}] File ${file.name} metadata uploaded. Server response:\n${JSON.stringify(body, null, 2)}`);
})
}

@@ -218,7 +265,7 @@ export default class OARepoUpload extends BasePlugin {
if (uploadURL) {
this.uppy.log(`Download ${file.name} from ${uploadURL}`)
}
this.uppy.log(`[OARepoUpload] ${uploadId} file upload successful. Server response:\n${JSON.stringify(data, null, 2)}`);
this.uppy.log(`[OARepoUpload] [UploadID: ${uploadId}] File ${file.name} upload successful. Server response:\n${JSON.stringify(data, null, 2)}`);
})
}

@@ -341,6 +388,7 @@ export default class OARepoUpload extends BasePlugin {

const chainedRequests = async () => {
try {
await this.#startFileUpload(file, opts, uploadId)
await this.#uploadFileMetadata(file, file.meta, opts, uploadId)
await xhrContentPromise
await this.#completeFileUpload(file, opts, uploadId)
@@ -360,42 +408,20 @@ export default class OARepoUpload extends BasePlugin {
})
.then(async (response) => {
if (!this.opts.validateStatus(response.status)) {
throw buildResponseError(response, opts.getResponseError(await response.text(), response))
const error = buildResponseError(response, opts.getResponseError(await response.text(), response))
this.uppy.emit('upload-error', file, error)
throw error
}
this.uppy.log(`[OARepoUpload] ${file.name} successfully deleted.`);
})
}

async #startFilesUpload(files) {
return fetch(this.opts.endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(
files.map((file) => ({
key: file.name,
}))
),
})
.then(async (response) => {
if (!this.opts.validateStatus(response.status)) {
throw buildResponseError(response, this.opts.getResponseError(await response.text(), response))
}
return response.json();
})
.then((data) => {
this.uppy.log(data);
})
}

async #uploadFiles(files) {
if (this.opts.deleteBeforeUpload) {
await Promise.all(files.map((file) =>
this.#deleteFile(file, this.opts))
)
}
await this.#startFilesUpload(files)
await Promise.allSettled(files.map((file, i) => {
const current = parseInt(i, 10) + 1
const total = files.length

0 comments on commit bb0fce5

Please sign in to comment.