From a30074c055da81c211d55cff520e2ef29d5d4844 Mon Sep 17 00:00:00 2001 From: Nick Radford Date: Tue, 9 Feb 2021 02:54:59 -0800 Subject: [PATCH 01/44] Addresses mikecao/umami#460 Adds an explicit meta tag for the viewport, resizes the navigation link size for smaller screens --- components/layout/Header.module.css | 3 +++ pages/_app.js | 1 + 2 files changed, 4 insertions(+) diff --git a/components/layout/Header.module.css b/components/layout/Header.module.css index 07e8939f..3b7339d5 100644 --- a/components/layout/Header.module.css +++ b/components/layout/Header.module.css @@ -44,4 +44,7 @@ .header { padding: 0 15px; } + .nav { + font-size: var(--font-size-normal); + } } diff --git a/pages/_app.js b/pages/_app.js index edfd2f5c..19392d03 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -36,6 +36,7 @@ export default function App({ Component, pageProps }) { + From 117bce9c1ab354a6d5411e25a32429f9c3c3a29f Mon Sep 17 00:00:00 2001 From: Joris Date: Wed, 10 Feb 2021 13:28:04 +0100 Subject: [PATCH 02/44] Correct hour display When unit is minute --- components/metrics/BarChart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/metrics/BarChart.js b/components/metrics/BarChart.js index 4354f18c..05353b2b 100644 --- a/components/metrics/BarChart.js +++ b/components/metrics/BarChart.js @@ -44,7 +44,7 @@ export default function BarChart({ switch (unit) { case 'minute': - return index % 2 === 0 ? dateFormat(d, 'h:mm', locale) : ''; + return index % 2 === 0 ? dateFormat(d, 'H:mm', locale) : ''; case 'hour': return dateFormat(d, 'ha', locale); case 'day': From 4a41d8be37c6277785e74451175741537db85530 Mon Sep 17 00:00:00 2001 From: gabehab Date: Thu, 11 Feb 2021 13:56:37 -0800 Subject: [PATCH 03/44] Add sliding stats for mobile --- components/metrics/MetricsBar.module.css | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/metrics/MetricsBar.module.css b/components/metrics/MetricsBar.module.css index b13e974f..7e90ba12 100644 --- a/components/metrics/MetricsBar.module.css +++ b/components/metrics/MetricsBar.module.css @@ -11,8 +11,6 @@ @media only screen and (max-width: 992px) { .bar { justify-content: space-between; - } - .bar > div:nth-child(n + 3) { - display: none; + overflow: auto; } } From 2cab8bb15d4ff23a7f94355d41748c48f242435b Mon Sep 17 00:00:00 2001 From: Alexander Klein Date: Mon, 15 Feb 2021 13:02:12 +0100 Subject: [PATCH 04/44] feature(lib): add event type filter --- lib/filters.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/filters.js b/lib/filters.js index d4853618..8adeb13e 100644 --- a/lib/filters.js +++ b/lib/filters.js @@ -113,6 +113,17 @@ export const refFilter = (data, { domain, domainOnly, raw }) => { return Object.keys(map).map(key => ({ x: key, y: map[key], w: links[key] })); }; +export const eventTypeFilter = (data, types) => { + if (!types || types.length === 0) { + return data; + } + + return data.filter(({ x }) => { + const [event] = x.split('\t'); + return types.some(type => type === event); + }); +}; + export const browserFilter = data => data.map(({ x, y }) => ({ x: BROWSERS[x] || x, y })).filter(({ x }) => x); From 8b6308d68650818e4651bcf56dc950119264e5e2 Mon Sep 17 00:00:00 2001 From: Alexander Klein Date: Mon, 15 Feb 2021 16:28:09 +0100 Subject: [PATCH 05/44] feature(lib): add event type query --- lib/queries.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/lib/queries.js b/lib/queries.js index df23bde8..f5a677a4 100644 --- a/lib/queries.js +++ b/lib/queries.js @@ -501,6 +501,41 @@ export function getEventMetrics( ); } +export function getEventTypes( + website_id, + start_at, + end_at, + timezone = 'utc', + unit = 'day', + filters = {}, +) { + const params = [website_id, start_at, end_at]; + const { url } = filters; + + let urlFilter = ''; + + if (url) { + urlFilter = `and url=$${params.length + 1}`; + params.push(decodeURIComponent(url)); + } + + return rawQuery( + ` + select + event_type x, + ${getDateQuery('created_at', unit, timezone)} t, + count(*) y + from event + where website_id=$1 + and created_at between $2 and $3 + ${urlFilter} + group by 1, 2 + order by 2 + `, + params, + ); +} + export async function getRealtimeData(websites, time) { const [pageviews, sessions, events] = await Promise.all([ getPageviews(websites, time), From 67551fb1294669e635ec96be08d047d1a70fe5fa Mon Sep 17 00:00:00 2001 From: Alexander Klein Date: Mon, 15 Feb 2021 16:28:46 +0100 Subject: [PATCH 06/44] feature(api): add event types endpoint --- pages/api/website/[id]/event-types.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 pages/api/website/[id]/event-types.js diff --git a/pages/api/website/[id]/event-types.js b/pages/api/website/[id]/event-types.js new file mode 100644 index 00000000..9b461cb8 --- /dev/null +++ b/pages/api/website/[id]/event-types.js @@ -0,0 +1,25 @@ +import { getEventTypes } from 'lib/queries'; +import { ok, methodNotAllowed, unauthorized } from 'lib/response'; +import { allowQuery } from 'lib/auth'; + +export default async (req, res) => { + if (req.method === 'GET') { + if (!(await allowQuery(req))) { + return unauthorized(res); + } + + const { id, start_at, end_at, url } = req.query; + + const websiteId = +id; + const startDate = new Date(+start_at); + const endDate = new Date(+end_at); + + const eventTypes = await getEventTypes(websiteId, startDate, endDate, undefined, undefined, { + url, + }); + + return ok(res, eventTypes); + } + + return methodNotAllowed(res); +}; From 49b45acf4bfb7e658025aaf983b207ba6be03537 Mon Sep 17 00:00:00 2001 From: Alexander Klein Date: Mon, 15 Feb 2021 16:31:53 +0100 Subject: [PATCH 07/44] feature(dashboard): filter events by their type --- components/metrics/EventsTable.js | 65 ++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/components/metrics/EventsTable.js b/components/metrics/EventsTable.js index 437942fb..2d301f8d 100644 --- a/components/metrics/EventsTable.js +++ b/components/metrics/EventsTable.js @@ -1,18 +1,65 @@ -import React from 'react'; +import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; import MetricsTable from './MetricsTable'; import Tag from 'components/common/Tag'; +import DropDown from 'components/common/DropDown'; +import { eventTypeFilter } from 'lib/filters'; +import useDateRange from 'hooks/useDateRange'; +import useFetch from 'hooks/useFetch'; +import usePageQuery from 'hooks/usePageQuery'; +import useShareToken from 'hooks/useShareToken'; +import { TOKEN_HEADER } from 'lib/constants'; + +const EVENT_FILTER_DEFAULT = { + value: 'EVENT_FILTER_DEFAULT', + label: 'All Events', +}; export default function EventsTable({ websiteId, ...props }) { + const [eventType, setEventType] = useState(EVENT_FILTER_DEFAULT.value); + + const { + query: { url }, + } = usePageQuery(); + + const shareToken = useShareToken(); + const [dateRange] = useDateRange(websiteId); + + const { startDate, endDate, modified } = dateRange; + const { data, loading, error } = useFetch( + `/api/website/${websiteId}/event-types`, + { + params: { + start_at: +startDate, + end_at: +endDate, + url, + }, + headers: { [TOKEN_HEADER]: shareToken?.token }, + }, + [modified], + ); + + const eventTypes = data ? [...new Set(data.map(({ x }) => x))] : []; + const dropDownOptions = [EVENT_FILTER_DEFAULT, ...eventTypes.map(t => ({ value: t, label: t }))]; + return ( - } - type="event" - metric={} - websiteId={websiteId} - renderLabel={({ x }) =>