diff --git a/.storybook/msw-mock.js b/.storybook/msw-mock.js index b02f0da..aa5c017 100644 --- a/.storybook/msw-mock.js +++ b/.storybook/msw-mock.js @@ -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, diff --git a/src/components/file-management-dialog/UppyDashboardDialog.jsx b/src/components/file-management-dialog/UppyDashboardDialog.jsx index 8e7ed11..84b227e 100644 --- a/src/components/file-management-dialog/UppyDashboardDialog.jsx +++ b/src/components/file-management-dialog/UppyDashboardDialog.jsx @@ -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) { diff --git a/src/utils/helpers.js b/src/utils/helpers.js index e0a0de5..c9ddd39 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -17,3 +17,7 @@ export function waitForElement(selector) { }); }); } + +export function isString(value) { + return typeof value === 'string' || value instanceof String; +} \ No newline at end of file diff --git a/src/utils/uppy-plugins/oarepo-upload/index.js b/src/utils/uppy-plugins/oarepo-upload/index.js index b238125..f2cb091 100644 --- a/src/utils/uppy-plugins/oarepo-upload/index.js +++ b/src/utils/uppy-plugins/oarepo-upload/index.js @@ -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