diff --git a/assets/js/dashboard/api.js b/assets/js/dashboard/api.js index 636bf47e97..3d191a8527 100644 --- a/assets/js/dashboard/api.js +++ b/assets/js/dashboard/api.js @@ -45,7 +45,13 @@ export function serializeQuery(query, extraQuery=[]) { if (query.filters) { queryObj.filters = serializeFilters(query.filters) } if (query.with_imported) { queryObj.with_imported = query.with_imported } if (SHARED_LINK_AUTH) { queryObj.auth = SHARED_LINK_AUTH } - if (query.comparison) { queryObj.comparison = query.comparison } + + if (query.comparison) { + queryObj.comparison = query.comparison + queryObj.compare_from = query.compare_from + queryObj.compare_to = query.compare_to + } + Object.assign(queryObj, ...extraQuery) return '?' + serialize(queryObj) diff --git a/assets/js/dashboard/comparison-input.js b/assets/js/dashboard/comparison-input.js index bcc12d223c..23f74c2e9f 100644 --- a/assets/js/dashboard/comparison-input.js +++ b/assets/js/dashboard/comparison-input.js @@ -1,15 +1,18 @@ import React, { Fragment } from 'react' -import { withRouter } from "react-router-dom"; +import { withRouter } from 'react-router-dom' import { navigateToQuery } from './query' import { Menu, Transition } from '@headlessui/react' import { ChevronDownIcon } from '@heroicons/react/20/solid' import classNames from 'classnames' import * as storage from './util/storage' +import Flatpickr from 'react-flatpickr' +import { formatISO, parseUTCDate, formatDayShort } from './util/date.js' const COMPARISON_MODES = { 'off': 'Disable comparison', 'previous_period': 'Previous period', 'year_over_year': 'Year over year', + 'custom': 'Custom period', } const DEFAULT_COMPARISON_MODE = 'previous_period' @@ -26,6 +29,7 @@ export const getStoredComparisonMode = function(domain) { } const storeComparisonMode = function(domain, mode) { + if (mode == "custom") return storage.setItem(`comparison_mode__${domain}`, mode) } @@ -49,17 +53,31 @@ export const toggleComparisons = function(history, query, site) { } } -function DropdownItem({ label, value, isCurrentlySelected, updateMode }) { +function DropdownItem({ label, value, isCurrentlySelected, updateMode, calendar }) { + const click = () => { + if (value == "custom") { + // https://github.com/flatpickr/flatpickr/issues/399#issuecomment-260007013 + // FIXME: Use setState to prevent this issue + setTimeout(() => calendar.current.flatpickr.open(), 100) + } else { + updateMode(value) + } + } + + const render = ({ active }) => { + const buttonClass = classNames("px-4 py-2 w-full text-left font-medium text-sm dark:text-white cursor-pointer", { + "bg-gray-100 text-gray-900 dark:bg-gray-900 dark:text-gray-100": active, + "font-bold": isCurrentlySelected, + }) + + return + } + + const disabled = isCurrentlySelected && value !== "custom" + return ( - updateMode(value)} - disabled={isCurrentlySelected}> - {({ active }) => ( - - )} + + { render } ) } @@ -69,19 +87,46 @@ const ComparisonInput = function({ site, query, history }) { if (COMPARISON_DISABLED_PERIODS.includes(query.period)) return null if (!isComparisonEnabled(query.comparison)) return null - const updateMode = (key) => { - storeComparisonMode(site.domain, key) - navigateToQuery(history, query, { comparison: key }) + const updateMode = (mode, from = null, to = null) => { + storeComparisonMode(site.domain, mode) + navigateToQuery(history, query, { comparison: mode, compare_from: from, compare_to: to }) + } + + const buildLabel = (query) => { + if (query.comparison == "custom") { + const from = parseUTCDate(query.compare_from) + const to = parseUTCDate(query.compare_to) + return `${formatDayShort(from, false)} - ${formatDayShort(to, false)}` + } else { + return COMPARISON_MODES[query.comparison] + } + } + + const calendar = React.useRef(null) + + const flatpickrOptions = { + mode: 'range', + showMonths: 1, + maxDate: 'today', + minDate: parseUTCDate(site.statsBegin), + animate: true, + static: true, + onChange: ([from, to]) => { + if (from && to) updateMode("custom", formatISO(from), formatISO(to)) + } } return ( <> + + + vs.
-
+
- { COMPARISON_MODES[query.comparison] || 'Compare to' } + { buildLabel(query) } - { Object.keys(COMPARISON_MODES).map((key) => DropdownItem({ label: COMPARISON_MODES[key], value: key, isCurrentlySelected: key == query.comparison, updateMode })) } + { Object.keys(COMPARISON_MODES).map((key) => DropdownItem({ label: COMPARISON_MODES[key], value: key, isCurrentlySelected: key == query.comparison, updateMode, calendar })) } diff --git a/assets/js/dashboard/datepicker.js b/assets/js/dashboard/datepicker.js index 707b6bfbe6..2363d711bb 100644 --- a/assets/js/dashboard/datepicker.js +++ b/assets/js/dashboard/datepicker.js @@ -382,8 +382,6 @@ function DatePicker({query, site, history}) {
); } if (mode === "calendar") { - const insertionDate = new Date(site.statsBegin); - const dayBeforeCreation = insertionDate - 86400000; return (
dataPoint.dataset.yAxisID == "y") const comparisonData = tooltipModel.dataPoints.find((dataPoint) => dataPoint.dataset.yAxisID == "yComparison") - const label = renderBucketLabel(query, graphData, graphData.labels[data.dataIndex]) - const comparisonLabel = comparisonData && renderBucketLabel(query, graphData, graphData.comparison_labels[data.dataIndex], true) + const label = data && renderBucketLabel(query, graphData, graphData.labels[data.dataIndex]) + const comparisonLabel = comparisonData && renderBucketLabel(query, graphData, graphData.comparison_labels[comparisonData.dataIndex], true) const value = data?.raw || 0 const comparisonValue = comparisonData?.raw || 0 - const comparisonDifference = comparisonData && calculatePercentageDifference(comparisonValue, value) + const comparisonDifference = label && comparisonLabel && calculatePercentageDifference(comparisonValue, value) const metricFormatter = METRIC_FORMATTER[metric] const formattedValue = metricFormatter(value) @@ -84,21 +84,23 @@ export default function GraphTooltip(graphData, metric, query) {