FEATURE: uploads are processed a faster

Also cleans up API to always return 422 on upload error. (previously returned 200)

Uploads are processed using new hijack pattern
This commit is contained in:
Sam 2017-11-27 12:43:18 +11:00
parent 71942e4f62
commit eb428ef54d
5 changed files with 82 additions and 87 deletions

View File

@ -416,6 +416,19 @@ export default Ember.Component.extend({
}
});
$element.on("fileuploaddone", (e, data) => {
let upload = data.result;
if (!this._xhr || !this._xhr._userCancelled) {
const markdown = getUploadMarkdown(upload);
cacheShortUploadUrl(upload.short_url, upload.url);
this.appEvents.trigger('composer:replace-text', uploadPlaceholder, markdown);
this._resetUpload(false);
} else {
this._resetUpload(true);
}
});
$element.on("fileuploadfail", (e, data) => {
this._resetUpload(true);
@ -423,24 +436,7 @@ export default Ember.Component.extend({
this._xhr = null;
if (!userCancelled) {
displayErrorForUpload(data);
}
});
this.messageBus.subscribe("/uploads/composer", upload => {
// replace upload placeholder
if (upload && upload.url) {
if (!this._xhr || !this._xhr._userCancelled) {
const markdown = getUploadMarkdown(upload);
cacheShortUploadUrl(upload.short_url, upload.url);
this.appEvents.trigger('composer:replace-text', uploadPlaceholder, markdown);
this._resetUpload(false);
} else {
this._resetUpload(true);
}
} else {
this._resetUpload(true);
displayErrorForUpload(upload);
displayErrorForUpload(data.jqXHR.responseJSON);
}
});

View File

@ -18,12 +18,9 @@ export default Em.Mixin.create({
uploadUrl = Discourse.getURL(this.getWithDefault("uploadUrl", "/uploads")),
reset = () => this.setProperties({ uploading: false, uploadProgress: 0});
this.messageBus.subscribe("/uploads/" + this.get("type"), upload => {
if (upload && upload.url) {
this.uploadDone(upload);
} else {
displayErrorForUpload(upload);
}
$upload.on("fileuploaddone", (e, data) => {
let upload = data.result;
this.uploadDone(upload);
reset();
});
@ -59,7 +56,7 @@ export default Em.Mixin.create({
});
$upload.on("fileuploadfail", (e, data) => {
displayErrorForUpload(data);
displayErrorForUpload(data.jqXHR.responseJSON);
reset();
});
}.on("didInsertElement"),

View File

@ -20,19 +20,23 @@ class UploadsController < ApplicationController
file = params[:file] || params[:files]&.first
pasted = params[:pasted] == "true"
for_private_message = params[:for_private_message] == "true"
is_api = is_api?
retain_hours = params[:retain_hours].to_i
if params[:synchronous] && (current_user.staff? || is_api?)
data = create_upload(current_user, file, url, type, for_private_message, pasted)
render json: serialize_upload(data)
else
Scheduler::Defer.later("Create Upload") do
begin
data = create_upload(me, file, url, type, for_private_message, pasted)
ensure
MessageBus.publish("/uploads/#{type}", serialize_upload(data), client_ids: [params[:client_id]])
end
end
render json: success_json
# note, atm hijack is processed in its own context and has not access to controller
# longer term we may change this
hijack do
info = UploadsController.create_upload(
current_user: me,
file: file,
url: url,
type: type,
for_private_message: for_private_message,
pasted: pasted,
is_api: is_api,
retain_hours: retain_hours
)
render json: UploadsController.serialize_upload(info), status: Upload === info ? 200 : 422
end
end
@ -72,20 +76,20 @@ class UploadsController < ApplicationController
protected
def serialize_upload(data)
def render_404
raise Discourse::NotFound
end
def self.serialize_upload(data)
# as_json.as_json is not a typo... as_json in AM serializer returns keys as symbols, we need them
# as strings here
serialized = UploadSerializer.new(data, root: nil).as_json.as_json if Upload === data
serialized ||= (data || {}).as_json
end
def render_404
raise Discourse::NotFound
end
def create_upload(current_user, file, url, type, for_private_message, pasted)
def self.create_upload(current_user:, file:, url:, type:, for_private_message:, pasted:, is_api:, retain_hours:)
if file.nil?
if url.present? && is_api?
if url.present? && is_api
maximum_upload_size = [SiteSetting.max_image_size_kb, SiteSetting.max_attachment_size_kb].max.kilobytes
tempfile = FileHelper.download(
url,
@ -112,7 +116,6 @@ class UploadsController < ApplicationController
upload = UploadCreator.new(tempfile, filename, opts).create_for(current_user.id)
if upload.errors.empty? && current_user.admin?
retain_hours = params[:retain_hours].to_i
upload.update_columns(retain_hours: retain_hours) if retain_hours > 0
end

View File

@ -25,9 +25,17 @@ module Hijack
end
if opts.key?(:plain)
@content_type = 'text/plain'
@content_type = 'text/plain; charset=utf-8'
@body = opts[:plain].to_s
end
if opts.key?(:json)
@content_type = 'application/json; charset=utf-8'
@body = opts[:json]
unless String === @body
@body = @body.to_json
end
end
end
end

View File

@ -40,12 +40,10 @@ describe UploadsController do
it 'is successful with an image' do
Jobs.expects(:enqueue).with(:create_avatar_thumbnails, anything)
message = MessageBus.track_publish('/uploads/avatar') do
post :create, params: { file: logo, type: "avatar", format: :json }
end.first
post :create, params: { file: logo, type: "avatar", format: :json }
expect(response.status).to eq 200
expect(message.data["id"]).to be
expect(JSON.parse(response.body)["id"]).to be
end
it 'is successful with an attachment' do
@ -53,12 +51,11 @@ describe UploadsController do
Jobs.expects(:enqueue).never
message = MessageBus.track_publish('/uploads/composer') do
post :create, params: { file: text_file, type: "composer", format: :json }
end.first
post :create, params: { file: text_file, type: "composer", format: :json }
expect(response.status).to eq 200
expect(message.data["id"]).to be
id = JSON.parse(response.body)["id"]
expect(id).to be
end
it 'is successful with synchronous api' do
@ -88,28 +85,25 @@ describe UploadsController do
log_in :admin
Jobs.expects(:enqueue).with(:create_avatar_thumbnails, anything).never
message = MessageBus.track_publish('/uploads/profile_background') do
post :create, params: {
file: logo,
retain_hours: 100,
type: "profile_background",
format: :json
}
end.first
post :create, params: {
file: logo,
retain_hours: 100,
type: "profile_background",
format: :json
}
id = message.data["id"]
id = JSON.parse(response.body)["id"]
expect(Upload.find(id).retain_hours).to eq(100)
end
it 'requires a file' do
Jobs.expects(:enqueue).never
message = MessageBus.track_publish('/uploads/composer') do
post :create, params: { type: "composer", format: :json }
end.first
post :create, params: { type: "composer", format: :json }
expect(response.status).to eq 200
expect(message.data["errors"]).to contain_exactly(I18n.t("upload.file_missing"))
message = JSON.parse(response.body)
expect(response.status).to eq 422
expect(message["errors"]).to contain_exactly(I18n.t("upload.file_missing"))
end
it 'properly returns errors' do
@ -117,12 +111,11 @@ describe UploadsController do
Jobs.expects(:enqueue).never
message = MessageBus.track_publish("/uploads/avatar") do
post :create, params: { file: text_file, type: "avatar", format: :json }
end.first
post :create, params: { file: text_file, type: "avatar", format: :json }
expect(response.status).to eq 200
expect(message.data["errors"]).to be
expect(response.status).to eq 422
errors = JSON.parse(response.body)["errors"]
expect(errors).to be
end
it 'ensures allow_uploaded_avatars is enabled when uploading an avatar' do
@ -142,28 +135,26 @@ describe UploadsController do
SiteSetting.allow_staff_to_upload_any_file_in_pm = true
@user.update_columns(moderator: true)
message = MessageBus.track_publish('/uploads/composer') do
post :create, params: {
file: text_file,
type: "composer",
for_private_message: "true",
format: :json
}
end.first
post :create, params: {
file: text_file,
type: "composer",
for_private_message: "true",
format: :json
}
expect(response).to be_success
expect(message.data["id"]).to be
id = JSON.parse(response.body)["id"]
expect(id).to be
end
it 'returns an error when it could not determine the dimensions of an image' do
Jobs.expects(:enqueue).with(:create_avatar_thumbnails, anything).never
message = MessageBus.track_publish('/uploads/composer') do
post :create, params: { file: fake_jpg, type: "composer", format: :json }
end.first
post :create, params: { file: fake_jpg, type: "composer", format: :json }
expect(response.status).to eq 200
expect(message.data["errors"]).to contain_exactly(I18n.t("upload.images.size_not_found"))
expect(response.status).to eq 422
message = JSON.parse(response.body)["errors"]
expect(message).to contain_exactly(I18n.t("upload.images.size_not_found"))
end
end