Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F7163751
D2378.id8406.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
13 KB
Subscribers
None
D2378.id8406.diff
View Options
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
@@ -89,6 +89,209 @@
});
});
+ context('Test pagination', function() {
+ it('should not paginate if there are not many results', function() {
+ // Setup search
+ cy.get('#swh-search-origins-with-visit')
+ .uncheck()
+ .get('#swh-filter-empty-visits')
+ .uncheck()
+ .then(() => {
+ const searchText = 'libtess';
+
+ // Get first page of results
+ doSearch(searchText);
+
+ cy.get('.swh-search-result-entry')
+ .should('have.length', 1);
+
+ cy.get('.swh-search-result-entry#origin-0 td a')
+ .should('have.text', 'https://github.com/memononen/libtess2');
+
+ cy.get('#origins-prev-results-button')
+ .should('have.class', 'disabled');
+ cy.get('#origins-next-results-button')
+ .should('have.class', 'disabled');
+ });
+ });
+
+ it('should paginate forward when there are many results', function() {
+ // Setup search
+ cy.get('#swh-search-origins-with-visit')
+ .uncheck()
+ .get('#swh-filter-empty-visits')
+ .uncheck()
+ .then(() => {
+ const searchText = 'many.origins';
+
+ // Get first page of results
+ doSearch(searchText);
+
+ cy.get('.swh-search-result-entry')
+ .should('have.length', 100);
+
+ cy.get('.swh-search-result-entry#origin-0 td a')
+ .should('have.text', 'https://many.origins/1');
+ cy.get('.swh-search-result-entry#origin-99 td a')
+ .should('have.text', 'https://many.origins/100');
+
+ cy.get('#origins-prev-results-button')
+ .should('have.class', 'disabled');
+ cy.get('#origins-next-results-button')
+ .should('not.have.class', 'disabled');
+
+ // Get second page of results
+ cy.get('#origins-next-results-button a')
+ .click();
+
+ cy.get('.swh-search-result-entry')
+ .should('have.length', 100);
+
+ cy.get('.swh-search-result-entry#origin-0 td a')
+ .should('have.text', 'https://many.origins/101');
+ cy.get('.swh-search-result-entry#origin-99 td a')
+ .should('have.text', 'https://many.origins/200');
+
+ cy.get('#origins-prev-results-button')
+ .should('not.have.class', 'disabled');
+ cy.get('#origins-next-results-button')
+ .should('not.have.class', 'disabled');
+
+ // Get third (and last) page of results
+ cy.get('#origins-next-results-button a')
+ .click();
+
+ cy.get('.swh-search-result-entry')
+ .should('have.length', 50);
+
+ cy.get('.swh-search-result-entry#origin-0 td a')
+ .should('have.text', 'https://many.origins/201');
+ cy.get('.swh-search-result-entry#origin-49 td a')
+ .should('have.text', 'https://many.origins/250');
+
+ cy.get('#origins-prev-results-button')
+ .should('not.have.class', 'disabled');
+ cy.get('#origins-next-results-button')
+ .should('have.class', 'disabled');
+ });
+ });
+
+ it('should paginate backward from a middle page', function() {
+ // Setup search
+ cy.get('#swh-search-origins-with-visit')
+ .uncheck()
+ .get('#swh-filter-empty-visits')
+ .uncheck()
+ .then(() => {
+ const searchText = 'many.origins';
+
+ // Get first page of results
+ doSearch(searchText);
+
+ cy.get('#origins-prev-results-button')
+ .should('have.class', 'disabled');
+ cy.get('#origins-next-results-button')
+ .should('not.have.class', 'disabled');
+
+ // Get second page of results
+ cy.get('#origins-next-results-button a')
+ .click();
+ cy.get('#origins-prev-results-button')
+ .should('not.have.class', 'disabled');
+ cy.get('#origins-next-results-button')
+ .should('not.have.class', 'disabled');
+
+ // Get first page of results again
+ cy.get('#origins-prev-results-button a')
+ .click();
+
+ cy.get('.swh-search-result-entry')
+ .should('have.length', 100);
+
+ cy.get('.swh-search-result-entry#origin-0 td a')
+ .should('have.text', 'https://many.origins/1');
+ cy.get('.swh-search-result-entry#origin-99 td a')
+ .should('have.text', 'https://many.origins/100');
+
+ cy.get('#origins-prev-results-button')
+ .should('have.class', 'disabled');
+ cy.get('#origins-next-results-button')
+ .should('not.have.class', 'disabled');
+ });
+ });
+
+ it('should paginate backward from the last page', function() {
+ // Setup search
+ cy.get('#swh-search-origins-with-visit')
+ .uncheck()
+ .get('#swh-filter-empty-visits')
+ .uncheck()
+ .then(() => {
+ const searchText = 'many.origins';
+
+ // Get first page of results
+ doSearch(searchText);
+
+ cy.get('#origins-prev-results-button')
+ .should('have.class', 'disabled');
+ cy.get('#origins-next-results-button')
+ .should('not.have.class', 'disabled');
+
+ // Get second page of results
+ cy.get('#origins-next-results-button a')
+ .click();
+
+ cy.get('#origins-prev-results-button')
+ .should('not.have.class', 'disabled');
+ cy.get('#origins-next-results-button')
+ .should('not.have.class', 'disabled');
+
+ // Get third (and last) page of results
+ cy.get('#origins-next-results-button a')
+ .click();
+
+ cy.get('#origins-prev-results-button')
+ .should('not.have.class', 'disabled');
+ cy.get('#origins-next-results-button')
+ .should('have.class', 'disabled');
+
+ // Get second page of results again
+ cy.get('#origins-prev-results-button a')
+ .click();
+
+ cy.get('.swh-search-result-entry')
+ .should('have.length', 100);
+
+ cy.get('.swh-search-result-entry#origin-0 td a')
+ .should('have.text', 'https://many.origins/101');
+ cy.get('.swh-search-result-entry#origin-99 td a')
+ .should('have.text', 'https://many.origins/200');
+
+ cy.get('#origins-prev-results-button')
+ .should('not.have.class', 'disabled');
+ cy.get('#origins-next-results-button')
+ .should('not.have.class', 'disabled');
+
+ // Get first page of results again
+ cy.get('#origins-prev-results-button a')
+ .click();
+
+ cy.get('.swh-search-result-entry')
+ .should('have.length', 100);
+
+ cy.get('.swh-search-result-entry#origin-0 td a')
+ .should('have.text', 'https://many.origins/1');
+ cy.get('.swh-search-result-entry#origin-99 td a')
+ .should('have.text', 'https://many.origins/100');
+
+ cy.get('#origins-prev-results-button')
+ .should('have.class', 'disabled');
+ cy.get('#origins-next-results-button')
+ .should('not.have.class', 'disabled');
+ });
+ });
+ });
+
context('Test valid persistent ids', function() {
it('should resolve directory', function() {
const redirectUrl = this.Urls.browse_directory(origin.content[0].directory);
diff --git a/swh/web/assets/src/bundles/browse/origin-search.js b/swh/web/assets/src/bundles/browse/origin-search.js
--- a/swh/web/assets/src/bundles/browse/origin-search.js
+++ b/swh/web/assets/src/bundles/browse/origin-search.js
@@ -8,13 +8,17 @@
import {heapsPermute} from 'utils/heaps-permute';
import {handleFetchError} from 'utils/functions';
-let originPatterns;
-let perPage = 100;
-let limit = perPage * 2;
-let offset = 0;
-let currentData = null;
+const limit = 100;
+let linksPrev = [];
+let linkNext = null;
+let linkCurrent = null;
let inSearch = false;
+function parseLinkHeader(s) {
+ let re = /<(.+)>; rel="next"/;
+ return s.match(re)[1];
+}
+
function fixTableRowsStyle() {
setTimeout(() => {
$('#origin-search-results tbody tr').removeAttr('style');
@@ -25,15 +29,13 @@
$('#origin-search-results tbody tr').remove();
}
-function populateOriginSearchResultsTable(origins, offset) {
- let localOffset = offset % limit;
+function populateOriginSearchResultsTable(origins) {
if (origins.length > 0) {
$('#swh-origin-search-results').show();
$('#swh-no-result').hide();
clearOriginSearchResultsTable();
let table = $('#origin-search-results tbody');
- for (let i = localOffset; i < localOffset + perPage && i < origins.length; ++i) {
- let origin = origins[i];
+ for (let [i, origin] of origins.entries()) {
let browseUrl = Urls.browse_origin(origin.url);
let tableRow = `<tr id="origin-${i}" class="swh-search-result-entry swh-tr-hover-highlight">`;
tableRow += `<td style="white-space: nowrap;"><a href="${encodeURI(browseUrl)}">${encodeURI(origin.url)}</a></td>`;
@@ -65,17 +67,19 @@
$('#swh-no-result').text('No origins matching the search criteria were found.');
$('#swh-no-result').show();
}
- if (origins.length - localOffset < perPage ||
- (origins.length < limit && (localOffset + perPage) === origins.length)) {
+
+ if (linkNext === null) {
$('#origins-next-results-button').addClass('disabled');
} else {
$('#origins-next-results-button').removeClass('disabled');
}
- if (offset > 0) {
- $('#origins-prev-results-button').removeClass('disabled');
- } else {
+
+ if (linksPrev.length === 0) {
$('#origins-prev-results-button').addClass('disabled');
+ } else {
+ $('#origins-prev-results-button').removeClass('disabled');
}
+
inSearch = false;
setTimeout(() => {
window.scrollTo(0, 0);
@@ -87,13 +91,12 @@
return str.replace(matchOperatorsRe, '%5C$&');
}
-function searchOrigins(patterns, limit, searchOffset, offset) {
+function searchOriginsFirst(patterns, limit) {
let baseSearchUrl;
let searchMetadata = $('#swh-search-origin-metadata').prop('checked');
if (searchMetadata) {
baseSearchUrl = Urls.api_1_origin_metadata_search() + `?fulltext=${patterns}`;
} else {
- originPatterns = patterns;
let patternsArray = patterns.trim().replace(/\s+/g, ' ').split(' ');
for (let i = 0; i < patternsArray.length; ++i) {
patternsArray[i] = escapeStringRegexp(patternsArray[i]);
@@ -111,17 +114,35 @@
}
let withVisit = $('#swh-search-origins-with-visit').prop('checked');
- let searchUrl = baseSearchUrl + `&limit=${limit}&offset=${searchOffset}&with_visit=${withVisit}`;
+ let searchUrl = baseSearchUrl + `&limit=${limit}&with_visit=${withVisit}`;
+ searchOrigins(searchUrl);
+}
+function searchOrigins(searchUrl) {
clearOriginSearchResultsTable();
$('.swh-loading').addClass('show');
- fetch(searchUrl)
+ let response = fetch(searchUrl)
.then(handleFetchError)
- .then(response => response.json())
+ .then(resp => {
+ response = resp;
+ return response.json();
+ })
.then(data => {
- currentData = data;
+ // Save link to the current results page
+ linkCurrent = searchUrl;
+ // Save link to the next results page.
+ linkNext = null;
+ if (response.headers.has('Link')) {
+ let parsedLink = parseLinkHeader(response.headers.get('Link'));
+ if (parsedLink !== undefined) {
+ linkNext = parsedLink;
+ }
+ }
+ // prevLinks is updated by the caller, which is the one to know if
+ // we're going forward or backward in the pages.
+
$('.swh-loading').removeClass('show');
- populateOriginSearchResultsTable(data, offset);
+ populateOriginSearchResultsTable(data);
})
.catch(response => {
$('.swh-loading').removeClass('show');
@@ -135,7 +156,6 @@
function doSearch() {
$('#swh-no-result').hide();
let patterns = $('#origins-url-patterns').val();
- offset = 0;
inSearch = true;
// first try to resolve a swh persistent identifier
let resolvePidUrl = Urls.api_1_resolve_swh_pid(patterns);
@@ -162,7 +182,7 @@
// otherwise, proceed with origins search
$('#swh-origin-search-results').show();
$('.swh-search-pagination').show();
- searchOrigins(patterns, limit, offset, offset);
+ searchOriginsFirst(patterns, limit);
}
});
}
@@ -194,12 +214,8 @@
return;
}
inSearch = true;
- offset += perPage;
- if (!currentData || (offset >= limit && offset % limit === 0)) {
- searchOrigins(originPatterns, limit, offset, offset);
- } else {
- populateOriginSearchResultsTable(currentData, offset);
- }
+ linksPrev.push(linkCurrent);
+ searchOrigins(linkNext);
event.preventDefault();
});
@@ -208,12 +224,7 @@
return;
}
inSearch = true;
- offset -= perPage;
- if (!currentData || (offset > 0 && (offset + perPage) % limit === 0)) {
- searchOrigins(originPatterns, limit, (offset + perPage) - limit, offset);
- } else {
- populateOriginSearchResultsTable(currentData, offset);
- }
+ searchOrigins(linksPrev.pop());
event.preventDefault();
});
diff --git a/swh/web/tests/data.py b/swh/web/tests/data.py
--- a/swh/web/tests/data.py
+++ b/swh/web/tests/data.py
@@ -184,6 +184,11 @@
origin.update(storage.origin_get(origin)) # add an 'id' key if enabled
+ for i in range(250):
+ url = 'https://many.origins/%d' % (i+1)
+ storage.origin_add([{'url': url}])
+ storage.origin_visit_add(url, '2019-12-03 13:55:05', 'tar')
+
contents = set()
directories = set()
revisions = set()
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Jan 30, 2:42 PM (7 h, 47 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3231281
Attached To
D2378: Paginate origin search results using the Link header instead of offsets.
Event Timeline
Log In to Comment