diff --git a/swh/web/ui/apidoc.py b/swh/web/ui/apidoc.py
--- a/swh/web/ui/apidoc.py
+++ b/swh/web/ui/apidoc.py
@@ -21,6 +21,7 @@
ts = 'timestamp'
int = 'integer'
+ str = 'string'
path = 'path'
sha1 = 'sha1'
uuid = 'uuid'
@@ -225,7 +226,8 @@
# Build example endpoint URL
if 'args' in env:
defaults = {arg['name']: arg['default'] for arg in env['args']}
- env['example'] = url_for(f.__name__, **defaults)
+ example = url_for(f.__name__, **defaults)
+ env['example'] = re.sub(r'(.*)\?.*', r'\1', example)
# Prepare and send to mimetype selector if it's not a doc request
if re.match(route_re, request.url) and not kwargs['noargs']:
diff --git a/swh/web/ui/backend.py b/swh/web/ui/backend.py
--- a/swh/web/ui/backend.py
+++ b/swh/web/ui/backend.py
@@ -80,17 +80,18 @@
return res[0]
-def origin_get(origin_id):
- """Return information about the origin with id origin_id.
+def origin_get(origin):
+ """Return information about the origin matching dict origin.
Args:
- origin_id: origin's identifier
+ origin: origin's dict with keys either 'id' or
+ ('type' AND 'url')
Returns:
Origin information as dict.
"""
- return main.storage().origin_get({'id': origin_id})
+ return main.storage().origin_get(origin)
def person_get(person_id):
diff --git a/swh/web/ui/renderers.py b/swh/web/ui/renderers.py
--- a/swh/web/ui/renderers.py
+++ b/swh/web/ui/renderers.py
@@ -6,10 +6,10 @@
import re
import yaml
import json
-import sys
from docutils.core import publish_parts
from docutils.writers.html4css1 import Writer, HTMLTranslator
+from inspect import cleandoc
from flask import request, Response, render_template
from flask import g
@@ -136,30 +136,7 @@
api.
"""
- def trim(docstring):
- """Correctly trim triple-quoted docstrings, taking into account
- first-line indentation inconsistency.
- Source: https://www.python.org/dev/peps/pep-0257/#handling-docstring-indentation # noqa
- """
- if not docstring:
- return ''
- lines = docstring.expandtabs().splitlines()
- indent = sys.maxsize
- for line in lines[1:]:
- stripped = line.lstrip()
- if stripped:
- indent = min(indent, len(line) - len(stripped))
- trimmed = [lines[0].strip()]
- if indent < sys.maxsize:
- for line in lines[1:]:
- trimmed.append(line[indent:].rstrip())
- while trimmed and not trimmed[-1]:
- trimmed.pop()
- while trimmed and not trimmed[0]:
- trimmed.pop(0)
- return '\n'.join(trimmed)
-
- docstring = trim(docstring)
+ docstring = cleandoc(docstring)
return publish_parts(docstring, writer=DOCSTRING_WRITER)['html_body']
diff --git a/swh/web/ui/service.py b/swh/web/ui/service.py
--- a/swh/web/ui/service.py
+++ b/swh/web/ui/service.py
@@ -73,17 +73,18 @@
return converters.from_origin(origin)
-def lookup_origin(origin_id):
- """Return information about the origin with id origin_id.
+def lookup_origin(origin):
+ """Return information about the origin matching dict origin.
Args:
- origin_id as string
+ origin: origin's dict with keys either 'id' or
+ ('type' AND 'url')
Returns:
origin information as dict.
"""
- return backend.origin_get(origin_id)
+ return backend.origin_get(origin)
def lookup_person(person_id):
diff --git a/swh/web/ui/templates/origin.html b/swh/web/ui/templates/origin.html
--- a/swh/web/ui/templates/origin.html
+++ b/swh/web/ui/templates/origin.html
@@ -12,7 +12,7 @@
-
Details on origin {{ origin_id }}:
+
Details on origin {{ origin['id'] }}:
{% endif %}
-
+
{% endif %}
{% endblock %}
diff --git a/swh/web/ui/tests/test_backend.py b/swh/web/ui/tests/test_backend.py
--- a/swh/web/ui/tests/test_backend.py
+++ b/swh/web/ui/tests/test_backend.py
@@ -174,7 +174,7 @@
self.storage.content_missing_per_sha1.assert_called_with(sha1s_bin)
@istest
- def origin_get(self):
+ def origin_get_by_id(self):
# given
self.storage.origin_get = MagicMock(return_value={
'id': 'origin-id',
@@ -184,7 +184,7 @@
'type': 'ftp'})
# when
- actual_origin = backend.origin_get('origin-id')
+ actual_origin = backend.origin_get({'id': 'origin-id'})
# then
self.assertEqual(actual_origin, {'id': 'origin-id',
@@ -196,6 +196,31 @@
self.storage.origin_get.assert_called_with({'id': 'origin-id'})
@istest
+ def origin_get_by_type_url(self):
+ # given
+ self.storage.origin_get = MagicMock(return_value={
+ 'id': 'origin-id',
+ 'lister': 'uuid-lister',
+ 'project': 'uuid-project',
+ 'url': 'ftp://some/url/to/origin',
+ 'type': 'ftp'})
+
+ # when
+ actual_origin = backend.origin_get({'type': 'ftp',
+ 'url': 'ftp://some/url/to/origin'})
+
+ # then
+ self.assertEqual(actual_origin, {'id': 'origin-id',
+ 'lister': 'uuid-lister',
+ 'project': 'uuid-project',
+ 'url': 'ftp://some/url/to/origin',
+ 'type': 'ftp'})
+
+ self.storage.origin_get.assert_called_with(
+ {'type': 'ftp',
+ 'url': 'ftp://some/url/to/origin'})
+
+ @istest
def person_get(self):
# given
self.storage.person_get = MagicMock(return_value=[{
diff --git a/swh/web/ui/tests/test_service.py b/swh/web/ui/tests/test_service.py
--- a/swh/web/ui/tests/test_service.py
+++ b/swh/web/ui/tests/test_service.py
@@ -362,7 +362,7 @@
'type': 'ftp'})
# when
- actual_origin = service.lookup_origin('origin-id')
+ actual_origin = service.lookup_origin({'id': 'origin-id'})
# then
self.assertEqual(actual_origin, {'id': 'origin-id',
@@ -371,7 +371,7 @@
'url': 'ftp://some/url/to/origin',
'type': 'ftp'})
- mock_backend.origin_get.assert_called_with('origin-id')
+ mock_backend.origin_get.assert_called_with({'id': 'origin-id'})
@patch('swh.web.ui.service.backend')
@istest
diff --git a/swh/web/ui/tests/views/test_api.py b/swh/web/ui/tests/views/test_api.py
--- a/swh/web/ui/tests/views/test_api.py
+++ b/swh/web/ui/tests/views/test_api.py
@@ -298,7 +298,7 @@
}
# when
- rv = self.app.get('/api/1/search/sha1:blah/')
+ rv = self.app.get('/api/1/content/search/sha1:blah/')
self.assertEquals(rv.status_code, 200)
self.assertEquals(rv.mimetype, 'application/json')
@@ -320,7 +320,7 @@
}
# when
- rv = self.app.get('/api/1/search/sha1:halb/',
+ rv = self.app.get('/api/1/content/search/sha1:halb/',
headers={'Accept': 'application/yaml'})
self.assertEquals(rv.status_code, 200)
@@ -345,7 +345,7 @@
}
# when
- rv = self.app.get('/api/1/search/sha1:halb/')
+ rv = self.app.get('/api/1/content/search/sha1:halb/')
self.assertEquals(rv.status_code, 200)
self.assertEquals(rv.mimetype, 'application/json')
@@ -527,7 +527,7 @@
@patch('swh.web.ui.views.api.service')
@istest
- def api_origin(self, mock_service):
+ def api_origin_by_id(self, mock_service):
# given
stub_origin = {
'id': 1234,
@@ -548,7 +548,34 @@
response_data = json.loads(rv.data.decode('utf-8'))
self.assertEquals(response_data, stub_origin)
- mock_service.lookup_origin.assert_called_with(1234)
+ mock_service.lookup_origin.assert_called_with({'id': 1234})
+
+ @patch('swh.web.ui.views.api.service')
+ @istest
+ def api_origin_by_type_url(self, mock_service):
+ # given
+ stub_origin = {
+ 'id': 1234,
+ 'lister': 'uuid-lister-0',
+ 'project': 'uuid-project-0',
+ 'url': 'ftp://some/url/to/origin/0',
+ 'type': 'ftp'
+ }
+ mock_service.lookup_origin.return_value = stub_origin
+
+ # when
+ rv = self.app.get('/api/1/origin/ftp/url/ftp://some/url/to/origin/0/')
+
+ # then
+ self.assertEquals(rv.status_code, 200)
+ self.assertEquals(rv.mimetype, 'application/json')
+
+ response_data = json.loads(rv.data.decode('utf-8'))
+ self.assertEquals(response_data, stub_origin)
+
+ mock_service.lookup_origin.assert_called_with(
+ {'url': 'ftp://some/url/to/origin/0',
+ 'type': 'ftp'})
@patch('swh.web.ui.views.api.service')
@istest
@@ -567,7 +594,7 @@
'error': 'Origin with id 4321 not found.'
})
- mock_service.lookup_origin.assert_called_with(4321)
+ mock_service.lookup_origin.assert_called_with({'id': 4321})
@patch('swh.web.ui.views.api.service')
@istest
diff --git a/swh/web/ui/tests/views/test_browse.py b/swh/web/ui/tests/views/test_browse.py
--- a/swh/web/ui/tests/views/test_browse.py
+++ b/swh/web/ui/tests/views/test_browse.py
@@ -43,7 +43,7 @@
@istest
def search_default(self):
# when
- rv = self.client.get('/search/')
+ rv = self.client.get('/content/search/')
self.assertEqual(rv.status_code, 200)
self.assertEqual(self.get_context_variable('message'), '')
@@ -62,7 +62,7 @@
'search_stats': {'nbfiles': 1, 'pct': 100}}
# when
- rv = self.client.get('/search/?q=sha1:456')
+ rv = self.client.get('/content/search/?q=sha1:456')
self.assertEqual(rv.status_code, 200)
self.assertEqual(self.get_context_variable('message'), '')
@@ -81,7 +81,7 @@
mock_api.api_search.side_effect = BadInputExc('error msg')
# when
- rv = self.client.get('/search/?q=sha1_git:789')
+ rv = self.client.get('/content/search/?q=sha1_git:789')
self.assertEqual(rv.status_code, 200)
self.assertEqual(self.get_context_variable('message'), 'error msg')
@@ -102,7 +102,7 @@
'search_stats': {'nbfiles': 1, 'pct': 100}}
# when
- rv = self.client.get('/search/?q=sha1:123')
+ rv = self.client.get('/content/search/?q=sha1:123')
self.assertEqual(rv.status_code, 200)
self.assertEqual(self.get_context_variable('message'), '')
@@ -127,7 +127,7 @@
'error bad input')
# when (mock_request completes the post request)
- rv = self.client.post('/search/')
+ rv = self.client.post('/content/search/')
# then
self.assertEqual(rv.status_code, 200)
@@ -156,7 +156,7 @@
'found': False}]}
# when (mock_request completes the post request)
- rv = self.client.post('/search/')
+ rv = self.client.post('/content/search/')
# then
self.assertEqual(rv.status_code, 200)
@@ -193,7 +193,7 @@
'found': True}]}
# when (mock_request completes the post request)
- rv = self.client.post('/search/')
+ rv = self.client.post('/content/search/')
# then
self.assertEqual(rv.status_code, 200)
@@ -551,6 +551,22 @@
class OriginView(test_app.SWHViewTestCase):
render_template = False
+ def setUp(self):
+
+ def url_for_test(fn, **args):
+ if fn == 'browse_revision_with_origin':
+ return '/browse/revision/origin/%s/' % args['origin_id']
+ elif fn == 'api_origin_visits':
+ return '/api/1/stat/visits/%s/' % args['origin_id']
+
+ self.url_for_test = url_for_test
+
+ self.stub_origin = {'type': 'git',
+ 'lister': None,
+ 'project': None,
+ 'url': 'rsync://some/url',
+ 'id': 426}
+
@patch('swh.web.ui.views.browse.api')
@istest
def browse_origin_ko_not_found(self, mock_api):
@@ -563,12 +579,12 @@
# then
self.assertEqual(rv.status_code, 200)
self.assert_template_used('origin.html')
- self.assertEqual(self.get_context_variable('origin_id'), 1)
+ self.assertIsNone(self.get_context_variable('origin'))
self.assertEqual(
self.get_context_variable('message'),
'Not found!')
- mock_api.api_origin.assert_called_once_with(1)
+ mock_api.api_origin.assert_called_once_with(1, None, None)
@patch('swh.web.ui.views.browse.api')
@istest
@@ -582,28 +598,19 @@
# then
self.assertEqual(rv.status_code, 200)
self.assert_template_used('origin.html')
- self.assertEqual(self.get_context_variable('origin_id'), 426)
+ self.assertIsNone(self.get_context_variable('origin'))
- mock_api.api_origin.assert_called_once_with(426)
+ mock_api.api_origin.assert_called_once_with(426, None, None)
@patch('swh.web.ui.views.browse.api')
@patch('swh.web.ui.views.browse.url_for')
@istest
- def browse_origin_found(self, mock_url_for, mock_api):
+ def browse_origin_found_id(self, mock_url_for, mock_api):
# given
- def url_for_test(fn, **args):
- if fn == 'browse_revision_with_origin':
- return '/browse/revision/origin/%s/' % args['origin_id']
- elif fn == 'api_origin_visits':
- return '/api/1/stat/visits/%s/' % args['origin_id']
- mock_url_for.side_effect = url_for_test
- mock_origin = {'type': 'git',
- 'lister': None,
- 'project': None,
- 'url': 'rsync://some/url',
- 'id': 426}
- mock_api.api_origin.return_value = mock_origin
+ mock_url_for.side_effect = self.url_for_test
+
+ mock_api.api_origin.return_value = self.stub_origin
# when
rv = self.client.get('/browse/origin/426/')
@@ -611,14 +618,38 @@
# then
self.assertEqual(rv.status_code, 200)
self.assert_template_used('origin.html')
- self.assertEqual(self.get_context_variable('origin_id'), 426)
- self.assertEqual(self.get_context_variable('origin'), mock_origin)
+ self.assertEqual(self.get_context_variable('origin'), self.stub_origin)
+ self.assertEqual(self.get_context_variable('browse_url'),
+ '/browse/revision/origin/426/')
+ self.assertEqual(self.get_context_variable('visit_url'),
+ '/api/1/stat/visits/426/')
+
+ mock_api.api_origin.assert_called_once_with(426, None, None)
+
+ @patch('swh.web.ui.views.browse.api')
+ @patch('swh.web.ui.views.browse.url_for')
+ @istest
+ def browse_origin_found_url_type(self, mock_url_for, mock_api):
+ # given
+
+ mock_url_for.side_effect = self.url_for_test
+
+ mock_api.api_origin.return_value = self.stub_origin
+
+ # when
+ rv = self.client.get('/browse/origin/git/url/rsync://some/url/')
+
+ # then
+ self.assertEqual(rv.status_code, 200)
+ self.assert_template_used('origin.html')
+ self.assertEqual(self.get_context_variable('origin'), self.stub_origin)
self.assertEqual(self.get_context_variable('browse_url'),
'/browse/revision/origin/426/')
self.assertEqual(self.get_context_variable('visit_url'),
'/api/1/stat/visits/426/')
- mock_api.api_origin.assert_called_once_with(426)
+ mock_api.api_origin.assert_called_once_with(None, 'git',
+ 'rsync://some/url')
class PersonView(test_app.SWHViewTestCase):
diff --git a/swh/web/ui/views/api.py b/swh/web/ui/views/api.py
--- a/swh/web/ui/views/api.py
+++ b/swh/web/ui/views/api.py
@@ -40,9 +40,9 @@
return sorted(date_gen)
-@app.route('/api/1/search/', methods=['POST'])
-@app.route('/api/1/search/
/')
-@doc.route('/api/1/search/')
+@app.route('/api/1/content/search/', methods=['POST'])
+@app.route('/api/1/content/search//')
+@doc.route('/api/1/content/search/')
@doc.arg('q',
default='sha1:adc83b19e793491b1c6ea0fd8b46cd9f32e592fc',
argtype=doc.argtypes.algo_and_hash,
@@ -153,21 +153,46 @@
@app.route('/api/1/origin//')
+@app.route('/api/1/origin//url//')
@doc.route('/api/1/origin/')
@doc.arg('origin_id',
default=1,
argtype=doc.argtypes.int,
argdoc="The origin's SWH origin_id.")
+@doc.arg('origin_type',
+ default='git',
+ argtype=doc.argtypes.str,
+ argdoc="The origin's type (git, svn..)")
+@doc.arg('origin_url',
+ default='https://github.com/hylang/hy',
+ argtype=doc.argtypes.path,
+ argdoc="The origin's URL.")
@doc.raises(exc=doc.excs.notfound,
doc='Raised if origin_id does not correspond to an origin in SWH')
@doc.returns(rettype=doc.rettypes.dict,
retdoc='The metadata of the origin identified by origin_id')
-def api_origin(origin_id):
- """Return information about origin with id origin_id.
+def api_origin(origin_id=None, origin_type=None, origin_url=None):
+ """Return information about the origin matching the passed criteria.
+
+ Criteria may be:
+ - An SWH-specific ID, if you already know it
+ - An origin type and its URL, if you do not have the origin's SWH
+ identifier
"""
+ ori_dict = {
+ 'id': origin_id,
+ 'type': origin_type,
+ 'url': origin_url
+ }
+ ori_dict = {k: v for k, v in ori_dict.items() if ori_dict[k]}
+ if 'id' in ori_dict:
+ error_msg = 'Origin with id %s not found.' % ori_dict['id']
+ else:
+ error_msg = 'Origin with type %s and URL %s not found' % (
+ ori_dict['type'], ori_dict['url'])
return _api_lookup(
- origin_id, lookup_fn=service.lookup_origin,
- error_msg_if_not_found='Origin with id %s not found.' % origin_id)
+ ori_dict, lookup_fn=service.lookup_origin,
+ error_msg_if_not_found=error_msg)
@app.route('/api/1/person//')
diff --git a/swh/web/ui/views/browse.py b/swh/web/ui/views/browse.py
--- a/swh/web/ui/views/browse.py
+++ b/swh/web/ui/views/browse.py
@@ -27,7 +27,7 @@
return render_template('api.html', **env)
-@app.route('/search/', methods=['GET', 'POST'])
+@app.route('/content/search/', methods=['GET', 'POST'])
def search():
"""Search for hashes in swh-storage.
@@ -241,22 +241,30 @@
return render_template('directory.html', **env)
+@app.route('/browse/origin//url//')
@app.route('/browse/origin//')
-def browse_origin(origin_id):
- """Browse origin with id id.
+def browse_origin(origin_id=None, origin_type=None, origin_url=None):
+ """Browse origin matching given criteria - either origin_id or
+ origin_type and origin_path.
+ Args:
+ - origin_id: origin's swh identifier
+ - origin_type: origin's type
+ - origin_url: origin's URL
"""
-
- browse_url = url_for('browse_revision_with_origin', origin_id=origin_id)
- visit_url = url_for('api_origin_visits', origin_id=origin_id)
-
- env = {'browse_url': browse_url,
- 'visit_url': visit_url,
- 'origin_id': origin_id,
+ # URLs for the calendar JS plugin
+ env = {'browse_url': None,
+ 'visit_url': None,
'origin': None}
try:
- env['origin'] = api.api_origin(origin_id)
+ origin = api.api_origin(origin_id, origin_type, origin_url)
+ env['origin'] = origin
+ env['browse_url'] = url_for('browse_revision_with_origin',
+ origin_id=origin['id'])
+ env['visit_url'] = url_for('api_origin_visits',
+ origin_id=origin['id'])
+
except (NotFoundExc, BadInputExc) as e:
env['message'] = str(e)