diff --git a/components/common/Tag.js b/components/common/Tag.js new file mode 100644 index 00000000..29612dca --- /dev/null +++ b/components/common/Tag.js @@ -0,0 +1,7 @@ +import React from 'react'; +import classNames from 'classnames'; +import styles from './Tag.module.css'; + +export default function Tag({ className, children }) { + return {children}; +} diff --git a/components/metrics/EventsTable.module.css b/components/common/Tag.module.css similarity index 94% rename from components/metrics/EventsTable.module.css rename to components/common/Tag.module.css index e6cb3961..38d66692 100644 --- a/components/metrics/EventsTable.module.css +++ b/components/common/Tag.module.css @@ -1,4 +1,4 @@ -.type { +.tag { font-size: var(--font-size-small); padding: 2px 4px; border: 1px solid var(--gray300); diff --git a/components/metrics/ActiveUsers.js b/components/metrics/ActiveUsers.js index ca93f391..c099d8f7 100644 --- a/components/metrics/ActiveUsers.js +++ b/components/metrics/ActiveUsers.js @@ -5,7 +5,10 @@ import useFetch from 'hooks/useFetch'; import styles from './ActiveUsers.module.css'; export default function ActiveUsers({ websiteId, token, className }) { - const { data } = useFetch(`/api/website/${websiteId}/active`, { token }, { interval: 60000 }); + const { data } = useFetch(`/api/website/${websiteId}/active`, { + params: { token }, + interval: 60000, + }); const count = useMemo(() => { return data?.[0]?.x || 0; }, [data]); diff --git a/components/metrics/EventsChart.js b/components/metrics/EventsChart.js index 0a348a29..c2804f9e 100644 --- a/components/metrics/EventsChart.js +++ b/components/metrics/EventsChart.js @@ -17,14 +17,16 @@ export default function EventsChart({ websiteId, token }) { const { data } = useFetch( `/api/website/${websiteId}/events`, { - start_at: +startDate, - end_at: +endDate, - unit, - tz: timezone, - url: query.url, - token, + params: { + start_at: +startDate, + end_at: +endDate, + unit, + tz: timezone, + url: query.url, + token, + }, }, - { update: [modified] }, + [modified], ); const datasets = useMemo(() => { if (!data) return []; diff --git a/components/metrics/EventsTable.js b/components/metrics/EventsTable.js index 9a7a09cb..43b52c4d 100644 --- a/components/metrics/EventsTable.js +++ b/components/metrics/EventsTable.js @@ -1,7 +1,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import MetricsTable from './MetricsTable'; -import styles from './EventsTable.module.css'; +import Tag from 'components/common/Tag'; export default function EventsTable({ websiteId, token, limit, onDataLoad }) { return ( @@ -22,7 +22,7 @@ const Label = ({ value }) => { const [event, label] = value.split(':'); return ( <> - {event} + {event} {label} ); diff --git a/components/metrics/MetricsBar.js b/components/metrics/MetricsBar.js index ed2c7b12..9fbc3054 100644 --- a/components/metrics/MetricsBar.js +++ b/components/metrics/MetricsBar.js @@ -18,17 +18,19 @@ export default function MetricsBar({ websiteId, token, className }) { query: { url }, } = usePageQuery(); + console.log({ modified }); + const { data, error, loading } = useFetch( `/api/website/${websiteId}/stats`, { - start_at: +startDate, - end_at: +endDate, - url, - token, - }, - { - update: [modified], + params: { + start_at: +startDate, + end_at: +endDate, + url, + token, + }, }, + [modified], ); const formatFunc = format ? formatLongNumber : formatNumber; diff --git a/components/metrics/MetricsTable.js b/components/metrics/MetricsTable.js index 162dce0f..2f508295 100644 --- a/components/metrics/MetricsTable.js +++ b/components/metrics/MetricsTable.js @@ -40,14 +40,18 @@ export default function MetricsTable({ const { data, loading, error } = useFetch( `/api/website/${websiteId}/metrics`, { - type, - start_at: +startDate, - end_at: +endDate, - domain: websiteDomain, - url, - token, + params: { + type, + start_at: +startDate, + end_at: +endDate, + domain: websiteDomain, + url, + token, + }, + onDataLoad, + delay: 300, }, - { onDataLoad, delay: 300, update: [modified] }, + [modified], ); const [format, setFormat] = useState(true); const formatFunc = format ? formatLongNumber : formatNumber; diff --git a/components/metrics/PageviewsChart.js b/components/metrics/PageviewsChart.js index b937abe6..30e85e80 100644 --- a/components/metrics/PageviewsChart.js +++ b/components/metrics/PageviewsChart.js @@ -4,7 +4,7 @@ import tinycolor from 'tinycolor2'; import CheckVisible from 'components/helpers/CheckVisible'; import BarChart from './BarChart'; import useTheme from 'hooks/useTheme'; -import { THEME_COLORS } from 'lib/constants'; +import { THEME_COLORS, DEFAULT_ANIMATION_DURATION } from 'lib/constants'; export default function PageviewsChart({ websiteId, @@ -13,7 +13,7 @@ export default function PageviewsChart({ records, className, loading, - animationDuration = 300, + animationDuration = DEFAULT_ANIMATION_DURATION, }) { const intl = useIntl(); const [theme] = useTheme(); diff --git a/components/metrics/RealtimeChart.js b/components/metrics/RealtimeChart.js index b4703036..ad7534fd 100644 --- a/components/metrics/RealtimeChart.js +++ b/components/metrics/RealtimeChart.js @@ -2,6 +2,7 @@ import React, { useMemo, useRef } from 'react'; import { format, parseISO, startOfMinute, subMinutes, isBefore } from 'date-fns'; import PageviewsChart from './PageviewsChart'; import { getDateArray } from 'lib/date'; +import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; function mapData(data) { let last = 0; @@ -44,7 +45,7 @@ export default function RealtimeChart({ data, unit, ...props }) { prevEndDate.current = endDate; return 0; } - return 300; + return DEFAULT_ANIMATION_DURATION; }, [data]); return ( diff --git a/components/metrics/RealtimeLog.js b/components/metrics/RealtimeLog.js index 9164dcdb..1ea0080e 100644 --- a/components/metrics/RealtimeLog.js +++ b/components/metrics/RealtimeLog.js @@ -5,6 +5,7 @@ import firstBy from 'thenby'; import { format } from 'date-fns'; import Icon from 'components/common/Icon'; import Table, { TableRow } from 'components/common/Table'; +import Tag from 'components/common/Tag'; import useLocale from 'hooks/useLocale'; import useCountryNames from 'hooks/useCountryNames'; import { BROWSERS } from 'lib/constants'; @@ -92,7 +93,7 @@ export default function RealtimeLog({ data, websites }) { if (event_type) { return (
- {event_type} {event_value} + {event_type} {event_value}
); } diff --git a/components/metrics/RealtimeLog.module.css b/components/metrics/RealtimeLog.module.css index a2ffa893..5227be98 100644 --- a/components/metrics/RealtimeLog.module.css +++ b/components/metrics/RealtimeLog.module.css @@ -12,14 +12,6 @@ overflow: auto; } -.event { - font-size: var(--font-size-small); - padding: 2px 4px; - border: 1px solid var(--gray300); - border-radius: 4px; - margin-right: 10px; -} - .icon { align-self: center; margin-right: 20px; diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js index 13d369c0..5a8e7dbf 100644 --- a/components/metrics/WebsiteChart.js +++ b/components/metrics/WebsiteChart.js @@ -35,14 +35,17 @@ export default function WebsiteChart({ const { data, loading, error } = useFetch( `/api/website/${websiteId}/pageviews`, { - start_at: +startDate, - end_at: +endDate, - unit, - tz: timezone, - url, - token, + params: { + start_at: +startDate, + end_at: +endDate, + unit, + tz: timezone, + url, + token, + }, + onDataLoad, }, - { onDataLoad, update: [modified] }, + [modified], ); const chartData = useMemo(() => { diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index af3fdc58..bb2948bc 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; import { subMinutes, startOfMinute } from 'date-fns'; import Page from 'components/layout/Page'; @@ -18,19 +18,34 @@ function mergeData(state, data, time) { .filter(({ created_at }) => new Date(created_at).getTime() >= time); } +function filterWebsite(data, id) { + return data.filter(({ website_id }) => website_id === id); +} + export default function RealtimeDashboard() { const [data, setData] = useState(); const [website, setWebsite] = useState(); - const { data: init, loading } = useFetch('/api/realtime', { type: 'init' }); - const { data: updates } = useFetch( - '/api/realtime', - { type: 'update', start_at: data?.timestamp }, - { - disabled: !init?.token || !data, - interval: REALTIME_INTERVAL, - headers: { 'x-umami-token': init?.token }, - }, - ); + const { data: init, loading } = useFetch('/api/realtime', { params: { type: 'init' } }); + const { data: updates } = useFetch('/api/realtime', { + params: { type: 'update', start_at: data?.timestamp }, + disabled: !init?.token || !data, + interval: REALTIME_INTERVAL, + headers: { 'x-umami-token': init?.token }, + }); + + const realtimeData = useMemo(() => { + if (website) { + const { website_id } = website; + const { pageviews, sessions, events, ...props } = data; + return { + pageviews: filterWebsite(pageviews, website_id), + sessions: filterWebsite(sessions, website_id), + events: filterWebsite(events, website_id), + ...props, + }; + } + return data; + }, [data, website]); useEffect(() => { if (init && !data) { @@ -70,10 +85,15 @@ export default function RealtimeDashboard() { - +
- +
diff --git a/components/pages/WebsiteDetails.js b/components/pages/WebsiteDetails.js index 66545928..23b19f19 100644 --- a/components/pages/WebsiteDetails.js +++ b/components/pages/WebsiteDetails.js @@ -31,7 +31,7 @@ const views = { }; export default function WebsiteDetails({ websiteId, token }) { - const { data } = useFetch(`/api/website/${websiteId}`, { token }); + const { data } = useFetch(`/api/website/${websiteId}`, { params: { token } }); const [chartLoaded, setChartLoaded] = useState(false); const [countryData, setCountryData] = useState(); const [eventsData, setEventsData] = useState(); diff --git a/components/pages/WebsiteList.js b/components/pages/WebsiteList.js index 73d20c98..cd96ae9f 100644 --- a/components/pages/WebsiteList.js +++ b/components/pages/WebsiteList.js @@ -9,7 +9,7 @@ import Arrow from 'assets/arrow-right.svg'; import styles from './WebsiteList.module.css'; export default function WebsiteList({ userId }) { - const { data } = useFetch('/api/websites', { user_id: userId }); + const { data } = useFetch('/api/websites', { params: { user_id: userId } }); if (!data) { return null; diff --git a/components/settings/AccountSettings.js b/components/settings/AccountSettings.js index aa206fa9..8b3e874e 100644 --- a/components/settings/AccountSettings.js +++ b/components/settings/AccountSettings.js @@ -25,7 +25,7 @@ export default function AccountSettings() { const [deleteAccount, setDeleteAccount] = useState(); const [saved, setSaved] = useState(0); const [message, setMessage] = useState(); - const { data } = useFetch(`/api/accounts`, {}, { update: [saved] }); + const { data } = useFetch(`/api/accounts`, {}, [saved]); const Checkmark = ({ is_admin }) => (is_admin ? } size="medium" /> : null); diff --git a/components/settings/WebsiteSettings.js b/components/settings/WebsiteSettings.js index 17fc5952..b9bc731e 100644 --- a/components/settings/WebsiteSettings.js +++ b/components/settings/WebsiteSettings.js @@ -29,7 +29,7 @@ export default function WebsiteSettings() { const [showUrl, setShowUrl] = useState(); const [saved, setSaved] = useState(0); const [message, setMessage] = useState(); - const { data } = useFetch(`/api/websites`, {}, { update: [saved] }); + const { data } = useFetch(`/api/websites`, {}, [saved]); const Buttons = row => ( diff --git a/hooks/useFetch.js b/hooks/useFetch.js index 5301e2a1..0a75c58d 100644 --- a/hooks/useFetch.js +++ b/hooks/useFetch.js @@ -4,14 +4,14 @@ import { get } from 'lib/web'; import { updateQuery } from 'redux/actions/queries'; import { useRouter } from 'next/router'; -export default function useFetch(url, params = {}, options = {}) { +export default function useFetch(url, options = {}, update = []) { const dispatch = useDispatch(); const [data, setData] = useState(); const [status, setStatus] = useState(); const [error, setError] = useState(); const [loading, setLoadiing] = useState(false); const { basePath } = useRouter(); - const { update = [], onDataLoad = () => {}, disabled, headers, interval, delay = 0 } = options; + const { params, disabled, headers, interval, delay = 0, onDataLoad } = options; async function loadData() { try { @@ -30,7 +30,7 @@ export default function useFetch(url, params = {}, options = {}) { } setStatus(status); - onDataLoad(data); + onDataLoad?.(data); } catch (e) { console.error(e); setError(e); @@ -41,17 +41,17 @@ export default function useFetch(url, params = {}, options = {}) { useEffect(() => { if (url && !disabled) { - if (!data) { - setTimeout(() => loadData(), delay); - } - - const id = interval ? setInterval(() => loadData(), interval) : null; - - return () => { - clearInterval(id); - }; + setTimeout(() => loadData(), delay); } - }, [data, url, disabled, ...update]); + }, [url, disabled, ...update]); + + useEffect(() => { + const id = interval ? setInterval(() => loadData(), interval) : null; + + return () => { + clearInterval(id); + }; + }, [interval, params]); return { data, status, error, loading }; } diff --git a/lib/constants.js b/lib/constants.js index 85515481..95d78f58 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -51,6 +51,7 @@ export const EVENT_COLORS = [ '#ffec16', ]; +export const DEFAULT_ANIMATION_DURATION = 300; export const DEFAULT_DATE_RANGE = '24hour'; export const POSTGRESQL = 'postgresql'; diff --git a/package.json b/package.json index 22dd957b..5d4d077c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.82.0", + "version": "0.88.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", diff --git a/pages/share/[...id].js b/pages/share/[...id].js index 85455af4..012e778a 100644 --- a/pages/share/[...id].js +++ b/pages/share/[...id].js @@ -8,7 +8,7 @@ export default function SharePage() { const router = useRouter(); const { id } = router.query; const shareId = id?.[0]; - const { data } = useFetch(shareId ? `/api/share/${shareId}` : null); + const { data } = useFetch(`/api/share/${shareId}`, { disabled: !shareId }); if (!data) { return null;