diff --git a/cypress/integration/origin-visits.spec.js b/cypress/integration/origin-visits.spec.js index f0b1b000..3f2a7897 100644 --- a/cypress/integration/origin-visits.spec.js +++ b/cypress/integration/origin-visits.spec.js @@ -1,78 +1,94 @@ /** * 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 */ import {getTime} from '../utils'; let origin; function checkTimeLink(element) { expect(element.text()).not.to.be.empty; const urlParams = new URLSearchParams(element.attr('href').split('?')[1]); const timeStringLink = urlParams.get('timestamp'); // time in link should be equal to that in text assert.deepEqual(getTime(timeStringLink), getTime(element.text())); } function searchInCalendar(date) { cy.contains('label', 'Show all visits') .click(); cy.get(`.year${date.year}`) .click({force: true}); cy.contains('.month', date.monthName) .find('.day-content') .eq(date.date - 1) .trigger('mouseenter') .get('.popover-body') .should('be.visible') .and('contain', `${date.hours}:${date.minutes} UTC`); } describe('Visits tests', function() { before(function() { origin = this.origin[1]; }); beforeEach(function() { cy.visit(`${this.Urls.browse_origin_visits()}?origin_url=${origin.url}`); }); it('should display first full visit time', function() { cy.get('#swh-first-full-visit > .swh-visit-full') .then(($el) => { checkTimeLink($el); searchInCalendar(getTime($el.text())); }); }); it('should display last full visit time', function() { cy.get('#swh-last-full-visit > .swh-visit-full') .then(($el) => { checkTimeLink($el); searchInCalendar(getTime($el.text())); }); }); it('should display last visit time', function() { cy.get('#swh-last-visit > .swh-visit-full') .then(($el) => { checkTimeLink($el); searchInCalendar(getTime($el.text())); }); }); it('should display list of visits and mark them on calendar', function() { cy.get('.swh-visits-list-row .swh-visit-full') .should('be.visible') .each(($el) => { checkTimeLink($el); searchInCalendar(getTime($el.text())); }); }); + + it('should close calendar popover when leaving day', function() { + cy.get('#swh-last-visit > .swh-visit-full') + .then(($el) => { + const date = getTime($el.text()); + searchInCalendar(date); + cy.contains('.month', date.monthName) + .find('.day-content') + .eq(date.date - 1) + .trigger('mouseout'); + + cy.get('.popover') + .should('not.be.visible'); + }); + }); + }); diff --git a/swh/web/assets/src/bundles/origin/visits-calendar.js b/swh/web/assets/src/bundles/origin/visits-calendar.js index 92dd919e..3617e958 100644 --- a/swh/web/assets/src/bundles/origin/visits-calendar.js +++ b/swh/web/assets/src/bundles/origin/visits-calendar.js @@ -1,140 +1,147 @@ /** * 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 Calendar from 'js-year-calendar'; import 'js-year-calendar/dist/js-year-calendar.css'; let minSize = 15; let maxSize = 28; let currentPopover = null; let visitsByDate = {}; function closePopover() { if (currentPopover) { - $(currentPopover).popover('hide'); + $(currentPopover).popover('dispose'); currentPopover = null; } } // function to update the visits calendar view based on the selected year export function updateCalendar(year, filteredVisits, yearClickedCallback) { visitsByDate = {}; let maxNbVisitsByDate = 0; let minDate, maxDate; for (let i = 0; i < filteredVisits.length; ++i) { filteredVisits[i]['startDate'] = filteredVisits[i]['date']; filteredVisits[i]['endDate'] = filteredVisits[i]['startDate']; let date = new Date(filteredVisits[i]['date']); date.setHours(0, 0, 0, 0); let dateStr = date.toDateString(); if (!visitsByDate.hasOwnProperty(dateStr)) { visitsByDate[dateStr] = [filteredVisits[i]]; } else { visitsByDate[dateStr].push(filteredVisits[i]); } maxNbVisitsByDate = Math.max(maxNbVisitsByDate, visitsByDate[dateStr].length); if (i === 0) { minDate = maxDate = date; } else { if (date.getTime() < minDate.getTime()) { minDate = date; } if (date.getTime() > maxDate.getTime()) { maxDate = date; } } } closePopover(); new Calendar('#swh-visits-calendar', { dataSource: filteredVisits, style: 'custom', minDate: minDate, maxDate: maxDate, startYear: year, renderEnd: e => yearClickedCallback(e.currentYear), customDataSourceRenderer: (element, date, events) => { let dateStr = date.toDateString(); let nbVisits = visitsByDate[dateStr].length; let t = nbVisits / maxNbVisitsByDate; if (maxNbVisitsByDate === 1) { t = 0; } let size = minSize + t * (maxSize - minSize); let offsetX = (maxSize - size) / 2 - parseInt($(element).css('padding-left')); let offsetY = (maxSize - size) / 2 - parseInt($(element).css('padding-top')) + 1; let cellWrapper = $('
'); cellWrapper.css('position', 'relative'); let dayNumber = $(''); dayNumber.text($(element).text()); let circle = $(''); let r = 0; let g = 0; for (let i = 0; i < nbVisits; ++i) { let visit = visitsByDate[dateStr][i]; if (visit.status === 'full') { g += 255; } else if (visit.status === 'partial') { r += 255; g += 255; } else { r += 255; } } r /= nbVisits; g /= nbVisits; circle.css('background-color', 'rgba(' + r + ', ' + g + ', 0, 0.3)'); circle.css('width', size + 'px'); circle.css('height', size + 'px'); circle.css('border-radius', size + 'px'); circle.css('position', 'absolute'); circle.css('top', offsetY + 'px'); circle.css('left', offsetX + 'px'); cellWrapper.append(dayNumber); cellWrapper.append(circle); $(element)[0].innerHTML = $(cellWrapper)[0].outerHTML; }, mouseOnDay: e => { if (currentPopover !== e.element) { closePopover(); } let dateStr = e.date.toDateString(); if (visitsByDate.hasOwnProperty(dateStr)) { let visits = visitsByDate[dateStr]; let content = '