diff --git a/cypress/integration/persistent-identifiers.spec.js b/cypress/integration/persistent-identifiers.spec.js index afac0fd7..5327a2c2 100644 --- a/cypress/integration/persistent-identifiers.spec.js +++ b/cypress/integration/persistent-identifiers.spec.js @@ -1,259 +1,270 @@ /** * 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 origin, originBadgeUrl, originBrowseUrl; let url, urlPrefix; let browsedObjectMetadata; let cntPid, cntPidWithOrigin, cntPidWithOriginAndLines; let dirPid, dirPidWithOrigin; let relPid, relPidWithOrigin; let revPid, revPidWithOrigin; let snpPid, snpPidWithOrigin; let testsData; const firstSelLine = 6; const lastSelLine = 12; describe('Persistent Identifiers Tests', function() { before(function() { origin = this.origin[1]; url = `${this.Urls.browse_origin_content()}?origin_url=${origin.url}&path=${origin.content[0].path}`; url = `${url}&release=${origin.release}#L${firstSelLine}-L${lastSelLine}`; originBadgeUrl = this.Urls.swh_badge('origin', origin.url); originBrowseUrl = `${this.Urls.browse_origin()}?origin_url=${origin.url}`; cy.visit(url).window().then(win => { urlPrefix = `${win.location.protocol}//${win.location.hostname}`; if (win.location.port) { urlPrefix += `:${win.location.port}`; } browsedObjectMetadata = win.swh.webapp.getBrowsedSwhObjectMetadata(); cntPid = `swh:1:cnt:${browsedObjectMetadata.sha1_git}`; cntPidWithOrigin = `${cntPid};origin=${origin.url}`; cntPidWithOriginAndLines = `${cntPidWithOrigin};lines=${firstSelLine}-${lastSelLine}`; dirPid = `swh:1:dir:${browsedObjectMetadata.directory}`; dirPidWithOrigin = `${dirPid};origin=${origin.url}`; revPid = `swh:1:rev:${browsedObjectMetadata.revision}`; revPidWithOrigin = `${revPid};origin=${origin.url}`; relPid = `swh:1:rel:${browsedObjectMetadata.release}`; relPidWithOrigin = `${relPid};origin=${origin.url}`; snpPid = `swh:1:snp:${browsedObjectMetadata.snapshot}`; snpPidWithOrigin = `${snpPid};origin=${origin.url}`; testsData = [ { 'objectType': 'content', 'objectPids': [cntPidWithOriginAndLines, cntPidWithOrigin, cntPid], 'badgeUrl': this.Urls.swh_badge('content', browsedObjectMetadata.sha1_git), 'badgePidUrl': this.Urls.swh_badge_pid(cntPidWithOriginAndLines), 'browseUrl': this.Urls.browse_swh_id(cntPidWithOriginAndLines) }, { 'objectType': 'directory', 'objectPids': [dirPidWithOrigin, dirPid], 'badgeUrl': this.Urls.swh_badge('directory', browsedObjectMetadata.directory), 'badgePidUrl': this.Urls.swh_badge_pid(dirPidWithOrigin), 'browseUrl': this.Urls.browse_swh_id(dirPidWithOrigin) }, { 'objectType': 'release', 'objectPids': [relPidWithOrigin, relPid], 'badgeUrl': this.Urls.swh_badge('release', browsedObjectMetadata.release), 'badgePidUrl': this.Urls.swh_badge_pid(relPidWithOrigin), 'browseUrl': this.Urls.browse_swh_id(relPidWithOrigin) }, { 'objectType': 'revision', 'objectPids': [revPidWithOrigin, revPid], 'badgeUrl': this.Urls.swh_badge('revision', browsedObjectMetadata.revision), 'badgePidUrl': this.Urls.swh_badge_pid(revPidWithOrigin), 'browseUrl': this.Urls.browse_swh_id(revPidWithOrigin) }, { 'objectType': 'snapshot', 'objectPids': [snpPidWithOrigin, snpPid], 'badgeUrl': this.Urls.swh_badge('snapshot', browsedObjectMetadata.snapshot), 'badgePidUrl': this.Urls.swh_badge_pid(snpPidWithOrigin), 'browseUrl': this.Urls.browse_swh_id(snpPidWithOrigin) } ]; }); }); beforeEach(function() { cy.visit(url); }); it('should open and close identifiers tab when clicking on handle', function() { cy.get('#swh-identifiers') .should('have.class', 'ui-slideouttab-ready'); cy.get('.ui-slideouttab-handle') .click(); cy.get('#swh-identifiers') .should('have.class', 'ui-slideouttab-open'); cy.get('.ui-slideouttab-handle') .click(); cy.get('#swh-identifiers') .should('not.have.class', 'ui-slideouttab-open'); }); it('should display identifiers with permalinks for browsed objects', function() { cy.get('.ui-slideouttab-handle') .click(); for (let td of testsData) { cy.get(`a[href="#swh-id-tab-${td.objectType}"]`) .click(); cy.get(`#swh-id-tab-${td.objectType}`) .should('be.visible'); cy.get(`#swh-id-tab-${td.objectType} .swh-id`) .contains(td.objectPids[0]) .should('have.attr', 'href', this.Urls.browse_swh_id(td.objectPids[0])); } }); it('should update content identifier metadata when toggling option checkboxes', function() { cy.get('.ui-slideouttab-handle') .click(); cy.get(`#swh-id-tab-content .swh-id`) .contains(cntPidWithOriginAndLines) .should('have.attr', 'href', this.Urls.browse_swh_id(cntPidWithOriginAndLines)); cy.get('#swh-id-tab-content .swh-id-option-lines') .click(); cy.get(`#swh-id-tab-content .swh-id`) .contains(cntPidWithOrigin) .should('have.attr', 'href', this.Urls.browse_swh_id(cntPidWithOrigin)); cy.get('#swh-id-tab-content .swh-id-option-origin') .click(); cy.get(`#swh-id-tab-content .swh-id`) .contains(cntPid) .should('have.attr', 'href', this.Urls.browse_swh_id(cntPid)); cy.get('#swh-id-tab-content .swh-id-option-origin') .click(); cy.get(`#swh-id-tab-content .swh-id`) .contains(cntPidWithOrigin) .should('have.attr', 'href', this.Urls.browse_swh_id(cntPidWithOrigin)); cy.get('#swh-id-tab-content .swh-id-option-lines') .click(); cy.get(`#swh-id-tab-content .swh-id`) .contains(cntPidWithOriginAndLines) .should('have.attr', 'href', this.Urls.browse_swh_id(cntPidWithOriginAndLines)); }); it('should update other object identifiers metadata when toggling option checkboxes', function() { cy.get('.ui-slideouttab-handle') .click(); for (let td of testsData) { // already tested if (td.objectType === 'content') continue; cy.get(`a[href="#swh-id-tab-${td.objectType}"]`) .click(); cy.get(`#swh-id-tab-${td.objectType} .swh-id`) .contains(td.objectPids[0]) .should('have.attr', 'href', this.Urls.browse_swh_id(td.objectPids[0])); cy.get(`#swh-id-tab-${td.objectType} .swh-id-option-origin`) .click(); cy.get(`#swh-id-tab-${td.objectType} .swh-id`) .contains(td.objectPids[1]) .should('have.attr', 'href', this.Urls.browse_swh_id(td.objectPids[1])); cy.get(`#swh-id-tab-${td.objectType} .swh-id-option-origin`) .click(); cy.get(`#swh-id-tab-${td.objectType} .swh-id`) .contains(td.objectPids[0]) .should('have.attr', 'href', this.Urls.browse_swh_id(td.objectPids[0])); } }); it('should display swh badges in identifiers tab for browsed objects', function() { cy.get('.ui-slideouttab-handle') .click(); const originBadgeUrl = this.Urls.swh_badge('origin', origin.url); for (let td of testsData) { cy.get(`a[href="#swh-id-tab-${td.objectType}"]`) .click(); cy.get(`#swh-id-tab-${td.objectType} .swh-badge-origin`) .should('have.attr', 'src', originBadgeUrl); cy.get(`#swh-id-tab-${td.objectType} .swh-badge-${td.objectType}`) .should('have.attr', 'src', td.badgeUrl); } }); it('should display badge integration info when clicking on it', function() { cy.get('.ui-slideouttab-handle') .click(); for (let td of testsData) { cy.get(`a[href="#swh-id-tab-${td.objectType}"]`) .click(); cy.get(`#swh-id-tab-${td.objectType} .swh-badge-origin`) .click() .wait(500); for (let badgeType of ['html', 'md', 'rst']) { cy.get(`.modal .swh-badge-${badgeType}`) .contains(`${urlPrefix}${originBrowseUrl}`) .contains(`${urlPrefix}${originBadgeUrl}`); } cy.get('.modal.show .close') .click() .wait(500); cy.get(`#swh-id-tab-${td.objectType} .swh-badge-${td.objectType}`) .click() .wait(500); for (let badgeType of ['html', 'md', 'rst']) { cy.get(`.modal .swh-badge-${badgeType}`) .contains(`${urlPrefix}${td.browseUrl}`) .contains(`${urlPrefix}${td.badgePidUrl}`); } cy.get('.modal.show .close') .click() .wait(500); } }); + it('should be possible to retrieve SWHIDs context from JavaScript', function() { + cy.window().then(win => { + const swhIdsContext = win.swh.webapp.getSwhIdsContext(); + for (let testData of testsData) { + assert.isTrue(swhIdsContext.hasOwnProperty(testData.objectType)); + assert.equal(swhIdsContext[testData.objectType].swh_id, + testData.objectPids.slice(-1)[0]); + } + }); + }); + }); diff --git a/swh/web/assets/src/bundles/webapp/webapp-utils.js b/swh/web/assets/src/bundles/webapp/webapp-utils.js index 3799863e..9d56f3b5 100644 --- a/swh/web/assets/src/bundles/webapp/webapp-utils.js +++ b/swh/web/assets/src/bundles/webapp/webapp-utils.js @@ -1,291 +1,312 @@ /** * Copyright (C) 2018-2019 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 objectFitImages from 'object-fit-images'; import {selectText} from 'utils/functions'; import {BREAKPOINT_MD} from 'utils/constants'; let collapseSidebar = false; let previousSidebarState = localStorage.getItem('remember.lte.pushmenu'); if (previousSidebarState !== undefined) { collapseSidebar = previousSidebarState === 'sidebar-collapse'; } $(document).on('DOMContentLoaded', () => { // set state to collapsed on smaller devices if ($(window).width() < BREAKPOINT_MD) { collapseSidebar = true; } // restore previous sidebar state (collapsed/expanded) if (collapseSidebar) { // hack to avoid animated transition for collapsing sidebar // when loading a page let sidebarTransition = $('.main-sidebar, .main-sidebar:before').css('transition'); let sidebarEltsTransition = $('.sidebar .nav-link p, .main-sidebar .brand-text, .sidebar .user-panel .info').css('transition'); $('.main-sidebar, .main-sidebar:before').css('transition', 'none'); $('.sidebar .nav-link p, .main-sidebar .brand-text, .sidebar .user-panel .info').css('transition', 'none'); $('body').addClass('sidebar-collapse'); $('.swh-words-logo-swh').css('visibility', 'visible'); // restore transitions for user navigation setTimeout(() => { $('.main-sidebar, .main-sidebar:before').css('transition', sidebarTransition); $('.sidebar .nav-link p, .main-sidebar .brand-text, .sidebar .user-panel .info').css('transition', sidebarEltsTransition); }); } }); $(document).on('collapsed.lte.pushmenu', event => { if ($('body').width() >= BREAKPOINT_MD) { $('.swh-words-logo-swh').css('visibility', 'visible'); } }); $(document).on('shown.lte.pushmenu', event => { $('.swh-words-logo-swh').css('visibility', 'hidden'); }); function ensureNoFooterOverflow() { $('body').css('padding-bottom', $('footer').outerHeight() + 'px'); } $(document).ready(() => { // redirect to last browse page if any when clicking on the 'Browse' entry // in the sidebar $(`.swh-browse-link`).click(event => { let lastBrowsePage = sessionStorage.getItem('last-browse-page'); if (lastBrowsePage) { event.preventDefault(); window.location = lastBrowsePage; } }); const mainSideBar = $('.main-sidebar'); function updateSidebarState() { const body = $('body'); if (body.hasClass('sidebar-collapse') && !mainSideBar.hasClass('swh-sidebar-collapsed')) { mainSideBar.removeClass('swh-sidebar-expanded'); mainSideBar.addClass('swh-sidebar-collapsed'); $('.swh-words-logo-swh').css('visibility', 'visible'); } else if (!body.hasClass('sidebar-collapse') && !mainSideBar.hasClass('swh-sidebar-expanded')) { mainSideBar.removeClass('swh-sidebar-collapsed'); mainSideBar.addClass('swh-sidebar-expanded'); $('.swh-words-logo-swh').css('visibility', 'hidden'); } // ensure correct sidebar state when loading a page if (body.hasClass('hold-transition')) { setTimeout(() => { updateSidebarState(); }); } } // set sidebar state after collapse / expand animation mainSideBar.on('transitionend', evt => { updateSidebarState(); }); updateSidebarState(); // ensure footer do not overflow main content for mobile devices // or after resizing the browser window ensureNoFooterOverflow(); $(window).resize(function() { ensureNoFooterOverflow(); if ($('body').hasClass('sidebar-collapse') && $('body').width() >= BREAKPOINT_MD) { $('.swh-words-logo-swh').css('visibility', 'visible'); } }); // activate css polyfill 'object-fit: contain' in old browsers objectFitImages(); // reparent the modals to the top navigation div in order to be able // to display them $('.swh-browse-top-navigation').append($('.modal')); let selectedCode = null; function getCodeOrPreEltUnderPointer(e) { let elts = document.elementsFromPoint(e.clientX, e.clientY); for (let elt of elts) { if (elt.nodeName === 'CODE' || elt.nodeName === 'PRE') { return elt; } } return null; } // click handler to set focus on code block for copy $(document).click(e => { selectedCode = getCodeOrPreEltUnderPointer(e); }); function selectCode(event, selectedCode) { if (selectedCode) { let hljsLnCodeElts = $(selectedCode).find('.hljs-ln-code'); if (hljsLnCodeElts.length) { selectText(hljsLnCodeElts[0], hljsLnCodeElts[hljsLnCodeElts.length - 1]); } else { selectText(selectedCode.firstChild, selectedCode.lastChild); } event.preventDefault(); } } // select the whole text of focused code block when user // double clicks or hits Ctrl+A $(document).dblclick(e => { if ((e.ctrlKey || e.metaKey)) { selectCode(e, getCodeOrPreEltUnderPointer(e)); } }); $(document).keydown(e => { if ((e.ctrlKey || e.metaKey) && e.key === 'a') { selectCode(e, selectedCode); } }); // show/hide back-to-top button let scrollThreshold = 0; scrollThreshold += $('.swh-top-bar').height() || 0; scrollThreshold += $('.navbar').height() || 0; $(window).scroll(() => { if ($(window).scrollTop() > scrollThreshold) { $('#back-to-top').css('display', 'block'); } else { $('#back-to-top').css('display', 'none'); } }); }); export function initPage(page) { $(document).ready(() => { // set relevant sidebar link to page active $(`.swh-${page}-item`).addClass('active'); $(`.swh-${page}-link`).addClass('active'); // triggered when unloading the current page $(window).on('unload', () => { // backup current browse page if (page === 'browse') { sessionStorage.setItem('last-browse-page', window.location); } }); }); } export function initHomePage() { $(document).ready(() => { $('.swh-coverage-list').iFrameResize({heightCalculationMethod: 'taggedElement'}); fetch(Urls.stat_counters()) .then(response => response.json()) .then(data => { if (data.stat_counters.content) { $('#swh-contents-count').html(data.stat_counters.content.toLocaleString()); $('#swh-revisions-count').html(data.stat_counters.revision.toLocaleString()); $('#swh-origins-count').html(data.stat_counters.origin.toLocaleString()); $('#swh-directories-count').html(data.stat_counters.directory.toLocaleString()); $('#swh-persons-count').html(data.stat_counters.person.toLocaleString()); $('#swh-releases-count').html(data.stat_counters.release.toLocaleString()); } if (data.stat_counters_history.content) { swh.webapp.drawHistoryCounterGraph('#swh-contents-count-history', data.stat_counters_history.content); swh.webapp.drawHistoryCounterGraph('#swh-revisions-count-history', data.stat_counters_history.revision); swh.webapp.drawHistoryCounterGraph('#swh-origins-count-history', data.stat_counters_history.origin); } else { $('#swh-contents-count-history').hide(); $('#swh-revisions-count-history').hide(); $('#swh-origins-count-history').hide(); } }); }); initPage('home'); } export function showModalMessage(title, message) { $('#swh-web-modal-message .modal-title').text(title); $('#swh-web-modal-message .modal-content p').text(message); $('#swh-web-modal-message').modal('show'); } export function showModalConfirm(title, message, callback) { $('#swh-web-modal-confirm .modal-title').text(title); $('#swh-web-modal-confirm .modal-content p').text(message); $('#swh-web-modal-confirm #swh-web-modal-confirm-ok-btn').bind('click', () => { callback(); $('#swh-web-modal-confirm').modal('hide'); $('#swh-web-modal-confirm #swh-web-modal-confirm-ok-btn').unbind('click'); }); $('#swh-web-modal-confirm').modal('show'); } export function showModalHtml(title, html) { $('#swh-web-modal-html .modal-title').text(title); $('#swh-web-modal-html .modal-body').html(html); $('#swh-web-modal-html').modal('show'); } export function addJumpToPagePopoverToDataTable(dataTableElt) { dataTableElt.on('draw.dt', function() { $('.paginate_button.disabled').css('cursor', 'pointer'); $('.paginate_button.disabled').on('click', event => { const pageInfo = dataTableElt.page.info(); let content = ' / ${pageInfo.pages}`; $(event.target).popover({ 'title': 'Jump to page', 'content': content, 'html': true, 'placement': 'top', 'sanitizeFn': swh.webapp.filterXSS }); $(event.target).popover('show'); $('.jump-to-page').on('change', function() { $('.paginate_button.disabled').popover('hide'); const pageNumber = parseInt($(this).val()) - 1; dataTableElt.page(pageNumber).draw('page'); }); }); }); dataTableElt.on('preXhr.dt', () => { $('.paginate_button.disabled').popover('hide'); }); } let swhObjectIcons; export function setSwhObjectIcons(icons) { swhObjectIcons = icons; } export function getSwhObjectIcon(swhObjectType) { return swhObjectIcons[swhObjectType]; } let browsedSwhObjectMetadata = {}; export function setBrowsedSwhObjectMetadata(metadata) { browsedSwhObjectMetadata = metadata; } export function getBrowsedSwhObjectMetadata() { return browsedSwhObjectMetadata; } + +// This will contain a mapping between an archived object type +// and its related SWHID metadata for each object reachable from +// the current browse view. +// SWHID metadata contain the following keys: +// * object_type: type of archived object +// * object_id: sha1 object identifier +// * swh_id: SWH persistent identifier without contextual info +// * swh_id_url: URL to resolve SWH persistent identifier without contextual info +let swhidsContext_ = {}; + +export function setSwhIdsContext(swhidsContext) { + swhidsContext_ = {}; + for (let swhidContext of swhidsContext) { + swhidsContext_[swhidContext.object_type] = swhidContext; + } +} + +export function getSwhIdsContext() { + return swhidsContext_; +} diff --git a/swh/web/templates/includes/show-swh-ids.html b/swh/web/templates/includes/show-swh-ids.html index 1223fd45..b81e61da 100644 --- a/swh/web/templates/includes/show-swh-ids.html +++ b/swh/web/templates/includes/show-swh-ids.html @@ -1,103 +1,106 @@ {% comment %} Copyright (C) 2017-2019 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 %} {% load swh_templatetags %} {% if swh_ids %}