mirror of
https://github.com/discourse/discourse.git
synced 2024-11-27 08:33:39 +08:00
FEATURE: Buffer file names of failed uploads when bulk uploading (#25068)
Currently, when bulk uploading and multiple uploads fail, we show a number of dialogs in quick succession. This is of course a terrible user experience. With this change, we buffer the error messages until there are no more pending uploads. Then we combine the buffered errors and display a single dialog with a list of failed files.
This commit is contained in:
parent
b4a89ea610
commit
a0fbce996a
|
@ -4,6 +4,7 @@ import { sanitize as textSanitize } from "pretty-text/sanitizer";
|
||||||
import deprecated from "discourse-common/lib/deprecated";
|
import deprecated from "discourse-common/lib/deprecated";
|
||||||
import { getURLWithCDN } from "discourse-common/lib/get-url";
|
import { getURLWithCDN } from "discourse-common/lib/get-url";
|
||||||
import { helperContext } from "discourse-common/lib/helpers";
|
import { helperContext } from "discourse-common/lib/helpers";
|
||||||
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
async function withEngine(name, ...args) {
|
async function withEngine(name, ...args) {
|
||||||
const engine = await import("discourse/static/markdown-it");
|
const engine = await import("discourse/static/markdown-it");
|
||||||
|
@ -136,3 +137,18 @@ export function excerpt(cooked, length) {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function humanizeList(listItems) {
|
||||||
|
const items = Array.from(listItems);
|
||||||
|
const last = items.pop();
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
return last;
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
items.join(I18n.t("word_connector.comma")),
|
||||||
|
I18n.t("word_connector.last_item"),
|
||||||
|
last,
|
||||||
|
].join(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { humanizeList } from "discourse/lib/text";
|
||||||
import { isAppleDevice } from "discourse/lib/utilities";
|
import { isAppleDevice } from "discourse/lib/utilities";
|
||||||
import deprecated from "discourse-common/lib/deprecated";
|
import deprecated from "discourse-common/lib/deprecated";
|
||||||
import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
|
import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
|
||||||
|
@ -302,6 +303,12 @@ export function getUploadMarkdown(upload) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function displayErrorForBulkUpload(errors) {
|
||||||
|
const fileNames = humanizeList(errors.mapBy("fileName"));
|
||||||
|
|
||||||
|
dialog.alert(I18n.t("post.errors.upload", { file_name: fileNames }));
|
||||||
|
}
|
||||||
|
|
||||||
export function displayErrorForUpload(data, siteSettings, fileName) {
|
export function displayErrorForUpload(data, siteSettings, fileName) {
|
||||||
if (!fileName) {
|
if (!fileName) {
|
||||||
deprecated(
|
deprecated(
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { cacheShortUploadUrl } from "pretty-text/upload-short-url";
|
||||||
import { updateCsrfToken } from "discourse/lib/ajax";
|
import { updateCsrfToken } from "discourse/lib/ajax";
|
||||||
import {
|
import {
|
||||||
bindFileInputChangeListener,
|
bindFileInputChangeListener,
|
||||||
|
displayErrorForBulkUpload,
|
||||||
displayErrorForUpload,
|
displayErrorForUpload,
|
||||||
getUploadMarkdown,
|
getUploadMarkdown,
|
||||||
validateUploadedFile,
|
validateUploadedFile,
|
||||||
|
@ -99,6 +100,7 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, {
|
||||||
|
|
||||||
_bindUploadTarget() {
|
_bindUploadTarget() {
|
||||||
this.set("inProgressUploads", []);
|
this.set("inProgressUploads", []);
|
||||||
|
this.set("bufferedUploadErrors", []);
|
||||||
this.placeholders = {};
|
this.placeholders = {};
|
||||||
this._preProcessorStatus = {};
|
this._preProcessorStatus = {};
|
||||||
this.editorEl = this.element.querySelector(this.editorClass);
|
this.editorEl = this.element.querySelector(this.editorClass);
|
||||||
|
@ -352,6 +354,7 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, {
|
||||||
this.appEvents.trigger(
|
this.appEvents.trigger(
|
||||||
`${this.composerEventPrefix}:all-uploads-complete`
|
`${this.composerEventPrefix}:all-uploads-complete`
|
||||||
);
|
);
|
||||||
|
this._displayBufferedErrors();
|
||||||
this._reset();
|
this._reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -403,11 +406,11 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, {
|
||||||
file.meta.error = error;
|
file.meta.error = error;
|
||||||
|
|
||||||
if (!this.userCancelled) {
|
if (!this.userCancelled) {
|
||||||
displayErrorForUpload(response || error, this.siteSettings, file.name);
|
this._bufferUploadError(response || error, file.name);
|
||||||
this.appEvents.trigger(`${this.composerEventPrefix}:upload-error`, file);
|
this.appEvents.trigger(`${this.composerEventPrefix}:upload-error`, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.inProgressUploads.length === 0) {
|
if (this.inProgressUploads.length === 0) {
|
||||||
|
this._displayBufferedErrors();
|
||||||
this._reset();
|
this._reset();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -419,6 +422,24 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_displayBufferedErrors() {
|
||||||
|
if (this.bufferedUploadErrors.length === 0) {
|
||||||
|
return;
|
||||||
|
} else if (this.bufferedUploadErrors.length === 1) {
|
||||||
|
displayErrorForUpload(
|
||||||
|
this.bufferedUploadErrors[0].data,
|
||||||
|
this.siteSettings,
|
||||||
|
this.bufferedUploadErrors[0].fileName
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
displayErrorForBulkUpload(this.bufferedUploadErrors);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_bufferUploadError(data, fileName) {
|
||||||
|
this.bufferedUploadErrors.push({ data, fileName });
|
||||||
|
},
|
||||||
|
|
||||||
_setupPreProcessors() {
|
_setupPreProcessors() {
|
||||||
const checksumPreProcessor = {
|
const checksumPreProcessor = {
|
||||||
pluginClass: UppyChecksum,
|
pluginClass: UppyChecksum,
|
||||||
|
@ -561,6 +582,7 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, {
|
||||||
isProcessingUpload: false,
|
isProcessingUpload: false,
|
||||||
isCancellable: false,
|
isCancellable: false,
|
||||||
inProgressUploads: [],
|
inProgressUploads: [],
|
||||||
|
bufferedUploadErrors: [],
|
||||||
});
|
});
|
||||||
this._resetPreProcessors();
|
this._resetPreProcessors();
|
||||||
this.fileInputEl.value = "";
|
this.fileInputEl.value = "";
|
||||||
|
|
|
@ -530,6 +530,53 @@ acceptance("Uppy Composer Attachment - Upload Error", function (needs) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
acceptance(
|
||||||
|
"Uppy Composer Attachment - Multiple Upload Errors",
|
||||||
|
function (needs) {
|
||||||
|
needs.user();
|
||||||
|
needs.pretender((server, helper) => {
|
||||||
|
server.post("/uploads.json", () => {
|
||||||
|
return helper.response(500, {
|
||||||
|
success: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
needs.settings({
|
||||||
|
simultaneous_uploads: 2,
|
||||||
|
allow_uncategorized_topics: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should show a consolidated message for multiple failed uploads", async function (assert) {
|
||||||
|
await visit("/");
|
||||||
|
await click("#create-topic");
|
||||||
|
const appEvents = loggedInUser().appEvents;
|
||||||
|
const image = createFile("meme1.png");
|
||||||
|
const image1 = createFile("meme2.png");
|
||||||
|
const done = assert.async();
|
||||||
|
|
||||||
|
appEvents.on("composer:upload-error", async () => {
|
||||||
|
await settled();
|
||||||
|
|
||||||
|
if (!query(".dialog-body")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".dialog-body").textContent.trim(),
|
||||||
|
"Sorry, there was an error uploading meme1.png and meme2.png. Please try again.",
|
||||||
|
"it should show a consolidated error dialog"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click(".dialog-footer .btn-primary");
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
appEvents.trigger("composer:add-files", [image, image1]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
acceptance("Uppy Composer Attachment - Upload Handler", function (needs) {
|
acceptance("Uppy Composer Attachment - Upload Handler", function (needs) {
|
||||||
needs.user();
|
needs.user();
|
||||||
needs.pretender(pretender);
|
needs.pretender(pretender);
|
||||||
|
|
|
@ -163,6 +163,7 @@ en:
|
||||||
|
|
||||||
word_connector:
|
word_connector:
|
||||||
comma: ", "
|
comma: ", "
|
||||||
|
last_item: "and"
|
||||||
|
|
||||||
action_codes:
|
action_codes:
|
||||||
public_topic: "Made this topic public %{when}"
|
public_topic: "Made this topic public %{when}"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user