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;