Skip to content

Commit e12e205

Browse files
committed
Issue #2659 - Move console logs saving to upload endpoint
1 parent c939c7b commit e12e205

File tree

9 files changed

+126
-116
lines changed

9 files changed

+126
-116
lines changed

tests/unit/test_console_logs.py

+20-8
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
# License, v. 2.0. If a copy of the MPL was not distributed with this
55
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
66

7-
'''Tests for image upload API endpoint.'''
7+
'''Tests for console logs upload.'''
88

99
import json
1010
import os.path
1111
import sys
1212
import unittest
13+
from werkzeug.datastructures import MultiDict
1314

1415
# Add webcompat module to import path
1516
sys.path.append(os.path.realpath(os.pardir))
@@ -50,6 +51,12 @@ def cleanup(filepath):
5051
os.remove(filepath)
5152

5253

54+
def form_request(data):
55+
d = MultiDict()
56+
d['console_logs'] = data
57+
return d
58+
59+
5360
class TestConsoleUploads(unittest.TestCase):
5461
def setUp(self):
5562
app.config['TESTING'] = True
@@ -60,30 +67,35 @@ def tearDown(self):
6067
pass
6168

6269
def test_get(self):
63-
'''Test that /console_logs/ doesn't let you GET.'''
64-
rv = self.test_client.get('/console_logs/')
70+
'''Test that /upload/ doesn't let you GET.'''
71+
rv = self.test_client.get('/upload/')
6572
self.assertEqual(rv.status_code, 404)
6673

6774
def test_console_log_upload(self):
6875
rv = self.test_client.post(
69-
'/console_logs/', data=json.dumps(LOG))
76+
'/upload/', data=form_request(json.dumps(LOG)))
7077
self.assertEqual(rv.status_code, 201)
7178

7279
response = json.loads(rv.data)
7380
self.assertTrue('url' in response)
7481
cleanup(get_path(self, response.get('url')))
7582

83+
def test_console_log_bad_format(self):
7684
rv = self.test_client.post(
77-
'/console_logs/', data='{test}')
85+
'/upload/', data=form_request('{test}'))
7886
self.assertEqual(rv.status_code, 400)
7987

8088
rv = self.test_client.post(
81-
'/console_logs/', data='')
82-
self.assertEqual(rv.status_code, 400)
89+
'/upload/', data=form_request(''))
90+
self.assertEqual(rv.status_code, 501)
91+
92+
rv = self.test_client.post(
93+
'/upload/', data=json.dumps(LOG))
94+
self.assertEqual(rv.status_code, 501)
8395

8496
def test_console_log_file_contents(self):
8597
rv = self.test_client.post(
86-
'/console_logs/', data=json.dumps(LOG))
98+
'/upload/', data=form_request(json.dumps(LOG)))
8799
response = json.loads(rv.data)
88100
filepath = get_path(self, response.get('url'))
89101
actual = get_json_contents(filepath)

webcompat/__init__.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,8 @@
3939
from webcompat.api.endpoints import api # noqa
4040
from webcompat.api.uploads import uploads # noqa
4141
from webcompat.error_handlers import error_handlers # noqa
42-
from webcompat.api.console_logs import console_logs # noqa
4342

44-
for blueprint in [api, error_handlers, uploads, console_logs]:
43+
for blueprint in [api, error_handlers, uploads]:
4544
app.register_blueprint(blueprint)
4645

4746

webcompat/api/console_logs.py

-64
This file was deleted.

webcompat/api/uploads.py

+75-33
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# License, v. 2.0. If a copy of the MPL was not distributed with this
55
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
66

7-
'''Flask Blueprint for image uploads.'''
7+
'''Flask Blueprint for file uploads.'''
88

99
import base64
1010
import datetime
@@ -22,13 +22,29 @@
2222
from werkzeug.exceptions import RequestEntityTooLarge
2323

2424
from webcompat import app
25+
from webcompat.helpers import get_data_from_request
2526

2627
uploads = Blueprint('uploads', __name__, url_prefix='/upload',
2728
template_folder='../templates')
2829
JSON_MIME = 'application/json'
2930

3031

31-
class Upload(object):
32+
class BaseUpload(object):
33+
def __init__(self):
34+
today = datetime.date.today()
35+
self.year = str(today.year)
36+
self.month = str(today.month)
37+
self.file_id = str(uuid.uuid4())
38+
39+
def get_file_path(self, year, month, file_id, ext, thumb=False):
40+
thumb_string = ''
41+
if thumb:
42+
thumb_string = '-thumb'
43+
file_name = file_id + thumb_string + '.' + ext
44+
return os.path.join(year, month, file_name)
45+
46+
47+
class ImageUpload(BaseUpload):
3248

3349
'''Class that abstracts over saving image and screenshot uploads.
3450
@@ -39,17 +55,15 @@ class Upload(object):
3955
ALLOWED_FORMATS = ('jpg', 'jpeg', 'jpe', 'png', 'gif', 'bmp')
4056

4157
def __init__(self, imagedata):
58+
super().__init__()
4259
self.image_object = self.to_image_object(imagedata)
43-
# computing the parameters to be used
44-
today = datetime.date.today()
45-
self.year = str(today.year)
46-
self.month = str(today.month)
47-
self.image_id = str(uuid.uuid4())
4860
self.file_ext = self.get_file_ext()
49-
self.image_path = self.img_path(self.month, self.year, self.image_id,
50-
thumb=False)
51-
self.thumb_path = self.img_path(self.month, self.year, self.image_id,
52-
thumb=True)
61+
self.image_path = self.get_file_path(self.year, self.month,
62+
self.file_id, self.file_ext,
63+
thumb=False)
64+
self.thumb_path = self.get_file_path(self.year, self.month,
65+
self.file_id, self.file_ext,
66+
thumb=True)
5367

5468
def to_image_object(self, imagedata):
5569
'''Method to return a Pillow Image object from the raw imagedata.'''
@@ -70,14 +84,6 @@ def to_image_object(self, imagedata):
7084
# Not a valid format
7185
abort(415)
7286

73-
def img_path(self, month, year, image_id, thumb=False):
74-
'''Return the right image path.'''
75-
thumb_string = ''
76-
if thumb:
77-
thumb_string = '-thumb'
78-
image_name = image_id + thumb_string + '.' + self.file_ext
79-
return os.path.join(year, month, image_name)
80-
8187
def get_file_ext(self):
8288
'''Method to return the file extension, as determined by Pillow.
8389
@@ -125,33 +131,69 @@ def save(self):
125131
self.image_object.thumbnail(size, Image.HAMMING)
126132
self.image_object.save(thumb_dest, **save_parameters)
127133

134+
def get_file_info(self):
135+
return {
136+
'filename': self.get_filename(self.image_path),
137+
'url': self.get_url(self.image_path),
138+
'thumb_url': self.get_url(self.thumb_path)
139+
}
140+
141+
142+
class LogUpload(BaseUpload):
143+
def __init__(self, data):
144+
super().__init__()
145+
self.data = self.process_data(data)
146+
self.file_path = self.get_file_path(self.year, self.month,
147+
self.file_id, 'json')
148+
149+
def process_data(self, data):
150+
try:
151+
return json.loads(data)
152+
except ValueError:
153+
abort(400)
154+
155+
def get_url(self, file_path):
156+
return os.path.splitext(file_path)[0]
157+
158+
def save(self):
159+
file_dest = app.config['UPLOADS_DEFAULT_DEST'] + self.file_path
160+
dest_dir = os.path.dirname(file_dest)
161+
162+
if not os.path.exists(dest_dir):
163+
os.makedirs(dest_dir)
164+
165+
with open(file_dest, 'w', encoding='utf-8') as f:
166+
json.dump(self.data, f, ensure_ascii=False)
167+
168+
def get_file_info(self):
169+
return {
170+
'url': self.get_url(self.file_path)
171+
}
172+
128173

129174
@uploads.route('/', methods=['POST'])
130175
def upload():
131-
'''Endpoint to upload an image.
176+
'''Endpoint to upload an image or a json with console logs.
132177
133-
If the image asset passes validation, it's saved as:
178+
If the file asset passes validation, it's saved as:
134179
UPLOADS_DEFAULT_DEST + /year/month/random-uuid.ext
135180
136181
Returns a JSON string that contains the filename and url.
137182
'''
138-
if 'image' in request.files and request.files['image'].filename:
139-
imagedata = request.files['image']
140-
elif 'image' in request.form:
141-
imagedata = request.form['image']
142-
else:
183+
is_image, data = get_data_from_request(request)
184+
if not data:
143185
# We don't know what you're trying to do, but it ain't gonna work.
144186
abort(501)
145187

146188
try:
147-
upload = Upload(imagedata)
189+
if is_image:
190+
upload = ImageUpload(data)
191+
else:
192+
upload = LogUpload(data)
193+
148194
upload.save()
149-
data = {
150-
'filename': upload.get_filename(upload.image_path),
151-
'url': upload.get_url(upload.image_path),
152-
'thumb_url': upload.get_url(upload.thumb_path)
153-
}
154-
return (json.dumps(data), 201, {'content-type': JSON_MIME})
195+
file_info = upload.get_file_info()
196+
return json.dumps(file_info), 201, {'content-type': JSON_MIME}
155197
except (TypeError, IOError):
156198
abort(415)
157199
except RequestEntityTooLarge:

webcompat/form.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from wtforms.validators import Regexp
3333

3434
from webcompat import app
35-
from webcompat.api.uploads import Upload
35+
from webcompat.api.uploads import ImageUpload
3636
from webcompat.helpers import get_browser
3737
from webcompat.helpers import get_os
3838
from webcompat.helpers import get_str_value
@@ -221,7 +221,8 @@ class IssueForm(FlaskForm):
221221
# any changes here need to be updated in form.html.
222222
image = FileField('Attach a screenshot image',
223223
[Optional(),
224-
FileAllowed(Upload.ALLOWED_FORMATS, image_message)])
224+
FileAllowed(ImageUpload.ALLOWED_FORMATS,
225+
image_message)])
225226
details = HiddenField()
226227
reported_with = HiddenField()
227228
ua_header = HiddenField()

webcompat/helpers.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ def wrapped_func(*args, **kwargs):
412412
file_path = FIXTURES_PATH + request.path + "." + checksum
413413
else:
414414
file_path = FIXTURES_PATH + request.path
415-
if '.json' not in file_path:
415+
if not file_path.endswith('.json'):
416416
file_path = file_path + '.json'
417417
if not os.path.exists(file_path):
418418
print('Fixture expected at: {fix}'.format(fix=file_path))
@@ -761,3 +761,14 @@ def get_extra_labels(form):
761761
extra_labels = ['form-v2-experiment']
762762

763763
return extra_labels
764+
765+
766+
def get_data_from_request(request):
767+
if 'image' in request.files and request.files['image'].filename:
768+
return True, request.files['image']
769+
elif 'image' in request.form:
770+
return True, request.form['image']
771+
elif 'console_logs' in request.form:
772+
return False, request.form['console_logs']
773+
else:
774+
return False, None

webcompat/static/js/lib/bugform.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -655,11 +655,15 @@ BugForm.prototype.uploadConsoleLogs = function() {
655655
return dfd.resolve();
656656
}
657657

658+
var formdata = new FormData();
659+
formdata.append("console_logs", JSON.stringify(details.consoleLog));
660+
658661
return $.ajax({
659-
contentType: "application/json",
660-
data: JSON.stringify(details.consoleLog),
662+
contentType: false,
663+
processData: false,
664+
data: formdata,
661665
method: "POST",
662-
url: "/console_logs/",
666+
url: "/upload/",
663667
success: function(response) {
664668
var path = location.origin + "/console_logs/";
665669
this.consoleLogsInput.val(path + response.url);

webcompat/static/js/lib/issue-wizard-bugform.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -1199,11 +1199,15 @@ BugForm.prototype.uploadConsoleLogs = function() {
11991199
return dfd.resolve();
12001200
}
12011201

1202+
var formdata = new FormData();
1203+
formdata.append("console_logs", JSON.stringify(details.consoleLog));
1204+
12021205
return $.ajax({
1203-
contentType: "application/json",
1204-
data: JSON.stringify(details.consoleLog),
1206+
contentType: false,
1207+
processData: false,
1208+
data: formdata,
12051209
method: "POST",
1206-
url: "/console_logs/",
1210+
url: "/upload/",
12071211
success: function(response) {
12081212
var path = location.origin + "/console_logs/";
12091213
this.consoleLogsInput.val(path + response.url);

webcompat/templates/console-logs.html

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<html lang="en">
33
<head>
44
<title>Console logs | webcompat.com</title>
5+
<meta name="robots" content="noindex">
56
<meta charset="utf-8">
67
<meta name="viewport" content="width=device-width, initial-scale=1.0">
78
<meta name="description" content="Open source community for web compatibility. Report bugs from websites or for browsers and help us move the web forward.">

0 commit comments

Comments
 (0)