diff --git a/swh/web/api/views/origin.py b/swh/web/api/views/origin.py --- a/swh/web/api/views/origin.py +++ b/swh/web/api/views/origin.py @@ -478,6 +478,48 @@ return result +@api_route(r'/origin/(?P.*)/visit/latest/', + 'api-1-origin-visit-latest') +@api_doc('/origin/visit/') +@format_docstring(return_origin_visit=DOC_RETURN_ORIGIN_VISIT) +def api_origin_visit_latest(request, origin_url=None): + """ + .. http:get:: /api/1/origin/(origin_url)/visit/latest/ + + Get information about a specific visit of a software origin. + + :param str origin_url: a software origin URL + :query boolean require_snapshot: if true, only return a visit + with a snapshot + + {common_headers} + + {return_origin_visit} + + **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`, + :http:method:`options` + + :statuscode 200: no error + :statuscode 404: requested origin or visit can not be found in the + archive + + **Example:** + + .. parsed-literal:: + + :swh_web_api:`origin/https://github.com/hylang/hy/visit/latest/` + """ + require_snapshot = request.query_params.get('require_snapshot', 'false') + return api_lookup( + service.lookup_origin_visit_latest, origin_url, + bool(strtobool(require_snapshot)), + notfound_msg=('No visit for origin {} found' + .format(origin_url)), + enrich_fn=partial(_enrich_origin_visit, + with_origin_link=True, + with_origin_visit_link=False)) + + @api_route(r'/origin/(?P.*)/visit/(?P[0-9]+)/', 'api-1-origin-visit') @api_route(r'/origin/(?P[0-9]+)/visit/(?P[0-9]+)/', diff --git a/swh/web/common/service.py b/swh/web/common/service.py --- a/swh/web/common/service.py +++ b/swh/web/common/service.py @@ -852,6 +852,25 @@ yield converters.from_origin_visit(visit) +def lookup_origin_visit_latest(origin_url, require_snapshot): + """Return the origin's latest visit + + Args: + origin_url (str): origin to list visits for + require_snapshot (bool): filter out origins without a snapshot + + Returns: + dict: The origin_visit concerned + + """ + visit = storage.origin_visit_get_latest( + origin_url, require_snapshot=require_snapshot) + if isinstance(visit['origin'], int): + # soon-to-be-legacy origin ids + visit['origin'] = storage.origin_get({'id': visit['origin']})['url'] + return converters.from_origin_visit(visit) + + def lookup_origin_visit(origin_url, visit_id): """Return information about visit visit_id with origin origin. diff --git a/swh/web/tests/api/views/test_origin.py b/swh/web/tests/api/views/test_origin.py --- a/swh/web/tests/api/views/test_origin.py +++ b/swh/web/tests/api/views/test_origin.py @@ -201,6 +201,79 @@ self.assertEqual(rv.data, expected_visit) + @given(new_origin(), visit_dates(2), new_snapshots(1)) + def test_api_lookup_origin_visit_latest( + self, new_origin, visit_dates, new_snapshots): + + origin_id = self.storage.origin_add_one(new_origin) + new_origin['id'] = origin_id + visit_dates.sort() + visit_ids = [] + for i, visit_date in enumerate(visit_dates): + origin_visit = self.storage.origin_visit_add(origin_id, visit_date) + visit_ids.append(origin_visit['visit']) + + self.storage.snapshot_add([new_snapshots[0]]) + self.storage.origin_visit_update( + origin_id, visit_ids[0], + snapshot=new_snapshots[0]['id']) + + url = reverse('api-1-origin-visit-latest', + url_args={'origin_url': new_origin['url']}) + + rv = self.client.get(url) + self.assertEqual(rv.status_code, 200, rv.data) + self.assertEqual(rv['Content-Type'], 'application/json') + + expected_visit = self.origin_visit_get_by(origin_id, visit_ids[1]) + + origin_url = reverse('api-1-origin', + url_args={'origin_url': new_origin['url']}) + + expected_visit['origin'] = new_origin['url'] + expected_visit['origin_url'] = origin_url + expected_visit['snapshot_url'] = None + + self.assertEqual(rv.data, expected_visit) + + @given(new_origin(), visit_dates(2), new_snapshots(1)) + def test_api_lookup_origin_visit_latest_with_snapshot( + self, new_origin, visit_dates, new_snapshots): + origin_id = self.storage.origin_add_one(new_origin) + new_origin['id'] = origin_id + visit_dates.sort() + visit_ids = [] + for i, visit_date in enumerate(visit_dates): + origin_visit = self.storage.origin_visit_add(origin_id, visit_date) + visit_ids.append(origin_visit['visit']) + + self.storage.snapshot_add([new_snapshots[0]]) + self.storage.origin_visit_update( + origin_id, visit_ids[0], + snapshot=new_snapshots[0]['id']) + + url = reverse('api-1-origin-visit-latest', + url_args={'origin_url': new_origin['url']}) + url += '?require_snapshot=true' + + rv = self.client.get(url) + self.assertEqual(rv.status_code, 200, rv.data) + self.assertEqual(rv['Content-Type'], 'application/json') + + expected_visit = self.origin_visit_get_by(origin_id, visit_ids[0]) + + origin_url = reverse('api-1-origin', + url_args={'origin_url': new_origin['url']}) + snapshot_url = reverse( + 'api-1-snapshot', + url_args={'snapshot_id': expected_visit['snapshot']}) + + expected_visit['origin'] = new_origin['url'] + expected_visit['origin_url'] = origin_url + expected_visit['snapshot_url'] = snapshot_url + + self.assertEqual(rv.data, expected_visit) + @pytest.mark.origin_id @given(new_origin(), visit_dates(3), new_snapshots(3)) def test_api_lookup_origin_visit_by_id(self, new_origin, visit_dates,