diff --git a/cypress/integration/origin-save.spec.js b/cypress/integration/origin-save.spec.js
index 28a00c42..a67bd07b 100644
--- a/cypress/integration/origin-save.spec.js
+++ b/cypress/integration/origin-save.spec.js
@@ -1,346 +1,346 @@
/**
* Copyright (C) 2019-2020 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
*/
let url;
let origin;
const $ = Cypress.$;
const saveCodeMsg = {
'success': 'The "save code now" request has been accepted and will be processed as soon as possible.',
'warning': 'The "save code now" request has been put in pending state and may be accepted for processing after manual review.',
'rejected': 'The "save code now" request has been rejected because the provided origin url is blacklisted.',
'rateLimit': 'The rate limit for "save code now" requests has been reached. Please try again later.',
'unknownError': 'An unexpected error happened when submitting the "save code now request',
'csrfError': 'CSRF Failed: Referrer checking failed - no Referrer.'
};
function makeOriginSaveRequest(originType, originUrl) {
cy.get('#swh-input-origin-url')
.type(originUrl)
.get('#swh-input-visit-type')
.select(originType)
.get('#swh-save-origin-form')
.submit();
}
function checkAlertVisible(alertType, msg) {
cy.get('#swh-origin-save-request-status')
.should('be.visible')
.find(`.alert-${alertType}`)
.should('be.visible')
.and('contain', msg);
}
// Stub requests to save an origin
function stubSaveRequest({
requestUrl,
visitType = 'git',
saveRequestStatus,
originUrl,
saveTaskStatus,
responseStatus = 200,
errorMessage = '',
saveRequestDate = new Date(),
visitDate = new Date()
} = {}) {
let response;
if (responseStatus !== 200 && errorMessage) {
response = {
'detail': errorMessage
};
} else {
response = genOriginSaveResponse({visitType: visitType,
saveRequestStatus: saveRequestStatus,
originUrl: originUrl,
saveRequestDate: saveRequestDate,
saveTaskStatus: saveTaskStatus,
visitDate: visitDate});
}
cy.route({
method: 'POST',
status: responseStatus,
url: requestUrl,
response: response
}).as('saveRequest');
}
// Mocks API response : /save/(:visit_type)/(:origin_url)
// visit_type : {'git', 'hg', 'svn'}
function genOriginSaveResponse({
visitType = 'git',
saveRequestStatus,
originUrl,
saveRequestDate = new Date(),
saveTaskStatus,
visitDate = new Date()
} = {}) {
return {
'visit_type': visitType,
'save_request_status': saveRequestStatus,
'origin_url': originUrl,
'id': 1,
'save_request_date': saveRequestDate ? saveRequestDate.toISOString() : null,
'save_task_status': saveTaskStatus,
'visit_date': visitDate ? visitDate.toISOString() : null
};
};
describe('Origin Save Tests', function() {
before(function() {
url = this.Urls.origin_save();
origin = this.origin[0];
this.originSaveUrl = this.Urls.origin_save_request(origin.type, origin.url);
});
beforeEach(function() {
cy.fixture('origin-save').as('originSaveJSON');
cy.fixture('save-task-info').as('saveTaskInfoJSON');
cy.visit(url);
cy.server();
});
it('should display accepted message when accepted', function() {
stubSaveRequest({requestUrl: this.originSaveUrl,
saveRequestStatus: 'accepted',
originUrl: origin.url,
saveTaskStatus: 'not yet scheduled'});
makeOriginSaveRequest(origin.type, origin.url);
cy.wait('@saveRequest').then(() => {
checkAlertVisible('success', saveCodeMsg['success']);
});
});
it('should validate gitlab subproject url', function() {
const gitlabSubProjectUrl = 'https://gitlab.com/user/project/sub/';
const originSaveUrl = this.Urls.origin_save_request('git', gitlabSubProjectUrl);
stubSaveRequest({requestUrl: originSaveUrl,
saveRequestStatus: 'accepted',
originurl: gitlabSubProjectUrl,
saveTaskStatus: 'not yet scheduled'});
makeOriginSaveRequest('git', gitlabSubProjectUrl);
cy.wait('@saveRequest').then(() => {
checkAlertVisible('success', saveCodeMsg['success']);
});
});
it('should display warning message when pending', function() {
stubSaveRequest({requestUrl: this.originSaveUrl,
saveRequestStatus: 'pending',
originUrl: origin.url,
saveTaskStatus: 'not created'});
makeOriginSaveRequest(origin.type, origin.url);
cy.wait('@saveRequest').then(() => {
checkAlertVisible('warning', saveCodeMsg['warning']);
});
});
it('should show error when csrf validation failed (status: 403)', function() {
stubSaveRequest({requestUrl: this.originSaveUrl,
saveRequestStatus: 'rejected',
originUrl: origin.url,
saveTaskStatus: 'not created',
responseStatus: 403,
errorMessage: saveCodeMsg['csrfError']});
makeOriginSaveRequest(origin.type, origin.url);
cy.wait('@saveRequest').then(() => {
checkAlertVisible('danger', saveCodeMsg['csrfError']);
});
});
it('should show error when origin is rejected (status: 403)', function() {
stubSaveRequest({requestUrl: this.originSaveUrl,
saveRequestStatus: 'rejected',
originUrl: origin.url,
saveTaskStatus: 'not created',
responseStatus: 403,
errorMessage: saveCodeMsg['rejected']});
makeOriginSaveRequest(origin.type, origin.url);
cy.wait('@saveRequest').then(() => {
checkAlertVisible('danger', saveCodeMsg['rejected']);
});
});
it('should show error when rate limited (status: 429)', function() {
stubSaveRequest({requestUrl: this.originSaveUrl,
saveRequestStatus: 'Request was throttled. Expected available in 60 seconds.',
originUrl: origin.url,
saveTaskStatus: 'not created',
responseStatus: 429});
makeOriginSaveRequest(origin.type, origin.url);
cy.wait('@saveRequest').then(() => {
checkAlertVisible('danger', saveCodeMsg['rateLimit']);
});
});
it('should show error when unknown error occurs (status other than 200, 403, 429)', function() {
stubSaveRequest({requestUrl: this.originSaveUrl,
saveRequestStatus: 'Error',
originUrl: origin.url,
saveTaskStatus: 'not created',
responseStatus: 406});
makeOriginSaveRequest(origin.type, origin.url);
cy.wait('@saveRequest').then(() => {
checkAlertVisible('danger', saveCodeMsg['unknownError']);
});
});
it('should display origin save info in the requests table', function() {
cy.route('GET', '/save/requests/list/**', '@originSaveJSON');
cy.get('#swh-origin-save-requests-list-tab').click();
cy.get('tbody tr').then(rows => {
let i = 0;
for (let row of rows) {
const cells = row.cells;
const requestDateStr = new Date(this.originSaveJSON.data[i].save_request_date).toLocaleString();
const saveStatus = this.originSaveJSON.data[i].save_task_status;
assert.equal($(cells[0]).text(), requestDateStr);
assert.equal($(cells[1]).text(), this.originSaveJSON.data[i].visit_type);
let html = '';
if (saveStatus === 'succeed') {
- let browseOriginUrl = `${this.Urls.browse_origin()}?origin_url=${this.originSaveJSON.data[i].origin_url}`;
+ let browseOriginUrl = `${this.Urls.browse_origin()}?origin_url=${encodeURIComponent(this.originSaveJSON.data[i].origin_url)}`;
browseOriginUrl += `×tamp=${encodeURIComponent(this.originSaveJSON.data[i].visit_date)}`;
html += `${this.originSaveJSON.data[i].origin_url}`;
} else {
html += this.originSaveJSON.data[i].origin_url;
}
html += ` `;
html += '';
assert.equal($(cells[2]).html(), html);
assert.equal($(cells[3]).text(), this.originSaveJSON.data[i].save_request_status);
assert.equal($(cells[4]).text(), saveStatus);
++i;
}
});
});
it('should not add timestamp to the browse origin URL is no visit date has been found', function() {
const originUrl = 'https://git.example.org/example.git';
const saveRequestData = genOriginSaveResponse({
saveRequestStatus: 'accepted',
originUrl: originUrl,
saveTaskStatus: 'succeed',
visitDate: null
});
const saveRequestsListData = {
'recordsTotal': 1,
'draw': 2,
'recordsFiltered': 1,
'data': [saveRequestData]
};
cy.route('GET', '/save/requests/list/**', saveRequestsListData);
cy.get('#swh-origin-save-requests-list-tab').click();
cy.get('tbody tr').then(rows => {
const firstRowCells = rows[0].cells;
- const browseOriginUrl = `${this.Urls.browse_origin()}?origin_url=${originUrl}`;
+ const browseOriginUrl = `${this.Urls.browse_origin()}?origin_url=${encodeURIComponent(originUrl)}`;
const browseOriginLink = `${originUrl}`;
expect($(firstRowCells[2]).html()).to.have.string(browseOriginLink);
});
});
it('should display/close task info popover when clicking on the info button', function() {
cy.route('GET', '/save/requests/list/**', '@originSaveJSON');
cy.route('GET', '/save/task/info/**', '@saveTaskInfoJSON');
cy.get('#swh-origin-save-requests-list-tab').click();
cy.get('.swh-save-request-info')
.eq(0)
.click();
cy.get('.swh-save-request-info-popover')
.should('be.visible');
cy.get('.swh-save-request-info')
.eq(0)
.click();
cy.get('.swh-save-request-info-popover')
.should('not.be.visible');
});
it('should hide task info popover when clicking on the close button', function() {
cy.route('GET', '/save/requests/list/**', '@originSaveJSON');
cy.route('GET', '/save/task/info/**', '@saveTaskInfoJSON');
cy.get('#swh-origin-save-requests-list-tab').click();
cy.get('.swh-save-request-info')
.eq(0)
.click();
cy.get('.swh-save-request-info-popover')
.should('be.visible');
cy.get('.swh-save-request-info-close')
.click();
cy.get('.swh-save-request-info-popover')
.should('not.be.visible');
});
it('should fill save request form when clicking on "Save again" button', function() {
cy.route('GET', '/save/requests/list/**', '@originSaveJSON');
cy.get('#swh-origin-save-requests-list-tab').click();
cy.get('.swh-save-origin-again')
.eq(0)
.click();
cy.get('tbody tr').eq(0).then(row => {
const cells = row[0].cells;
cy.get('#swh-input-visit-type')
.should('have.value', $(cells[1]).text());
cy.get('#swh-input-origin-url')
.should('have.value', $(cells[2]).text().slice(0, -1));
});
});
it('should select correct visit type if possible when clicking on "Save again" button', function() {
const originUrl = 'https://gitlab.inria.fr/solverstack/maphys/maphys/';
const badVisitType = 'hg';
const goodVisitType = 'git';
cy.route('GET', '/save/requests/list/**', '@originSaveJSON');
stubSaveRequest({requestUrl: this.Urls.origin_save_request(badVisitType, originUrl),
visitType: badVisitType,
saveRequestStatus: 'accepted',
originUrl: originUrl,
saveTaskStatus: 'failed',
responseStatus: 200,
errorMessage: saveCodeMsg['accepted']});
makeOriginSaveRequest(badVisitType, originUrl);
cy.get('#swh-origin-save-requests-list-tab').click();
cy.wait('@saveRequest').then(() => {
cy.get('.swh-save-origin-again')
.eq(0)
.click();
cy.get('tbody tr').eq(0).then(row => {
const cells = row[0].cells;
cy.get('#swh-input-visit-type')
.should('have.value', goodVisitType);
cy.get('#swh-input-origin-url')
.should('have.value', $(cells[2]).text().slice(0, -1));
});
});
});
});
diff --git a/cypress/integration/origin-search.spec.js b/cypress/integration/origin-search.spec.js
index ce8cd994..05aec89d 100644
--- a/cypress/integration/origin-search.spec.js
+++ b/cypress/integration/origin-search.spec.js
@@ -1,429 +1,433 @@
/**
* Copyright (C) 2019-2020 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
*/
const nonExistentText = 'NoMatchExists';
let origin;
let url;
function doSearch(searchText) {
cy.get('#origins-url-patterns')
.type(searchText)
.get('.swh-search-icon')
.click();
}
function searchShouldRedirect(searchText, redirectUrl) {
doSearch(searchText);
cy.location('pathname')
.should('equal', redirectUrl);
}
function searchShouldShowNotFound(searchText, msg) {
doSearch(searchText);
cy.get('#swh-no-result')
.should('be.visible')
.and('contain', msg);
}
function stubOriginVisitLatestRequests() {
cy.server();
cy.route({
method: 'GET',
url: '**/visit/latest/**',
response: {
type: 'tar'
}
}).as('originVisitLatest');
}
describe('Test origin-search', function() {
before(function() {
origin = this.origin[0];
url = this.Urls.browse_search();
});
beforeEach(function() {
cy.visit(url);
});
it('should show in result when url is searched', function() {
cy.get('#origins-url-patterns')
.type(origin.url);
cy.get('.swh-search-icon')
.click();
cy.get('#origin-search-results')
.should('be.visible');
cy.contains('tr', origin.url)
.should('be.visible')
.find('.swh-visit-status')
.find('i')
.should('have.class', 'mdi-check-bold')
.and('have.attr', 'title',
'Software origin has been archived by Software Heritage');
+
+ const browseOriginUrl = `${this.Urls.browse_origin()}?origin_url=${encodeURIComponent(origin.url)}`;
+ cy.get('tr a')
+ .should('have.attr', 'href', browseOriginUrl);
});
it('should show not found message when no repo matches', function() {
searchShouldShowNotFound(nonExistentText,
'No origins matching the search criteria were found.');
});
it('should add appropriate URL parameters', function() {
// Check all three checkboxes and check if
// correct url params are added
cy.get('#swh-search-origins-with-visit')
.check({force: true})
.get('#swh-filter-empty-visits')
.check({force: true})
.get('#swh-search-origin-metadata')
.check({force: true})
.then(() => {
const searchText = origin.url;
doSearch(searchText);
cy.location('search').then(locationSearch => {
const urlParams = new URLSearchParams(locationSearch);
const query = urlParams.get('q');
const withVisit = urlParams.has('with_visit');
const withContent = urlParams.has('with_content');
const searchMetadata = urlParams.has('search_metadata');
assert.strictEqual(query, searchText);
assert.strictEqual(withVisit, true);
assert.strictEqual(withContent, true);
assert.strictEqual(searchMetadata, true);
});
});
});
it('should not send request to the resolve endpoint', function() {
cy.server();
cy.route({
method: 'GET',
url: `${this.Urls.api_1_resolve_swhid('').slice(0, -1)}**`
}).as('resolveSWHID');
cy.route({
method: 'GET',
url: `${this.Urls.api_1_origin_search(origin.url)}**`
}).as('searchOrigin');
cy.get('#origins-url-patterns')
.type(origin.url);
cy.get('.swh-search-icon')
.click();
cy.wait('@searchOrigin');
cy.xhrShouldBeCalled('resolveSWHID', 0);
cy.xhrShouldBeCalled('searchOrigin', 1);
});
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({force: true})
.get('#swh-filter-empty-visits')
.uncheck({force: true})
.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() {
stubOriginVisitLatestRequests();
// Setup search
cy.get('#swh-search-origins-with-visit')
.uncheck({force: true})
.get('#swh-filter-empty-visits')
.uncheck({force: true})
.then(() => {
const searchText = 'many.origins';
// Get first page of results
doSearch(searchText);
cy.wait('@originVisitLatest');
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.wait('@originVisitLatest');
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.wait('@originVisitLatest');
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() {
stubOriginVisitLatestRequests();
// Setup search
cy.get('#swh-search-origins-with-visit')
.uncheck({force: true})
.get('#swh-filter-empty-visits')
.uncheck({force: true})
.then(() => {
const searchText = 'many.origins';
// Get first page of results
doSearch(searchText);
cy.wait('@originVisitLatest');
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.wait('@originVisitLatest');
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.wait('@originVisitLatest');
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() {
stubOriginVisitLatestRequests();
// Setup search
cy.get('#swh-search-origins-with-visit')
.uncheck({force: true})
.get('#swh-filter-empty-visits')
.uncheck({force: true})
.then(() => {
const searchText = 'many.origins';
// Get first page of results
doSearch(searchText);
cy.wait('@originVisitLatest');
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.wait('@originVisitLatest');
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.wait('@originVisitLatest');
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.wait('@originVisitLatest');
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 SWHIDs', function() {
it('should resolve directory', function() {
const redirectUrl = this.Urls.browse_directory(origin.content[0].directory);
const swhid = `swh:1:dir:${origin.content[0].directory}`;
searchShouldRedirect(swhid, redirectUrl);
});
it('should resolve revision', function() {
const redirectUrl = this.Urls.browse_revision(origin.revisions[0]);
const swhid = `swh:1:rev:${origin.revisions[0]}`;
searchShouldRedirect(swhid, redirectUrl);
});
it('should resolve snapshot', function() {
const redirectUrl = this.Urls.browse_snapshot_directory(origin.snapshot);
const swhid = `swh:1:snp:${origin.snapshot}`;
searchShouldRedirect(swhid, redirectUrl);
});
it('should resolve content', function() {
const redirectUrl = this.Urls.browse_content(`sha1_git:${origin.content[0].sha1git}`);
const swhid = `swh:1:cnt:${origin.content[0].sha1git}`;
searchShouldRedirect(swhid, redirectUrl);
});
it('should not send request to the search endpoint', function() {
cy.server();
const swhid = `swh:1:rev:${origin.revisions[0]}`;
cy.route({
method: 'GET',
url: this.Urls.api_1_resolve_swhid(swhid)
}).as('resolveSWHID');
cy.route({
method: 'GET',
url: `${this.Urls.api_1_origin_search('').slice(0, -1)}**`
}).as('searchOrigin');
cy.get('#origins-url-patterns')
.type(swhid);
cy.get('.swh-search-icon')
.click();
cy.wait('@resolveSWHID');
cy.xhrShouldBeCalled('resolveSWHID', 1);
cy.xhrShouldBeCalled('searchOrigin', 0);
});
});
context('Test invalid SWHIDs', function() {
it('should show not found for directory', function() {
const swhid = `swh:1:dir:${this.unarchivedRepo.rootDirectory}`;
const msg = `Directory with sha1_git ${this.unarchivedRepo.rootDirectory} not found`;
searchShouldShowNotFound(swhid, msg);
});
it('should show not found for snapshot', function() {
const swhid = `swh:1:snp:${this.unarchivedRepo.snapshot}`;
const msg = `Snapshot with id ${this.unarchivedRepo.snapshot} not found!`;
searchShouldShowNotFound(swhid, msg);
});
it('should show not found for revision', function() {
const swhid = `swh:1:rev:${this.unarchivedRepo.revision}`;
const msg = `Revision with sha1_git ${this.unarchivedRepo.revision} not found.`;
searchShouldShowNotFound(swhid, msg);
});
it('should show not found for content', function() {
const swhid = `swh:1:cnt:${this.unarchivedRepo.content[0].sha1git}`;
const msg = `Content with sha1_git checksum equals to ${this.unarchivedRepo.content[0].sha1git} not found!`;
searchShouldShowNotFound(swhid, msg);
});
});
});
diff --git a/swh/web/assets/src/bundles/admin/origin-save.js b/swh/web/assets/src/bundles/admin/origin-save.js
index f3f1099f..48270678 100644
--- a/swh/web/assets/src/bundles/admin/origin-save.js
+++ b/swh/web/assets/src/bundles/admin/origin-save.js
@@ -1,363 +1,363 @@
/**
* Copyright (C) 2018-2020 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
*/
import {handleFetchError, csrfPost, htmlAlert} from 'utils/functions';
import {swhSpinnerSrc} from 'utils/constants';
let authorizedOriginTable;
let unauthorizedOriginTable;
let pendingSaveRequestsTable;
let acceptedSaveRequestsTable;
let rejectedSaveRequestsTable;
function enableRowSelection(tableSel) {
$(`${tableSel} tbody`).on('click', 'tr', function() {
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
$(tableSel).closest('.tab-pane').find('.swh-action-need-selection').prop('disabled', true);
} else {
$(`${tableSel} tr.selected`).removeClass('selected');
$(this).addClass('selected');
$(tableSel).closest('.tab-pane').find('.swh-action-need-selection').prop('disabled', false);
}
});
}
export function initOriginSaveAdmin() {
$(document).ready(() => {
$.fn.dataTable.ext.errMode = 'throw';
authorizedOriginTable = $('#swh-authorized-origin-urls').DataTable({
serverSide: true,
ajax: Urls.admin_origin_save_authorized_urls_list(),
columns: [{data: 'url', name: 'url'}],
scrollY: '50vh',
scrollCollapse: true,
info: false
});
enableRowSelection('#swh-authorized-origin-urls');
swh.webapp.addJumpToPagePopoverToDataTable(authorizedOriginTable);
unauthorizedOriginTable = $('#swh-unauthorized-origin-urls').DataTable({
serverSide: true,
ajax: Urls.admin_origin_save_unauthorized_urls_list(),
columns: [{data: 'url', name: 'url'}],
scrollY: '50vh',
scrollCollapse: true,
info: false
});
enableRowSelection('#swh-unauthorized-origin-urls');
swh.webapp.addJumpToPagePopoverToDataTable(unauthorizedOriginTable);
let columnsData = [
{
data: 'id',
name: 'id',
visible: false,
searchable: false
},
{
data: 'save_request_date',
name: 'request_date',
render: (data, type, row) => {
if (type === 'display') {
let date = new Date(data);
return date.toLocaleString();
}
return data;
}
},
{
data: 'visit_type',
name: 'visit_type'
},
{
data: 'origin_url',
name: 'origin_url',
render: (data, type, row) => {
if (type === 'display') {
let html = '';
const sanitizedURL = $.fn.dataTable.render.text().display(data);
if (row.save_task_status === 'succeed') {
- let browseOriginUrl = `${Urls.browse_origin()}?origin_url=${sanitizedURL}`;
+ let browseOriginUrl = `${Urls.browse_origin()}?origin_url=${encodeURIComponent(sanitizedURL)}`;
if (row.visit_date) {
browseOriginUrl += `×tamp=${encodeURIComponent(row.visit_date)}`;
}
html += `${sanitizedURL}`;
} else {
html += sanitizedURL;
}
html += ` `;
return html;
}
return data;
}
}
];
pendingSaveRequestsTable = $('#swh-origin-save-pending-requests').DataTable({
serverSide: true,
processing: true,
language: {
processing: ``
},
ajax: Urls.origin_save_requests_list('pending'),
searchDelay: 1000,
columns: columnsData,
scrollY: '50vh',
scrollCollapse: true,
order: [[0, 'desc']],
responsive: {
details: {
type: 'none'
}
}
});
enableRowSelection('#swh-origin-save-pending-requests');
swh.webapp.addJumpToPagePopoverToDataTable(pendingSaveRequestsTable);
rejectedSaveRequestsTable = $('#swh-origin-save-rejected-requests').DataTable({
serverSide: true,
processing: true,
language: {
processing: `
`
},
ajax: Urls.origin_save_requests_list('rejected'),
searchDelay: 1000,
columns: columnsData,
scrollY: '50vh',
scrollCollapse: true,
order: [[0, 'desc']],
responsive: {
details: {
type: 'none'
}
}
});
enableRowSelection('#swh-origin-save-rejected-requests');
swh.webapp.addJumpToPagePopoverToDataTable(rejectedSaveRequestsTable);
columnsData.push({
data: 'save_task_status',
name: 'save_task_status'
});
columnsData.push({
name: 'info',
render: (data, type, row) => {
if (row.save_task_status === 'succeed' || row.save_task_status === 'failed') {
return '`;
} else {
return '';
}
}
});
acceptedSaveRequestsTable = $('#swh-origin-save-accepted-requests').DataTable({
serverSide: true,
processing: true,
language: {
processing: `
`
},
ajax: Urls.origin_save_requests_list('accepted'),
searchDelay: 1000,
columns: columnsData,
scrollY: '50vh',
scrollCollapse: true,
order: [[0, 'desc']],
responsive: {
details: {
type: 'none'
}
}
});
enableRowSelection('#swh-origin-save-accepted-requests');
swh.webapp.addJumpToPagePopoverToDataTable(acceptedSaveRequestsTable);
$('#swh-origin-save-requests-nav-item').on('shown.bs.tab', () => {
pendingSaveRequestsTable.draw();
});
$('#swh-origin-save-url-filters-nav-item').on('shown.bs.tab', () => {
authorizedOriginTable.draw();
});
$('#swh-authorized-origins-tab').on('shown.bs.tab', () => {
authorizedOriginTable.draw();
});
$('#swh-unauthorized-origins-tab').on('shown.bs.tab', () => {
unauthorizedOriginTable.draw();
});
$('#swh-save-requests-pending-tab').on('shown.bs.tab', () => {
pendingSaveRequestsTable.draw();
});
$('#swh-save-requests-accepted-tab').on('shown.bs.tab', () => {
acceptedSaveRequestsTable.draw();
});
$('#swh-save-requests-rejected-tab').on('shown.bs.tab', () => {
rejectedSaveRequestsTable.draw();
});
$('#swh-save-requests-pending-tab').click(() => {
pendingSaveRequestsTable.ajax.reload(null, false);
});
$('#swh-save-requests-accepted-tab').click(() => {
acceptedSaveRequestsTable.ajax.reload(null, false);
});
$('#swh-save-requests-rejected-tab').click(() => {
rejectedSaveRequestsTable.ajax.reload(null, false);
});
$('body').on('click', e => {
if ($(e.target).parents('.popover').length > 0) {
event.stopPropagation();
} else if ($(e.target).parents('.swh-save-request-info').length === 0) {
$('.swh-save-request-info').popover('dispose');
}
});
});
}
export function addAuthorizedOriginUrl() {
let originUrl = $('#swh-authorized-url-prefix').val();
let addOriginUrl = Urls.admin_origin_save_add_authorized_url(originUrl);
csrfPost(addOriginUrl)
.then(handleFetchError)
.then(() => {
authorizedOriginTable.row.add({'url': originUrl}).draw();
$('.swh-add-authorized-origin-status').html(
htmlAlert('success', 'The origin url prefix has been successfully added in the authorized list.', true)
);
})
.catch(response => {
$('.swh-add-authorized-origin-status').html(
htmlAlert('warning', 'The provided origin url prefix is already registered in the authorized list.', true)
);
});
}
export function removeAuthorizedOriginUrl() {
let originUrl = $('#swh-authorized-origin-urls tr.selected').text();
if (originUrl) {
let removeOriginUrl = Urls.admin_origin_save_remove_authorized_url(originUrl);
csrfPost(removeOriginUrl)
.then(handleFetchError)
.then(() => {
authorizedOriginTable.row('.selected').remove().draw();
})
.catch(() => {});
}
}
export function addUnauthorizedOriginUrl() {
let originUrl = $('#swh-unauthorized-url-prefix').val();
let addOriginUrl = Urls.admin_origin_save_add_unauthorized_url(originUrl);
csrfPost(addOriginUrl)
.then(handleFetchError)
.then(() => {
unauthorizedOriginTable.row.add({'url': originUrl}).draw();
$('.swh-add-unauthorized-origin-status').html(
htmlAlert('success', 'The origin url prefix has been successfully added in the unauthorized list.', true)
);
})
.catch(() => {
$('.swh-add-unauthorized-origin-status').html(
htmlAlert('warning', 'The provided origin url prefix is already registered in the unauthorized list.', true)
);
});
}
export function removeUnauthorizedOriginUrl() {
let originUrl = $('#swh-unauthorized-origin-urls tr.selected').text();
if (originUrl) {
let removeOriginUrl = Urls.admin_origin_save_remove_unauthorized_url(originUrl);
csrfPost(removeOriginUrl)
.then(handleFetchError)
.then(() => {
unauthorizedOriginTable.row('.selected').remove().draw();
})
.catch(() => {});
}
}
export function acceptOriginSaveRequest() {
let selectedRow = pendingSaveRequestsTable.row('.selected');
if (selectedRow.length) {
let acceptOriginSaveRequestCallback = () => {
let rowData = selectedRow.data();
let acceptSaveRequestUrl = Urls.admin_origin_save_request_accept(rowData['visit_type'], rowData['origin_url']);
csrfPost(acceptSaveRequestUrl)
.then(() => {
pendingSaveRequestsTable.ajax.reload(null, false);
});
};
swh.webapp.showModalConfirm(
'Accept origin save request ?',
'Are you sure to accept this origin save request ?',
acceptOriginSaveRequestCallback);
}
}
export function rejectOriginSaveRequest() {
let selectedRow = pendingSaveRequestsTable.row('.selected');
if (selectedRow.length) {
let rejectOriginSaveRequestCallback = () => {
let rowData = selectedRow.data();
let rejectSaveRequestUrl = Urls.admin_origin_save_request_reject(rowData['visit_type'], rowData['origin_url']);
csrfPost(rejectSaveRequestUrl)
.then(() => {
pendingSaveRequestsTable.ajax.reload(null, false);
});
};
swh.webapp.showModalConfirm(
'Reject origin save request ?',
'Are you sure to reject this origin save request ?',
rejectOriginSaveRequestCallback);
}
}
function removeOriginSaveRequest(requestTable) {
let selectedRow = requestTable.row('.selected');
if (selectedRow.length) {
let requestId = selectedRow.data()['id'];
let removeOriginSaveRequestCallback = () => {
let removeSaveRequestUrl = Urls.admin_origin_save_request_remove(requestId);
csrfPost(removeSaveRequestUrl)
.then(() => {
requestTable.ajax.reload(null, false);
});
};
swh.webapp.showModalConfirm(
'Remove origin save request ?',
'Are you sure to remove this origin save request ?',
removeOriginSaveRequestCallback);
}
}
export function removePendingOriginSaveRequest() {
removeOriginSaveRequest(pendingSaveRequestsTable);
}
export function removeAcceptedOriginSaveRequest() {
removeOriginSaveRequest(acceptedSaveRequestsTable);
}
export function removeRejectedOriginSaveRequest() {
removeOriginSaveRequest(rejectedSaveRequestsTable);
}
diff --git a/swh/web/assets/src/bundles/browse/origin-search.js b/swh/web/assets/src/bundles/browse/origin-search.js
index 7ee6c401..029e4603 100644
--- a/swh/web/assets/src/bundles/browse/origin-search.js
+++ b/swh/web/assets/src/bundles/browse/origin-search.js
@@ -1,242 +1,242 @@
/**
* Copyright (C) 2018-2020 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
*/
import {handleFetchError} from 'utils/functions';
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');
});
}
function clearOriginSearchResultsTable() {
$('#origin-search-results tbody tr').remove();
}
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, origin] of origins.entries()) {
- let browseUrl = `${Urls.browse_origin()}?origin_url=${origin.url}`;
+ let browseUrl = `${Urls.browse_origin()}?origin_url=${encodeURIComponent(origin.url)}`;
let tableRow =
`
Fetching task information ...
${info.key} |
${info.value} |
---|