diff --git a/assets/src/bundles/admin/deposit.js b/assets/src/bundles/admin/deposit.js index c3839ae9..ef7f773c 100644 --- a/assets/src/bundles/admin/deposit.js +++ b/assets/src/bundles/admin/deposit.js @@ -1,184 +1,206 @@ /** * Copyright (C) 2018-2022 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 {getHumanReadableDate} from 'utils/functions'; -function genSwhLink(data, type) { +function genSwhLink(data, type, linkText = '') { if (type === 'display' && data && data.startsWith('swh')) { const browseUrl = Urls.browse_swhid(data); const formattedSWHID = data.replace(/;/g, ';
'); - return `${formattedSWHID}`; + if (!linkText) { + linkText = formattedSWHID; + } + return `${linkText}`; } return data; } -function genLink(data, type) { +function genLink(data, type, openInNewTab = false, linkText = '') { if (type === 'display' && data) { const sData = encodeURI(data); - return `${sData}`; + if (!linkText) { + linkText = sData; + } + let attrs = ''; + if (openInNewTab) { + attrs = 'target="_blank" rel="noopener noreferrer"'; + } + return `${linkText}`; } return data; } export function initDepositAdmin(username, isStaff) { let depositsTable; $(document).ready(() => { $.fn.dataTable.ext.errMode = 'none'; depositsTable = $('#swh-admin-deposit-list') .on('error.dt', (e, settings, techNote, message) => { $('#swh-admin-deposit-list-error').text(message); }) .DataTable({ serverSide: true, processing: true, // let's define the order of table options display // f: (f)ilter // l: (l)ength changing // r: p(r)ocessing // t: (t)able // i: (i)nfo // p: (p)agination // see https://datatables.net/examples/basic_init/dom.html dom: '<<"d-flex justify-content-between align-items-center"f' + '<"#list-exclude">l>rt<"bottom"ip>>', // div#list-exclude is a custom filter added next to dataTable // initialization below through js dom manipulation, see // https://datatables.net/examples/advanced_init/dom_toolbar.html ajax: { url: Urls.admin_deposit_list(), data: d => { d.excludePattern = $('#swh-admin-deposit-list-exclude-filter').val(); if (!isStaff) { d.username = username; } } }, columns: [ { data: 'id', name: 'id' }, { data: 'type', name: 'type' }, { data: 'uri', name: 'uri', render: (data, type, row) => { - return genLink(data, type); + const sanitizedURL = $.fn.dataTable.render.text().display(data); + let swhLink = ''; + let originLink = ''; + if (row.swhid_context && data) { + swhLink = genSwhLink(row.swhid_context, type, sanitizedURL); + } else if (data) { + swhLink = sanitizedURL; + } + if (data) { + originLink = genLink(sanitizedURL, type, true, + ''); + } + return swhLink + ' ' + originLink; } }, { data: 'reception_date', name: 'reception_date', render: getHumanReadableDate }, { data: 'status', name: 'status' }, { data: 'raw_metadata', name: 'raw_metadata', render: (data, type, row) => { if (type === 'display') { if (row.raw_metadata) { - return ``; + return ``; } } return data; } }, { data: 'status_detail', name: 'status_detail', render: (data, type, row) => { if (type === 'display' && data) { let text = data; if (typeof data === 'object') { text = JSON.stringify(data, null, 4); } return `
${text}
`; } return data; }, orderable: false, visible: false }, { data: 'swhid', name: 'swhid', render: (data, type, row) => { return genSwhLink(data, type); }, orderable: false, visible: false }, { data: 'swhid_context', name: 'swhid_context', render: (data, type, row) => { return genSwhLink(data, type); }, orderable: false, visible: false } ], scrollX: true, scrollY: '50vh', scrollCollapse: true, order: [[0, 'desc']] }); // Some more customization is needed on the table $('div#list-exclude').html(`
`); // Show a modal when the "metadata" button is clicked $('#swh-admin-deposit-list tbody').on('click', 'tr button.metadata', function() { var row = depositsTable.row(this.parentNode.parentNode).data(); var metadata = row.raw_metadata; var escapedMetadata = $('
').text(metadata).html(); swh.webapp.showModalHtml(`Metadata of deposit ${row.id}`, `
${escapedMetadata}
`, '90%'); swh.webapp.highlightCode(); }); // Adding exclusion pattern update behavior, when typing, update search $('#swh-admin-deposit-list-exclude-filter').keyup(function() { depositsTable.draw(); }); // at last draw the table depositsTable.draw(); }); $('a.toggle-col').on('click', function(e) { e.preventDefault(); var column = depositsTable.column($(this).attr('data-column')); column.visible(!column.visible()); if (column.visible()) { $(this).removeClass('col-hidden'); } else { $(this).addClass('col-hidden'); } }); } diff --git a/cypress/integration/deposit-admin.spec.js b/cypress/integration/deposit-admin.spec.js index f3e18a49..a79daa4e 100644 --- a/cypress/integration/deposit-admin.spec.js +++ b/cypress/integration/deposit-admin.spec.js @@ -1,226 +1,239 @@ /** * Copyright (C) 2020-2022 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 */ // data to use as request query response let responseDeposits; let expectedOrigins; let depositModerationUrl; let depositListUrl; +const $ = Cypress.$; + describe('Test moderation deposit Login/logout', function() { before(function() { depositModerationUrl = this.Urls.admin_deposit(); }); it('should not display deposit moderation link in sidebar when anonymous', function() { cy.visit(depositModerationUrl); cy.get(`.sidebar a[href="${depositModerationUrl}"]`) .should('not.exist'); }); it('should not display deposit moderation link when connected as unprivileged user', function() { cy.userLogin(); cy.visit(depositModerationUrl); cy.get(`.sidebar a[href="${depositModerationUrl}"]`) .should('not.exist'); }); it('should display deposit moderation link in sidebar when connected as privileged user', function() { cy.depositLogin(); cy.visit(depositModerationUrl); cy.get(`.sidebar a[href="${depositModerationUrl}"]`) .should('exist'); }); it('should display deposit moderation link in sidebar when connected as staff member', function() { cy.adminLogin(); cy.visit(depositModerationUrl); cy.get(`.sidebar a[href="${depositModerationUrl}"]`) .should('exist'); }); }); describe('Test admin deposit page', function() { before(function() { depositModerationUrl = this.Urls.admin_deposit(); depositListUrl = this.Urls.admin_deposit_list(); }); beforeEach(() => { responseDeposits = [ { 'id': 614, 'type': 'code', 'external_id': 'ch-de-1', 'reception_date': '2020-05-18T13:48:27Z', 'status': 'done', 'status_detail': null, 'swhid': 'swh:1:dir:ef04a768', 'swhid_context': 'swh:1:dir:ef04a768;origin=https://w.s.o/c-d-1;visit=swh:1:snp:b234be1e;anchor=swh:1:rev:d24a75c9;path=/', 'raw_metadata': 'bar', 'uri': 'https://w.s.o/c-d-1' }, { 'id': 613, 'type': 'code', 'external_id': 'ch-de-2', 'reception_date': '2020-05-18T11:20:16Z', 'status': 'done', 'status_detail': null, 'swhid': 'swh:1:dir:181417fb', 'swhid_context': 'swh:1:dir:181417fb;origin=https://w.s.o/c-d-2;visit=swh:1:snp:8c32a2ef;anchor=swh:1:rev:3d1eba04;path=/', 'raw_metadata': null, 'uri': 'https://w.s.o/c-d-2' }, { 'id': 612, 'type': 'code', 'external_id': 'ch-de-3', 'reception_date': '2020-05-18T11:20:16Z', 'status': 'rejected', 'status_detail': 'incomplete deposit!', 'swhid': null, 'swhid_context': null, 'raw_metadata': null, 'uri': null } ]; // those are computed from the expectedOrigins = { 614: 'https://w.s.o/c-d-1', 613: 'https://w.s.o/c-d-2', 612: '' }; }); it('Should properly display entries', function() { cy.adminLogin(); const testDeposits = responseDeposits; cy.intercept(`${depositListUrl}**`, { body: { 'draw': 10, 'recordsTotal': testDeposits.length, 'recordsFiltered': testDeposits.length, 'data': testDeposits } }).as('listDeposits'); cy.visit(depositModerationUrl); cy.location('pathname') .should('be.equal', depositModerationUrl); cy.get('#swh-admin-deposit-list') .should('exist'); cy.wait('@listDeposits').then((xhr) => { cy.log('response:', xhr.response); cy.log(xhr.response.body); const deposits = xhr.response.body.data; cy.log('Deposits: ', deposits); expect(deposits.length).to.equal(testDeposits.length); cy.get('#swh-admin-deposit-list').find('tbody > tr').as('rows'); // only 2 entries cy.get('@rows').each((row, idx, collection) => { + const cells = row[0].cells; const deposit = deposits[idx]; const responseDeposit = testDeposits[idx]; assert.isNotNull(deposit); assert.isNotNull(responseDeposit); expect(deposit.id).to.be.equal(responseDeposit['id']); expect(deposit.uri).to.be.equal(responseDeposit['uri']); expect(deposit.type).to.be.equal(responseDeposit['type']); expect(deposit.external_id).to.be.equal(responseDeposit['external_id']); expect(deposit.status).to.be.equal(responseDeposit['status']); expect(deposit.status_detail).to.be.equal(responseDeposit['status_detail']); expect(deposit.swhid).to.be.equal(responseDeposit['swhid']); expect(deposit.swhid_context).to.be.equal(responseDeposit['swhid_context']); const expectedOrigin = expectedOrigins[deposit.id]; // ensure it's in the dom cy.contains(deposit.id).should('be.visible'); if (deposit.status !== 'rejected') { expect(row).to.not.contain(deposit.external_id); cy.contains(expectedOrigin).should('be.visible'); } + if (deposit.uri && deposit.swhid_context) { + let html = `${deposit.uri}`; + html += ` `; + html += ''; + expect($(cells[2]).html()).to.contain(html); + } else if (!deposit.uri) { + expect($(cells[2]).text().trim()).to.equal(''); + } + cy.contains(deposit.status).should('be.visible'); // those are hidden by default, so now visible if (deposit.status_detail !== null) { cy.contains(deposit.status_detail).should('not.exist'); } // those are hidden by default if (deposit.swhid !== null) { cy.contains(deposit.swhid).should('not.exist'); cy.contains(deposit.swhid_context).should('not.exist'); } if (deposit.raw_metadata !== null) { cy.get('button.metadata', {withinSubject: row}) .should('exist') + .should('have.text', 'display') .click({force: true}); cy.get('#swh-web-modal-html code.xml').should('be.visible'); // Dismiss the modal cy.get('body').wait(500).type('{esc}'); cy.get('#swh-web-modal-html code.xml').should('not.be.visible'); } else { cy.get('button.metadata', {withinSubject: row}).should('not.exist'); cy.get('#swh-web-modal-html code.xml').should('not.be.visible'); } }); // toggling all links and ensure, the previous checks are inverted cy.get('a.toggle-col').click({'multiple': true}).then(() => { cy.get('#swh-admin-deposit-list').find('tbody > tr').as('rows'); cy.get('@rows').each((row, idx, collection) => { const deposit = deposits[idx]; const expectedOrigin = expectedOrigins[deposit.id]; // ensure it's in the dom expect(row).to.not.contain(deposit.id); if (deposit.status !== 'rejected') { expect(row).to.not.contain(deposit.external_id); expect(row).to.contain(expectedOrigin); } expect(row).to.not.contain(deposit.status); // those are hidden by default, so now visible if (deposit.status_detail !== null) { cy.contains(deposit.status_detail).should('be.visible'); } // those are hidden by default, so now they should be visible if (deposit.swhid !== null) { cy.contains(deposit.swhid).should('be.visible'); cy.contains(deposit.swhid_context).should('be.visible'); // check SWHID link text formatting cy.contains(deposit.swhid_context).then(elt => { expect(elt[0].innerHTML).to.equal(deposit.swhid_context.replace(/;/g, ';
')); }); } }); }); cy.get('#swh-admin-deposit-list-error') .should('not.contain', 'An error occurred while retrieving the list of deposits'); }); }); });