[FIX] web_editor, web_unsplash, test_website: make unsplash work

Since cfe079221c, unsplash is not working anymore as that commit did not adapt
the unsplash code to that change.

task-2581567

closes odoo/odoo#76617

X-original-commit: 9a4723628e868836c7bbe8bec31ec9b6ce3f2554
Signed-off-by: Romain Derie (rde) <rde@odoo.com>
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
Romain Derie
2021-09-16 10:01:14 +00:00
parent 360abc0570
commit 6dd7207b6f
8 changed files with 205 additions and 135 deletions
+1
View File
@@ -13,6 +13,7 @@ and we don't want to reload the website module every time, including it's possib
dependencies. Neither we want to add in website module some routes, views and
models which only purpose is to run tests.""",
'depends': [
'web_unsplash',
'website',
],
'data': [
@@ -3,28 +3,30 @@ odoo.define('test_website.image_upload_progress', function (require) {
const tour = require('web_tour.tour');
const setupSteps = [{
content: "enter edit mode",
trigger: "a[data-action=edit]"
}, {
content: "drop a snippet",
trigger: "#oe_snippets .oe_snippet[name='Text - Image'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
extra_trigger: "body.editor_enable.editor_has_snippets",
moveTrigger: ".oe_drop_zone",
run: "drag_and_drop #wrap",
}, {
content: "drop a snippet",
trigger: "#oe_snippets .oe_snippet[name='Image Gallery'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
extra_trigger: "body.editor_enable.editor_has_snippets",
moveTrigger: ".oe_drop_zone",
run: "drag_and_drop #wrap",
}];
const formatErrorMsg = "format is not supported. Try with: .gif, .jpe, .jpeg, .jpg, .png, .svg";
tour.register('test_image_upload_progress', {
url: '/test_image_progress',
test: true
}, [
{
content: "enter edit mode",
trigger: "a[data-action=edit]"
}, {
content: "drop a snippet",
trigger: "#oe_snippets .oe_snippet[name='Text - Image'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
extra_trigger: "body.editor_enable.editor_has_snippets",
moveTrigger: ".oe_drop_zone",
run: "drag_and_drop #wrap",
}, {
content: "drop a snippet",
trigger: "#oe_snippets .oe_snippet[name='Image Gallery'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
extra_trigger: "body.editor_enable.editor_has_snippets",
moveTrigger: ".oe_drop_zone",
run: "drag_and_drop #wrap",
},
...setupSteps,
// 1. Check multi image upload
{
content: "click on dropped snippet",
@@ -158,4 +160,41 @@ tour.register('test_image_upload_progress', {
},
]);
tour.register('test_image_upload_progress_unsplash', {
url: '/test_image_progress',
test: true
}, [
...setupSteps,
// 1. Check multi image upload
{
content: "click on dropped snippet",
trigger: "#wrap .s_image_gallery .img",
}, {
content: "click on replace media to open image dialog",
trigger: 'we-customizeblock-option [data-replace-media]',
}, {
content: "search 'fox' images",
trigger: ".o_we_search",
run: "text fox",
}, {
content: "click on unsplash result", // note that unsplash is mocked
trigger: ".o_unsplash_attachment_cell"
}, {
content: "check that the upload progress bar is correctly shown",
// ensure it is there so we are sure next step actually test something
extra_trigger: '.o_notification_close',
trigger: ".o_we_progressbar:contains('fox'):contains('File has been uploaded')",
in_modal: false,
run: function () {}, // it's a check
}, {
content: "notification should close after 3 seconds",
trigger: 'body:not(:has(.o_notification_close))',
in_modal: false,
}, {
content: "unsplash image (mocked to logo) should have been used",
trigger: "#wrap .s_image_gallery .img[data-original-src^='/unsplash/HQqIOc8oYro/fox']",
},
]);
});
@@ -1,11 +1,57 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.web_editor.controllers.main import Web_Editor
from odoo.addons.web_unsplash.controllers.main import Web_Unsplash
import odoo.tests
from odoo import http
from odoo.tools import config
BASE_URL = "http://127.0.0.1:%s" % (config["http_port"],)
@odoo.tests.common.tagged('post_install', '-at_install')
class TestImageUploadProgress(odoo.tests.HttpCase):
def test_01_image_upload_progress(self):
self.start_tour("/test_image_progress", 'test_image_upload_progress', login="admin")
def test_02_image_upload_progress_unsplash(self):
def media_library_search(self, **params):
return {"results": 0, "media": []}
def fetch_unsplash_images(self, **post):
return {
'total': 1434,
'total_pages': 48,
'results': [{
'id': 'HQqIOc8oYro',
'alt_description': 'brown fox sitting on green grass field during daytime',
'urls': {
# 'regular': 'https://images.unsplash.com/photo-1462953491269-9aff00919695?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwzMDUwOHwwfDF8c2VhcmNofDF8fGZveHxlbnwwfHx8fDE2MzEwMzIzNDE&ixlib=rb-1.2.1&q=80&w=1080',
'regular': BASE_URL + '/website/static/src/img/phone.png',
},
'links': {
# 'download_location': 'https://api.unsplash.com/photos/HQqIOc8oYro/download?ixid=MnwzMDUwOHwwfDF8c2VhcmNofDF8fGZveHxlbnwwfHx8fDE2MzEwMzIzNDE'
'download_location': BASE_URL + '/website/static/src/img/phone.png',
},
'user': {
'name': 'Mitchell Admin',
'links': {
'html': BASE_URL,
},
},
}]
}
# because not preprocessed by ControllerType metaclass
fetch_unsplash_images.routing_type = 'json'
Web_Unsplash.fetch_unsplash_images = http.route("/web_unsplash/fetch_images", type='json', auth="user")(fetch_unsplash_images)
# disable undraw, no third party should be called in tests
media_library_search.routing_type = 'json'
Web_Editor.media_library_search = http.route(['/web_editor/media_library_search'], type='json', auth="user", website=True)(media_library_search)
self.start_tour("/", 'test_image_upload_progress_unsplash', login="admin")
@@ -127,10 +127,6 @@ var FileWidget = SearchableMediaWidget.extend({
'click .o_existing_attachment_remove': '_onRemoveClick',
'click .o_load_more': '_onLoadMoreClick',
}),
custom_events: Object.assign({}, SearchableMediaWidget.prototype.events || {}, {
'file_complete': '_onAttachmentUploaded',
'upload_complete': '_onUploadCompleted',
}),
existingAttachmentsTemplate: undefined,
IMAGE_MIMETYPES: ['image/jpg', 'image/jpeg', 'image/jpe', 'image/png', 'image/svg+xml', 'image/gif'],
@@ -205,7 +201,6 @@ var FileWidget = SearchableMediaWidget.extend({
if (this.uploader) {
// Prevent uploader from being destroyed with call to super so it can linger
this.uploader.setParent(null);
this.uploader.destroyOnClose = true;
this.uploader.close(2000);
}
this._super(...arguments);
@@ -580,18 +575,49 @@ var FileWidget = SearchableMediaWidget.extend({
* @returns {Promise}
*/
async _addData() {
const files = this.$fileInput[0].files;
let files = this.$fileInput[0].files;
if (!files.length) {
// Case if the input is emptied, return resolved promise
return;
}
this.uploader = new UploadProgressToast(this, {
files: files,
resModel: this.options.res_model,
resId: this.options.res_id,
isImage: this.widgetType === 'image',
const uploadMutex = new concurrency.Mutex();
// Upload the smallest file first to block the user the least possible.
files = _.sortBy(files, 'size');
await this._setUpProgressToast(files);
_.each(files, (file, index) => {
// Upload one file at a time: no need to parallel as upload is
// limited by bandwidth.
uploadMutex.exec(async () => {
const dataURL = await utils.getDataURLFromFile(file);
const attachment = await this.uploader.rpcShowProgress({
route: '/web_editor/attachment/add_data',
params: {
'name': file.name,
'data': dataURL.split(',')[1],
'res_id': this.options.res_id,
'res_model': this.options.res_model,
'is_image': this.widgetType === 'image',
'width': 0,
'quality': 0,
}
}, index);
if (!attachment.error) {
this._handleNewAttachment(attachment);
}
});
});
return uploadMutex.getUnlockedDef().then(() => {
if (!this.uploader.hasError) {
this.uploader.close(3000);
}
if (!this.options.multiImages && !this.noSave) {
this.trigger_up('save_request');
}
this.noSave = false;
});
this.uploader.appendTo(document.body);
},
/**
* @private
@@ -695,22 +721,20 @@ var FileWidget = SearchableMediaWidget.extend({
this.numberOfAttachmentsToDisplay = this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY;
this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Sets up a progress bar for every file being uploaded in a toast.
*
* @private
* @param {Object[]} files
*/
_onAttachmentUploaded({ data: attachment }) {
if (!attachment.error) {
this._handleNewAttachment(attachment);
}
},
/**
* @private
*/
_onUploadCompleted() {
if (!this.options.multiImages && !this.noSave) {
this.trigger_up('save_request');
}
this.noSave = false;
_setUpProgressToast: async function (files) {
this.uploader = new UploadProgressToast(this, files);
await this.uploader.appendTo(document.body);
},
});
@@ -1,8 +1,6 @@
/** @odoo-module */
import Widget from "web.Widget";
import concurrency from "web.concurrency";
import { getDataURLFromFile } from "web.utils";
import {qweb} from 'web.core';
export const UploadProgressToast = Widget.extend({
xmlDependencies: ['/web_editor/static/src/xml/wysiwyg.xml'],
@@ -14,62 +12,34 @@ export const UploadProgressToast = Widget.extend({
/**
* @override
*/
init(parent, { files, resModel, resId, isImage }) {
init(parent, files) {
this._super(...arguments);
this.resId = resId;
this.resModel = resModel;
this.isImage = isImage;
// Upload the smallest file first to block the user the least possible.
this.files = [...files].sort((a, b) => a.size - b.size);
this.files.forEach((file, index) => {
file.id = index;
if (!file.size) {
file.displaySize = null;
} else if (file.size < 1024) {
file.displaySize = file.size.toFixed(2) + " bytes";
} else if (file.size < 1048576) {
file.displaySize = (file.size / 1024).toFixed(2) + " KB";
} else {
file.displaySize = (file.size / 1048576).toFixed(2) + " MB";
}
});
this.files = files;
},
/**
* @override
*/
start() {
this.hasError = false;
const uploadMutex = new concurrency.Mutex();
this.files.forEach((file, index) => {
// Upload one file at a time: no need to parallel as upload is
// limited by bandwidth.
uploadMutex.exec(async () => {
const dataURL = await getDataURLFromFile(file);
const attachment = await this._rpcShowProgress({
route: '/web_editor/attachment/add_data',
params: {
name: file.name,
data: dataURL.split(',')[1],
res_id: this.resId,
res_model: this.resModel,
is_image: this.isImage,
width: 0,
quality: 0,
}
}, this.$el.find(`.js_progressbar_${index}`));
if (!attachment.error) {
this.trigger_up('file_complete', attachment);
}
});
});
this.$progress = $('<div/>');
_.each(this.files, (file, index) => {
let fileSize = file.size;
if (!fileSize) {
fileSize = null;
} else if (fileSize < 1024) {
fileSize = fileSize.toFixed(2) + " bytes";
} else if (fileSize < 1048576) {
fileSize = (fileSize / 1024).toFixed(2) + " KB";
} else {
fileSize = (fileSize / 1048576).toFixed(2) + " MB";
}
uploadMutex.getUnlockedDef().then(() => {
this.trigger_up('upload_complete');
this.destroyOnClose = true;
this.close(2000);
this.$progress.append(qweb.render('wysiwyg.widgets.upload.progressbar', {
fileId: index,
fileName: file.name,
fileSize: fileSize,
}));
});
this.uploadMutex = uploadMutex;
this.$el.find('.o_notification_content').append(this.$progress);
},
//--------------------------------------------------------------------------
@@ -84,32 +54,21 @@ export const UploadProgressToast = Widget.extend({
*/
close(delay = 0) {
window.setTimeout(() => {
if (!this.hasError || delay === 0) {
this.el.querySelector('.fade').classList.remove('show');
window.setTimeout(() => {
if (this.destroyOnClose) {
this.destroy();
} else {
this.el.classList.add('d-none');
}
}, 150);
}
this.el.querySelector('.fade').classList.remove('show');
window.setTimeout(() => {
this.destroy();
}, 150);
}, delay);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Calls a RPC and shows its progress status.
*
* @private
* @param {Object} params regular `_rpc()` parameters
* @param {jQuery} $progressBar the element holding the progress bar
* @returns {Promise} resolved when the RPC is complete
* @param {integer} index file index to retrieve its related progress bar
* @returns {Promise}
*/
async _rpcShowProgress(params, $progressBar) {
async rpcShowProgress(params, index) {
let $progressBar = this.$el.find(`.js_progressbar_${index}`);
try {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', ev => {
@@ -122,7 +81,7 @@ export const UploadProgressToast = Widget.extend({
// Don't show yet success as backend code only starts now
$progressBar.find('.progress-bar').css({width: '100%'}).text('100%');
});
const attachment = await this._rpc(params, { xhr });
const attachment = await this._rpc(params, {xhr});
$progressBar.find('.fa-spinner, .progress').addClass('d-none');
if (attachment.error) {
this.hasError = true;
+18 -19
View File
@@ -270,6 +270,23 @@
</div>
</t>
<t t-name="wysiwyg.widgets.upload.progressbar">
<div t-attf-class="o_we_progressbar js_progressbar_#{fileId}">
<div class="d-flex text-info">
<span><i class="fa fa-spinner fa-pulse"/></span>
<span class="font-italic font-weight-bold text-truncate flex-grow-1 mx-2" t-esc="fileName"/>
<span class="text-nowrap" t-esc="fileSize"/>
</div>
<div class='js_progressbar_txt'>
<span class="text-success d-none"><i class='fa fa-check'/> File has been uploaded</span>
<span class="text-danger d-none"><i class='fa fa-times float-left m-1'/> <span class="o_we_error_text">File could not be saved</span></span>
</div>
<div class="progress">
<div class="progress-bar bg-info progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%;">0%</div>
</div>
<hr/>
</div>
</t>
<t t-name="wysiwyg.widgets.upload.progress_toast">
<div class="o_notification_manager">
<div t-attf-class="o_notification bg-info fade show" role="alert" aria-live="assertive" aria-atomic="true">
@@ -278,25 +295,7 @@
aria-label="Close">
<span class="d-inline" aria-hidden="true">×</span>
</button>
<div class="mr-auto o_notification_content">
<div>
<div t-foreach="widget.files" t-as="file" t-attf-class="o_we_progressbar js_progressbar_#{file.id}">
<div class="d-flex text-info">
<span><i class="fa fa-spinner fa-pulse"/></span>
<span class="font-italic font-weight-bold text-truncate flex-grow-1 mx-2" t-esc="file.name"/>
<span class="text-nowrap" t-esc="file.displaySize"/>
</div>
<div class='js_progressbar_txt'>
<span class="text-success d-none"><i class='fa fa-check'/> File has been uploaded</span>
<span class="text-danger d-none"><i class='fa fa-times float-left m-1'/><span class="o_we_error_text">File could not be saved</span></span>
</div>
<div class="progress">
<div class="progress-bar bg-info progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%;">0%</div>
</div>
<hr/>
</div>
</div>
</div>
<div class="mr-auto o_notification_content"/>
</div>
</div>
</div>
+3 -2
View File
@@ -30,7 +30,7 @@ class Web_Unsplash(http.Controller):
view counter.
'''
try:
if not url.startswith('https://api.unsplash.com/photos/'):
if not url.startswith('https://api.unsplash.com/photos/') and not request.env.registry.in_test_mode():
raise Exception(_("ERROR: Unknown Unsplash notify URL!"))
access_key = self._get_access_key()
requests.get(url, params=url_encode({'client_id': access_key}))
@@ -81,9 +81,10 @@ class Web_Unsplash(http.Controller):
for key, value in unsplashurls.items():
url = value.get('url')
try:
if not url.startswith('https://images.unsplash.com/'):
if not url.startswith('https://images.unsplash.com/') and not request.env.registry.in_test_mode():
logger.exception("ERROR: Unknown Unsplash URL!: " + url)
raise Exception(_("ERROR: Unknown Unsplash URL!"))
req = requests.get(url)
if req.status_code != requests.codes.ok:
continue
@@ -3,6 +3,7 @@ odoo.define('web_unsplash.image_widgets', function (require) {
var core = require('web.core');
var UnsplashAPI = require('unsplash.api');
const {UploadProgressToast} = require('@web_editor/js/wysiwyg/widgets/upload_progress_toast');
var widgetsMedia = require('wysiwyg.widgets.media');
const {_t} = require('web.core');
@@ -91,7 +92,7 @@ widgetsMedia.ImageWidget.include({
_.str.sprintf(_t("Uploading '%s' image."), this._unsplash.query),
size: null,
}]);
const images = await this._rpcShowProgress({
const images = await this.uploader.rpcShowProgress({
route: '/web_unsplash/attachment/add',
params: {
unsplashurls: selectedImages,
@@ -100,7 +101,7 @@ widgetsMedia.ImageWidget.include({
query: this._unsplash.query,
},
}, 0);
this._closeProgressToast();
this.uploader.close(3000);
this.attachments.push(...images);
this.selectedAttachments.push(...images);
}