diff --git a/assets/src/bundles/browse/browse-utils.js b/assets/src/bundles/browse/browse-utils.js --- a/assets/src/bundles/browse/browse-utils.js +++ b/assets/src/bundles/browse/browse-utils.js @@ -63,6 +63,14 @@ $('.swh-popover-toggler').popover('hide'); }); + $('#swh-branch-search-form').submit(function(e) { + var searchParams = new URLSearchParams(window.location.search); + const searchString = $('#swh-branch-search-string').val().trim(); + searchParams.set('branch_name_include', searchString); + window.location.search = searchParams.toString(); + e.preventDefault(); + }); + $('body').on('click', e => { if ($(e.target).parents('.swh-popover').length) { e.stopPropagation(); diff --git a/cypress/integration/origin-browse.spec.js b/cypress/integration/origin-browse.spec.js --- a/cypress/integration/origin-browse.spec.js +++ b/cypress/integration/origin-browse.spec.js @@ -83,5 +83,45 @@ cy.get('#swh-browse-snapshot-releases-nav-link') .should('have.class', 'disabled'); }); +}); + +describe('Test browse branches', function() { + beforeEach(function() { + const url = `${this.Urls.browse_origin_branches()}?origin_url=${this.origin[1].url}`; + cy.visit(url); + }); + + it('should have the master branch in the list', function() { + cy.get('table').contains('td', 'master').should('be.visible'); + }); + + it('should search inside the branches', function() { + cy.get('#swh-branch-search-string').type('mas'); + cy.get('#swh-branch-serach-button').click(); + + cy.location('search') + .should('include', 'branch_name_include=mas'); + + cy.get('table').contains('td', 'master').should('be.visible'); + cy.get('#swh-branch-search-string').should('have.value', 'mas'); + }); + + it('should empty search show all branches', function() { + cy.get('#swh-branch-search-string').clear(); + cy.get('#swh-branch-serach-button').click(); + + cy.location('search') + .should('include', 'branch_name_include='); + + cy.get('table').contains('td', 'master').should('be.visible'); + + cy.get('#swh-branch-search-string').should('have.value', ''); + }); + + it('should show no branch exists message on failed search', function() { + cy.get('#swh-branch-search-string').type('random{enter}'); + + cy.get('table').contains('td', 'No branch names containing random have been found!').should('be.visible'); + }); }); diff --git a/swh/web/browse/snapshot_context.py b/swh/web/browse/snapshot_context.py --- a/swh/web/browse/snapshot_context.py +++ b/swh/web/browse/snapshot_context.py @@ -1214,7 +1214,7 @@ def browse_snapshot_branches( - request, snapshot_id=None, origin_url=None, timestamp=None + request, snapshot_id=None, origin_url=None, timestamp=None, branch_name_include=None ): """ Django view implementation for browsing a list of branches in a snapshot @@ -1247,9 +1247,11 @@ branches_from, PER_PAGE + 1, target_types=["revision", "alias"], + branch_name_include_substring=branch_name_include, ) - - displayed_branches, _, _ = process_snapshot_branches(snapshot) + displayed_branches = [] + if snapshot: + displayed_branches, _, _ = process_snapshot_branches(snapshot) for branch in displayed_branches: rev_query_params = {} @@ -1290,7 +1292,7 @@ browse_view_name, url_args=url_args, query_params=query_params ) - if snapshot["next_branch"] is not None: + if snapshot and snapshot["next_branch"] is not None: query_params_next = dict(query_params) next_branch = displayed_branches[-1]["name"] del displayed_branches[-1] @@ -1318,6 +1320,7 @@ "prev_branches_url": prev_branches_url, "next_branches_url": next_branches_url, "snapshot_context": snapshot_context, + "search_string": branch_name_include or "", }, ) @@ -1352,7 +1355,6 @@ PER_PAGE + 1, target_types=["release", "alias"], ) - _, displayed_releases, _ = process_snapshot_branches(snapshot) for release in displayed_releases: diff --git a/swh/web/browse/views/origin.py b/swh/web/browse/views/origin.py --- a/swh/web/browse/views/origin.py +++ b/swh/web/browse/views/origin.py @@ -161,6 +161,7 @@ origin_url=request.GET.get("origin_url"), snapshot_id=request.GET.get("snapshot"), timestamp=request.GET.get("timestamp"), + branch_name_include=request.GET.get("branch_name_include"), ) diff --git a/swh/web/common/archive.py b/swh/web/common/archive.py --- a/swh/web/common/archive.py +++ b/swh/web/common/archive.py @@ -1085,11 +1085,16 @@ branch_name_exclude_prefix: if provided, do not return branches whose name starts with given pattern + Raises: + NotFoundException if the given snapshot_id is missing Returns: A dict filled with the snapshot content. """ snapshot_id_bin = _to_sha1_bin(snapshot_id) + if storage.snapshot_missing([snapshot_id_bin]): + raise NotFoundExc(f"Snapshot with id {snapshot_id} not found!") + partial_branches = storage.snapshot_get_branches( snapshot_id_bin, branches_from.encode(), @@ -1100,10 +1105,9 @@ else None, branch_name_exclude_prefix.encode() if branch_name_exclude_prefix else None, ) - if not partial_branches: - raise NotFoundExc(f"Snapshot with id {snapshot_id} not found!") - - return converters.from_partial_branches(partial_branches) + return ( + converters.from_partial_branches(partial_branches) if partial_branches else None + ) def lookup_latest_origin_snapshot( diff --git a/swh/web/templates/browse/branches.html b/swh/web/templates/browse/branches.html --- a/swh/web/templates/browse/branches.html +++ b/swh/web/templates/browse/branches.html @@ -11,8 +11,18 @@ {% load swh_templatetags %} {% block swh-browse-content %} -{% if displayed_branches|length > 0 %}
+
+
+ +
+ +
+
+
@@ -23,54 +33,63 @@ - {% for branch in displayed_branches %} - - + {% if displayed_branches|length > 0 %} + {% for branch in displayed_branches %} + + + + + + + {% endfor %} + {% else %} + - - - {% endfor %} + {% endif %}
- - {% if branch.alias %} - - {% else %} - - {% endif %} - {{ branch.name }} - -
+ + {% if branch.alias %} + + {% else %} + + {% endif %} + {{ branch.name }} + + + + {{ branch.revision|slice:":7" }} + + + {{ branch.message }} + + {{ branch.date }} +
- - {{ branch.revision|slice:":7" }} - - - {{ branch.message }} - - {{ branch.date }} + {% if search_string %} + No branch names containing {{search_string}} have been found! + {% else %} + The list of branches is empty! + {% endif %}
- -{% else %} - The list of branches is empty ! -{% endif %} {% endblock %} + {% block swh-browse-after-content %} -{% if prev_branches_url or next_branches_url %} - + {% endif %} {% endblock %} diff --git a/swh/web/tests/common/test_archive.py b/swh/web/tests/common/test_archive.py --- a/swh/web/tests/common/test_archive.py +++ b/swh/web/tests/common/test_archive.py @@ -1069,6 +1069,34 @@ assert resolved_alias["target"] is not None +@given(revision()) +def test_lookup_snapshot_missing(revision): + with pytest.raises(NotFoundExc): + archive.lookup_snapshot(revision) + + +@given(revision()) +def test_lookup_snapshot_empty_branch_list(archive_data, revision): + rev_id = hash_to_bytes(revision) + snapshot = Snapshot( + branches={ + b"refs/heads/master": SnapshotBranch( + target=rev_id, target_type=TargetType.REVISION, + ), + }, + ) + archive_data.snapshot_add([snapshot]) + + # FIXME; This test will change once the inconsistency in storage is fixed + # postgres backend returns None in case of a missing branch whereas the + # in-memory implementation (used in tests) returns a data structure; + # hence the inconsistency + branches = archive.lookup_snapshot( + hash_to_hex(snapshot.id), branch_name_include_substring="non-existing", + )["branches"] + assert not branches + + @given(revision()) def test_lookup_snapshot_branch_names_filtering(archive_data, revision): rev_id = hash_to_bytes(revision)