diff --git a/components/common/Table.module.css b/components/common/Table.module.css index bde2588a..5db753ec 100644 --- a/components/common/Table.module.css +++ b/components/common/Table.module.css @@ -21,6 +21,7 @@ } .row { + display: flex; border-bottom: 1px solid var(--gray300); padding: 10px 0; } diff --git a/components/metrics/RealtimeLog.js b/components/metrics/RealtimeLog.js index fce9f7e9..9164dcdb 100644 --- a/components/metrics/RealtimeLog.js +++ b/components/metrics/RealtimeLog.js @@ -103,7 +103,7 @@ export default function RealtimeLog({ data, websites }) { return ( ); diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index 348b6642..af3fdc58 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -11,20 +11,22 @@ import RealtimeLog from '../metrics/RealtimeLog'; const REALTIME_RANGE = 30; const REALTIME_INTERVAL = 5000; -function filterTime(data, time) { - return data.filter(({ created_at }) => new Date(created_at).getTime() >= time); +function mergeData(state, data, time) { + const ids = state.map(({ __id }) => __id); + return state + .concat(data.filter(({ __id }) => !ids.includes(__id))) + .filter(({ created_at }) => new Date(created_at).getTime() >= time); } export default function RealtimeDashboard() { const [data, setData] = useState(); const [website, setWebsite] = useState(); - const [lastTime, setLastTime] = useState(); const { data: init, loading } = useFetch('/api/realtime', { type: 'init' }); const { data: updates } = useFetch( '/api/realtime', - { type: 'update', start_at: lastTime }, + { type: 'update', start_at: data?.timestamp }, { - disabled: !init?.token, + disabled: !init?.token || !data, interval: REALTIME_INTERVAL, headers: { 'x-umami-token': init?.token }, }, @@ -34,15 +36,15 @@ export default function RealtimeDashboard() { if (init && !data) { setData(init.data); } else if (updates) { - const { pageviews, sessions, events } = updates; + const { pageviews, sessions, events, timestamp } = updates; const minTime = subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(); setData(state => ({ - pageviews: filterTime(state.pageviews.concat(pageviews), minTime), - sessions: filterTime(state.sessions.concat(sessions), minTime), - events: filterTime(state.events.concat(events), minTime), + pageviews: mergeData(state.pageviews, pageviews, minTime), + sessions: mergeData(state.sessions, sessions, minTime), + events: mergeData(state.events, events, minTime), + timestamp, })); } - setLastTime(Date.now()); }, [init, updates]); if (!init || loading || !data) { diff --git a/pages/api/collect.js b/pages/api/collect.js index 4f01b225..a6140169 100644 --- a/pages/api/collect.js +++ b/pages/api/collect.js @@ -6,6 +6,8 @@ import { createToken } from 'lib/crypto'; import { getIpAddress } from '../../lib/request'; export default async (req, res) => { + await useCors(req, res); + if (isBot(req.headers['user-agent'])) { return ok(res); } @@ -19,7 +21,6 @@ export default async (req, res) => { } } - await useCors(req, res); await useSession(req, res); const { type, payload } = req.body; diff --git a/pages/api/realtime.js b/pages/api/realtime.js index 64cbea5e..538f611a 100644 --- a/pages/api/realtime.js +++ b/pages/api/realtime.js @@ -8,11 +8,30 @@ export default async (req, res) => { await useAuth(req, res); async function getData(websites, time) { - return Promise.all([ + const [pageviews, sessions, events] = await Promise.all([ getPageviews(websites, time), getSessions(websites, time), getEvents(websites, time), ]); + + return { + pageviews: pageviews.map(({ view_id, ...props }) => ({ + __id: `p${view_id}`, + view_id, + ...props, + })), + sessions: sessions.map(({ session_id, ...props }) => ({ + __id: `s${session_id}`, + session_id, + ...props, + })), + events: events.map(({ event_id, ...props }) => ({ + __id: `e${event_id}`, + event_id, + ...props, + })), + timestamp: Date.now(), + }; } if (req.method === 'GET') { @@ -22,10 +41,14 @@ export default async (req, res) => { if (type === 'init') { const websites = await getUserWebsites(user_id); const ids = websites.map(({ website_id }) => website_id); - const [pageviews, sessions, events] = await getData(ids, subMinutes(new Date(), 30)); const token = await createToken({ websites: ids }); + const data = await getData(ids, subMinutes(new Date(), 30)); - return ok(res, { websites, token, data: { pageviews, sessions, events } }); + return ok(res, { + websites, + token, + data, + }); } if (type === 'update') { @@ -37,9 +60,9 @@ export default async (req, res) => { const { websites } = await parseToken(token); - const [pageviews, sessions, events] = await getData(websites, new Date(+start_at)); + const data = await getData(websites, new Date(+start_at)); - return ok(res, { pageviews, sessions, events }); + return ok(res, data); } return badRequest(res);