diff --git a/assets/src/bundles/browse/origin-search.js b/assets/src/bundles/browse/origin-search.js --- a/assets/src/bundles/browse/origin-search.js +++ b/assets/src/bundles/browse/origin-search.js @@ -200,27 +200,32 @@ $(document).ready(() => { $('#swh-search-origins').submit(event => { event.preventDefault(); - let searchQueryText = $('#swh-origins-url-patterns').val().trim(); - let withVisit = $('#swh-search-origins-with-visit').prop('checked'); - let withContent = $('#swh-filter-empty-visits').prop('checked'); - let searchMetadata = $('#swh-search-origin-metadata').prop('checked'); - const visitType = $('#swh-search-visit-type').val(); - let queryParameters = new URLSearchParams(); - queryParameters.append('q', searchQueryText); - if (withVisit) { - queryParameters.append('with_visit', withVisit); - } - if (withContent) { - queryParameters.append('with_content', withContent); - } - if (searchMetadata) { - queryParameters.append('search_metadata', searchMetadata); - } - if (visitType !== 'any') { - queryParameters.append('visit_type', visitType); + if (event.target.checkValidity()) { + $(event.target).removeClass('was-validated'); + let searchQueryText = $('#swh-origins-url-patterns').val().trim(); + let withVisit = $('#swh-search-origins-with-visit').prop('checked'); + let withContent = $('#swh-filter-empty-visits').prop('checked'); + let searchMetadata = $('#swh-search-origin-metadata').prop('checked'); + const visitType = $('#swh-search-visit-type').val(); + let queryParameters = new URLSearchParams(); + queryParameters.append('q', searchQueryText); + if (withVisit) { + queryParameters.append('with_visit', withVisit); + } + if (withContent) { + queryParameters.append('with_content', withContent); + } + if (searchMetadata) { + queryParameters.append('search_metadata', searchMetadata); + } + if (visitType !== 'any') { + queryParameters.append('visit_type', visitType); + } + // Update the url, triggering page reload and effective search + window.location = `${Urls.browse_search()}?${queryParameters.toString()}`; + } else { + $(event.target).addClass('was-validated'); } - // Update the url, triggering page reload and effective search - window.location = `${Urls.browse_search()}?${queryParameters.toString()}`; }); $('#origins-next-results-button').click(event => { diff --git a/assets/src/bundles/webapp/webapp-utils.js b/assets/src/bundles/webapp/webapp-utils.js --- a/assets/src/bundles/webapp/webapp-utils.js +++ b/assets/src/bundles/webapp/webapp-utils.js @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018-2020 The Software Heritage developers + * Copyright (C) 2018-2021 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information @@ -168,14 +168,18 @@ // navbar search form submission callback $('#swh-origins-search-top').submit(event => { event.preventDefault(); - let searchQueryText = $('#swh-origins-search-top-input').val().trim(); - let queryParameters = new URLSearchParams(); - queryParameters.append('q', searchQueryText); - queryParameters.append('with_visit', true); - queryParameters.append('with_content', true); - window.location = `${Urls.browse_search()}?${queryParameters.toString()}`; + if (event.target.checkValidity()) { + $(event.target).removeClass('was-validated'); + let searchQueryText = $('#swh-origins-search-top-input').val().trim(); + let queryParameters = new URLSearchParams(); + queryParameters.append('q', searchQueryText); + queryParameters.append('with_visit', true); + queryParameters.append('with_content', true); + window.location = `${Urls.browse_search()}?${queryParameters.toString()}`; + } else { + $(event.target).addClass('was-validated'); + } }); - }); export function initPage(page) { @@ -355,3 +359,18 @@ setFullWidth(previousFullWidthState); } } + +export async function validateSWHIDInput(swhidInputElt) { + const swhidInput = swhidInputElt.value.trim(); + let customValidity = ''; + if (swhidInput.startsWith('swh:')) { + const resolveSWHIDUrl = Urls.api_1_resolve_swhid(swhidInput); + const response = await fetch(resolveSWHIDUrl); + const responseData = await response.json(); + if (responseData.hasOwnProperty('exception')) { + customValidity = responseData.reason; + } + } + swhidInputElt.setCustomValidity(customValidity); + $(swhidInputElt).siblings('.invalid-feedback').text(customValidity); +} diff --git a/assets/src/bundles/webapp/webapp.css b/assets/src/bundles/webapp/webapp.css --- a/assets/src/bundles/webapp/webapp.css +++ b/assets/src/bundles/webapp/webapp.css @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018-2020 The Software Heritage developers + * Copyright (C) 2018-2021 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information @@ -699,3 +699,7 @@ right: -65px; } } + +.invalid-feedback { + font-size: 100%; +} diff --git a/cypress/integration/origin-search.spec.js b/cypress/integration/origin-search.spec.js --- a/cypress/integration/origin-search.spec.js +++ b/cypress/integration/origin-search.spec.js @@ -10,11 +10,21 @@ let origin; let url; -function doSearch(searchText) { - cy.get('#swh-origins-url-patterns') - .type(searchText) +function doSearch(searchText, searchInputElt = '#swh-origins-url-patterns') { + if (searchText.startsWith('swh:')) { + cy.intercept('**/api/1/resolve/**') + .as('swhidResolve'); + } + cy.get(searchInputElt) + // to avoid sending too much SWHID validation requests + // as cypress insert character one by one when using type + .invoke('val', searchText.slice(0, -1)) + .type(searchText.slice(-1)) .get('.swh-search-icon') .click(); + if (searchText.startsWith('swh:')) { + cy.wait('@swhidResolve'); + } } function searchShouldRedirect(searchText, redirectUrl) { @@ -25,9 +35,11 @@ function searchShouldShowNotFound(searchText, msg) { doSearch(searchText); - cy.get('#swh-no-result') - .should('be.visible') - .and('contain', msg); + if (searchText.startsWith('swh:')) { + cy.get('.invalid-feedback') + .should('be.visible') + .and('contain', msg); + } } function stubOriginVisitLatestRequests(status = 200, response = {type: 'tar'}) { @@ -498,6 +510,28 @@ searchShouldShowNotFound(swhid, msg); }); + + function checkInvalidSWHIDReport(url, searchInputElt, repoData) { + const invalidSWHID = `swh:1:cnt:${repoData.content[0].sha1git};lines=45-60/`; + cy.visit(url); + doSearch(invalidSWHID, searchInputElt); + cy.get(searchInputElt) + .then($el => $el[0].checkValidity()).should('be.false'); + cy.get(searchInputElt) + .invoke('prop', 'validationMessage') + .should('not.equal', ''); + + } + + it('should report invalid SWHID in search page input', function() { + checkInvalidSWHIDReport(this.Urls.browse_search(), '#swh-origins-url-patterns', this.unarchivedRepo); + cy.get('.invalid-feedback') + .should('be.visible'); + }); + + it('should report invalid SWHID in top right search input', function() { + checkInvalidSWHIDReport(this.Urls.browse_help(), '#swh-origins-search-top-input', this.unarchivedRepo); + }); }); }); diff --git a/swh/web/templates/includes/origin-search-form.html b/swh/web/templates/includes/origin-search-form.html --- a/swh/web/templates/includes/origin-search-form.html +++ b/swh/web/templates/includes/origin-search-form.html @@ -1,18 +1,20 @@ {% comment %} -Copyright (C) 2020 The Software Heritage developers +Copyright (C) 2020-2021 The Software Heritage developers See the AUTHORS file at the top-level directory of this distribution License: GNU Affero General Public License version 3, or any later version See top-level LICENSE file for more information {% endcomment %} -
+
+ type="text" id="swh-origins-url-patterns" + oninput="swh.webapp.validateSWHIDInput(this)" autofocus required>
+
diff --git a/swh/web/templates/layout.html b/swh/web/templates/layout.html --- a/swh/web/templates/layout.html +++ b/swh/web/templates/layout.html @@ -1,5 +1,5 @@ {% comment %} -Copyright (C) 2015-2020 The Software Heritage developers +Copyright (C) 2015-2021 The Software Heritage developers See the AUTHORS file at the top-level directory of this distribution License: GNU Affero General Public License version 3, or any later version See top-level LICENSE file for more information @@ -26,7 +26,7 @@ /* @licstart The following is the entire license notice for the JavaScript code in this page. -Copyright (C) 2015-2020 The Software Heritage developers +Copyright (C) 2015-2021 The Software Heritage developers This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -149,11 +149,12 @@
{% block navbar-content %}{% endblock %} {% if request.resolver_match.url_name != 'swh-web-homepage' and request.resolver_match.url_name != 'browse-search' %} - + type="text" id="swh-origins-search-top-input" + oninput="swh.webapp.validateSWHIDInput(this)" required/>