Page MenuHomeSoftware Heritage
Paste P679

fail to have a search with inclusion and exclusion pattern
ActivePublic

Authored by ardumont on May 22 2020, 7:09 PM.
/**
* Copyright (C) 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 username = 'admin';
const password = 'admin';
function login(username, password) {
cy.get('input[name="username"]')
.type(username)
.get('input[name="password"]')
.type(password)
.get('form')
.submit();
}
// data to use as request query response
let responseDeposits;
describe('Test admin deposit page', function() {
beforeEach(() => {
responseDeposits = [
{
'id': 614,
'external_id': 'ch-de-1',
'reception_date': '2020-05-18T13:48:27Z',
'status': 'done',
'status_detail': null,
'swh_id': 'swh:1:dir:ef04a768',
'swh_id_context': 'swh:1:dir:ef04a768;origin=https://w.s.o/c-d-1;visit=swh:1:snp:b234be1e;anchor=swh:1:rev:d24a75c9;path=/'
},
{
'id': 613,
'external_id': 'ch-de-2',
'reception_date': '2020-05-18T11:20:16Z',
'status': 'done',
'status_detail': null,
'swh_id': 'swh:1:dir:181417fb',
'swh_id_context': 'swh:1:dir:181417fb;origin=https://w.s.o/c-d-2;visit=swh:1:snp:8c32a2ef;anchor=swh:1:rev:3d1eba04;path=/'
},
{
'id': 612,
'external_id': 'ch-de-3',
'reception_date': '2020-05-18T11:20:16Z',
'status': 'rejected',
'status_detail': 'incomplete deposit!',
'swh_id': null,
'swh_id_context': null
}
];
});
it('Should filter out deposits matching excluding pattern from display', function() {
cy.visit(this.Urls.admin_deposit());
// FIXME: cypress anti-pattern, do not use ui to log ¯\_(ツ)_/¯
// https://docs.cypress.io/guides/getting-started/testing-your-app.html#Logging-in
login(username, password);
cy.server();
// entry supposed to be excluded from the display by default
let extraDeposit = {
'id': 10,
'external_id': 'check-deposit-3',
'reception_date': '2020-05-18T11:20:16Z',
'status': 'done',
'status_detail': null,
'swh_id': 'swh:1:dir:fb234417',
'swh_id_context': 'swh:1:dir:fb234417;origin=https://w.s.o/c-d-3;visit=swh:1:snp:181417fb;anchor=swh:1:rev:3d166604;path=/'
};
// of course, that's how to copy a list (an "array")
let testDeposits = responseDeposits.slice();
// and add a new element to that array by mutating it...
testDeposits.push(extraDeposit);
// ensure we don't touch the original reference
expect(responseDeposits.length).to.be.equal(3);
expect(testDeposits.length).to.be.equal(4);
cy.route({
method: 'GET',
url: `${this.Urls.admin_deposit_list()}**`,
response: {
'draw': 10,
'recordsTotal': testDeposits.length,
'recordsFiltered': testDeposits.length,
'data': testDeposits
}
}).as('listDeposits');
cy.get('#swh-admin-deposit-list-exclude-filter')
.clear()
.type("check");
cy.location('pathname')
.should('be.equal', this.Urls.admin_deposit());
cy.url().should('include', '/admin/deposit');
cy.get('#swh-admin-deposit-list')
.should('exist');
// those are computed from the
let expectedOrigins = [
'https://w.s.o/c-d-1', 'https://w.s.o/c-d-2', '', 'https://w.s.o/c-d-3',
];
cy.wait('@listDeposits').then((xhr) => {
let deposits = xhr.response.body.data;
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) => {
let deposit = deposits[idx];
let responseDeposit = testDeposits[idx];
cy.log('deposit', deposit);
cy.log('responseDeposit', responseDeposit);
expect(deposit.id).to.be.equal(responseDeposit['id']);
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.swh_id).to.be.equal(responseDeposit['swh_id']);
expect(deposit.swh_id_context).to.be.equal(responseDeposit['swh_id_context']);
let expectedOrigin = expectedOrigins[idx];
// part of the data, but it should not be displayed (got filtered out)
if (deposit.external_id === 'check-deposit-3') {
cy.contains(deposit.status).should('not.be.visible');
cy.contains(deposit.status_detail).should('not.be.visible');
cy.contains(deposit.external_id).should('not.be.visible');
cy.contains(expectedOrigin).should('not.be.visible');
cy.contains(deposit.swh_id).should('not.be.visible');
cy.contains(deposit.swh_id_context).should('not.be.visible');
} else {
expect(deposit.external_id).to.be.not.equal('check-deposit-3');
cy.contains(deposit.id).should('be.visible');
if (deposit.status !== 'rejected') {
cy.contains(deposit.external_id).should('not.be.visible');
cy.contains(expectedOrigin).should('be.visible');
// ensure it's in the dom
}
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.be.visible');
}
// those are hidden by default
if (deposit.swh_id !== null) {
cy.contains(deposit.swh_id).should('not.be.visible');
cy.contains(deposit.swh_id_context).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').should('have.length', 3);
// cy.get('@rows').each((row, idx, collection) => {
// let deposit = deposits[idx];
// let expectedOrigin = expectedOrigins[idx];
// if (deposit.external_id === 'check-deposit-3') { // filtered out deposit
// cy.contains(deposit.status).should('not.be.visible');
// cy.contains(deposit.status_detail).should('not.be.visible');
// cy.contains(deposit.external_id).should('not.be.visible');
// cy.contains(expectedOrigin).should('not.be.visible');
// cy.contains(deposit.swh_id).should('not.be.visible');
// cy.contains(deposit.swh_id_context).should('not.be.visible');
// } else {
// expect(deposit.external_id).to.be.not.equal('check-deposit-3');
// // ensure it's in the dom
// cy.contains(deposit.id).should('not.be.visible');
// if (deposit.status !== 'rejected') {
// cy.contains(deposit.external_id).should('not.be.visible');
// 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.swh_id !== null) {
// cy.contains(deposit.swh_id).should('be.visible');
// cy.contains(deposit.swh_id_context).should('be.visible');
// }
// }
// });
// });
// cy.get('#swh-admin-deposit-list-error')
// .should('not.contain',
// 'An error occurred while retrieving the list of deposits');
});
});
// it('Should display properly entries', function() {
// cy.visit(this.Urls.admin_deposit());
// // FIXME: cypress anti-pattern, do not use ui to log ¯\_(ツ)_/¯
// // https://docs.cypress.io/guides/getting-started/testing-your-app.html#Logging-in
// login(username, password);
// let testDeposits = responseDeposits;
// cy.server();
// cy.route({
// method: 'GET',
// url: `${this.Urls.admin_deposit_list()}**`,
// response: {
// 'draw': 10,
// 'recordsTotal': testDeposits.length,
// 'recordsFiltered': testDeposits.length,
// 'data': testDeposits
// }
// }).as('listDeposits');
// cy.location('pathname')
// .should('be.equal', this.Urls.admin_deposit());
// cy.url().should('include', '/admin/deposit');
// cy.get('#swh-admin-deposit-list')
// .should('exist');
// // those are computed from the
// let expectedOrigins = [
// 'https://w.s.o/c-d-1', 'https://w.s.o/c-d-2', ''
// ];
// cy.wait('@listDeposits').then((xhr) => {
// cy.log('response:', xhr.response);
// cy.log(xhr.response.body);
// let 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) => {
// let deposit = deposits[idx];
// let responseDeposit = testDeposits[idx];
// assert.isNotNull(deposit);
// assert.isNotNull(responseDeposit);
// expect(deposit.id).to.be.equal(responseDeposit['id']);
// 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.swh_id).to.be.equal(responseDeposit['swh_id']);
// expect(deposit.swh_id_context).to.be.equal(responseDeposit['swh_id_context']);
// let expectedOrigin = expectedOrigins[idx];
// // 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');
// }
// 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.be.visible');
// }
// // those are hidden by default
// if (deposit.swh_id !== null) {
// cy.contains(deposit.swh_id).should('not.be.visible');
// cy.contains(deposit.swh_id_context).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) => {
// let deposit = deposits[idx];
// let expectedOrigin = expectedOrigins[idx];
// // ensure it's in the dom
// cy.contains(deposit.id).should('not.be.visible');
// 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.swh_id !== null) {
// cy.contains(deposit.swh_id).should('be.visible');
// cy.contains(deposit.swh_id_context).should('be.visible');
// }
// });
// });
// cy.get('#swh-admin-deposit-list-error')
// .should('not.contain',
// 'An error occurred while retrieving the list of deposits');
// });
// });
});

Event Timeline

deposit.js:

/**
 * 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
 */

function genSwhLink(data, type) {
  if (type === 'display') {
    if (data && data.startsWith('swh')) {
      let browseUrl = Urls.browse_swh_id(data);
      return `<a href="${browseUrl}">${data}</a>`;
    }
  }
  return data;
}

function swhPatternMatch(string, pattern) {
  // Check if a string match a given pattern
  return (typeof string === 'string' || string instanceof String) &&
    string.search(pattern) !== -1;
}

function swhUpdateDataWithoutExcludedPattern(settings, data, dataIndex) {
  // expect data to be a dict
  console.log('swh update - settings', settings);
  console.log('swh update - data index', dataIndex);
  console.log('swh update - data', data);
  let excludePattern = $('#swh-admin-deposit-list-exclude-filter').val();
  console.log('swh update - exclude pattern', excludePattern);
  if (swhPatternMatch(data["external_id"], excludePattern)) {
    return false;
  }
  return true;
  // for (const key in data) {
  //   console.log('swh update - key: ', key);
  //   let value = data[key];
  //   console.log('swh update - value: ', value);
  //   if (swhPatternMatch(value, excludePattern)) {
  //     return false; // exclude the data from filtering
  //   }
  // }
  // return true;
}

export function initDepositAdmin() {
  let depositsTable;
  $(document).ready(() => {
    $.fn.dataTable.ext.errMode = 'none'; // none (prod), alert (default), throw
    // including the custom exclude filtering
    $.fn.dataTable.ext.search.push(swhUpdateDataWithoutExcludedPattern);
    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: '<<f<"#list-exclude">l>rt<"bottom"ip>>',
        // list-exclude is a custom filter
        // see https://datatables.net/examples/advanced_init/dom_toolbar.html
        ajax: Urls.admin_deposit_list(),
        // rowCallback: function(row, data) {
        //   const api = this.api();
        //   let excludePattern = $('#swh-admin-deposit-list-exclude-filter').val();
        //   // console.log('row-callback - ', row);
        //   // console.log('row-callback - ', data);
        //   for (const key in data) {
        //     // console.log('row callback - key: ', key);
        //     let value = data[key];
        //     // console.log('row callback - value: ', value);
        //     if (swhPatternMatch(value, excludePattern)) {
        //       console.log('row callback, should be removed', key, value);
        //       api.rows(row).remove().draw();
        //       break;
        //     }
        //   }
        // },
        columns: [
          {
            data: 'id',
            name: 'id'
          },
          {
            data: 'swh_id_context',
            name: 'swh_id_context',
            render: (data, type, row) => {
              if (data && type === 'display') {
                let originPattern = ';origin=';
                let originPatternIdx = data.indexOf(originPattern);
                if (originPatternIdx !== -1) {
                  let originUrl = data.slice(originPatternIdx + originPattern.length);
                  let nextSepPattern = ';';
                  let nextSepPatternIdx = originUrl.indexOf(nextSepPattern);
                  if (nextSepPatternIdx !== -1) { /* Remove extra context */
                    originUrl = originUrl.slice(0, nextSepPatternIdx);
                  }
                  return `<a href="${originUrl}">${originUrl}</a>`;
                }
              }
              return data;
            }
          },
          {
            data: 'reception_date',
            name: 'reception_date',
            render: (data, type, row) => {
              if (type === 'display') {
                let date = new Date(data);
                return date.toLocaleString();
              }
              return data;
            }
          },
          {
            data: 'status',
            name: 'status'
          },
          {
            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 `<div style="width: 200px; white-space: pre; overflow-x: auto;">${text}</div>`;
              }
              return data;
            },
            orderable: false,
            visible: false
          },
          {
            data: 'swh_id',
            name: 'swh_id',
            render: (data, type, row) => {
              return genSwhLink(data, type);
            },
            orderable: false,
            visible: false
          },
          {
            data: 'swh_id_context',
            name: 'swh_id_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(`<div id="swh-admin-deposit-list-exclude-wrapper">
    <div id="swh-admin-deposit-list-exclude-div-wrapper" class="dataTables_filter">
      <label>
        Exclude:<input id="swh-admin-deposit-list-exclude-filter"
                       type="search"
                       value="check-deposit"
                       class="form-control form-control-sm"
                       placeholder="exclude-pattern" aria-controls="swh-admin-deposit-list">
          </input>
      </label>
    </div>
  </div>
`);

    // Adding exclusion pattern update behavior
    $('#swh-admin-deposit-list-exclude-filter').keyup(function() {
      depositsTable.draw();
    });

    // search with current default search so that it uses the excluding pattern
    // and then draw
    depositsTable.search(".*", true);
    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');
    }
  });

}