From 93b77672f3340d7d56a9844988a439360cf6fd4e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 15 Feb 2023 02:27:18 -0800 Subject: [PATCH] More updates to realtime. --- components/{common => input}/DateFilter.js | 0 components/input/WebsiteSelect.js | 28 ++++ components/layout/PageHeader.module.css | 4 +- components/messages.js | 1 + components/metrics/DataTable.js | 2 +- components/metrics/EventDataForm.js | 2 +- components/metrics/WebsiteChart.js | 2 +- components/pages/console/TestConsole.js | 13 +- .../pages/realtime/RealtimeDashboard.js | 135 +++++++----------- components/pages/realtime/RealtimeHeader.js | 20 +-- components/pages/realtime/RealtimeHome.js | 28 ++++ components/pages/realtime/RealtimeLog.js | 14 +- .../{RealtimeViews.js => RealtimeUrls.js} | 31 ++-- .../settings/profile/DateRangeSetting.js | 2 +- hooks/useLocale.js | 2 +- hooks/useTheme.js | 2 +- next.config.js | 1 + pages/404.js | 18 +-- pages/api/realtime/[id].ts | 20 +++ pages/api/realtime/init.ts | 28 ---- pages/api/realtime/update.ts | 36 ----- pages/dashboard/{[[...id]].js => index.js} | 0 pages/realtime/[id]/index.js | 18 +++ pages/{realtime.js => realtime/index.js} | 4 +- queries/analytics/event/getEvents.ts | 16 +-- queries/analytics/pageview/getPageviews.ts | 22 ++- queries/analytics/session/getSessions.ts | 18 +-- queries/analytics/stats/getRealtimeData.ts | 8 +- 28 files changed, 215 insertions(+), 260 deletions(-) rename components/{common => input}/DateFilter.js (100%) create mode 100644 components/input/WebsiteSelect.js create mode 100644 components/pages/realtime/RealtimeHome.js rename components/pages/realtime/{RealtimeViews.js => RealtimeUrls.js} (71%) create mode 100644 pages/api/realtime/[id].ts delete mode 100644 pages/api/realtime/init.ts delete mode 100644 pages/api/realtime/update.ts rename pages/dashboard/{[[...id]].js => index.js} (100%) create mode 100644 pages/realtime/[id]/index.js rename pages/{realtime.js => realtime/index.js} (58%) diff --git a/components/common/DateFilter.js b/components/input/DateFilter.js similarity index 100% rename from components/common/DateFilter.js rename to components/input/DateFilter.js diff --git a/components/input/WebsiteSelect.js b/components/input/WebsiteSelect.js new file mode 100644 index 0000000000..82f516e82f --- /dev/null +++ b/components/input/WebsiteSelect.js @@ -0,0 +1,28 @@ +import { useIntl } from 'react-intl'; +import { Dropdown, Item } from 'react-basics'; +import { labels } from 'components/messages'; +import useApi from 'hooks/useApi'; + +export default function WebsiteSelect({ websiteId, onSelect }) { + const { formatMessage } = useIntl(); + const { get, useQuery } = useApi(); + const { data } = useQuery(['websites:me'], () => get('/me/websites')); + + const renderValue = value => { + return data?.find(({ id }) => id === value)?.name; + }; + + return ( + + {item => {item.name}} + + ); +} diff --git a/components/layout/PageHeader.module.css b/components/layout/PageHeader.module.css index c48749f7bf..511f10913e 100644 --- a/components/layout/PageHeader.module.css +++ b/components/layout/PageHeader.module.css @@ -19,7 +19,7 @@ .title { display: flex; align-items: center; - font-size: 18px; - font-weight: bold; + font-size: 24px; + font-weight: 700; gap: 20px; } diff --git a/components/messages.js b/components/messages.js index ce506fd123..28f5bf18d0 100644 --- a/components/messages.js +++ b/components/messages.js @@ -96,6 +96,7 @@ export const labels = defineMessages({ selectWebsite: { id: 'label.select-website', defaultMessage: 'Select website' }, all: { id: 'label.all', defaultMessage: 'All' }, sessions: { id: 'label.sessions', defaultMessage: 'Sessions' }, + pageNotFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' }, }); export const messages = defineMessages({ diff --git a/components/metrics/DataTable.js b/components/metrics/DataTable.js index 9a2de075fd..76272808e3 100644 --- a/components/metrics/DataTable.js +++ b/components/metrics/DataTable.js @@ -8,7 +8,7 @@ import { formatNumber, formatLongNumber } from 'lib/format'; import styles from './DataTable.module.css'; export default function DataTable({ - data, + data = [], title, metric, className, diff --git a/components/metrics/EventDataForm.js b/components/metrics/EventDataForm.js index dbcc59179a..aa35041527 100644 --- a/components/metrics/EventDataForm.js +++ b/components/metrics/EventDataForm.js @@ -1,6 +1,6 @@ import { useMutation } from '@tanstack/react-query'; import classNames from 'classnames'; -import DateFilter from 'components/common/DateFilter'; +import DateFilter from 'components/input/DateFilter'; import DataTable from 'components/metrics/DataTable'; import FilterTags from 'components/metrics/FilterTags'; import useApi from 'hooks/useApi'; diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js index 2f495a3715..52132fd76b 100644 --- a/components/metrics/WebsiteChart.js +++ b/components/metrics/WebsiteChart.js @@ -5,7 +5,7 @@ import Link from 'next/link'; import PageviewsChart from './PageviewsChart'; import MetricsBar from './MetricsBar'; import WebsiteHeader from './WebsiteHeader'; -import DateFilter from 'components/common/DateFilter'; +import DateFilter from 'components/input/DateFilter'; import StickyHeader from 'components/helpers/StickyHeader'; import ErrorMessage from 'components/common/ErrorMessage'; import FilterTags from 'components/metrics/FilterTags'; diff --git a/components/pages/console/TestConsole.js b/components/pages/console/TestConsole.js index a0c562c87e..0f9c144e2a 100644 --- a/components/pages/console/TestConsole.js +++ b/components/pages/console/TestConsole.js @@ -6,12 +6,13 @@ import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import EventsChart from 'components/metrics/EventsChart'; import WebsiteChart from 'components/metrics/WebsiteChart'; +import WebsiteSelect from 'components/input/WebsiteSelect'; import useApi from 'hooks/useApi'; import styles from './TestConsole.module.css'; export default function TestConsole() { const { get, useQuery } = useApi(); - const { data, isLoading, error } = useQuery(['websites:test-console'], () => get('/websites')); + const { data, isLoading, error } = useQuery(['websites:me'], () => get('/me/websites')); const router = useRouter(); const { basePath, @@ -50,15 +51,7 @@ export default function TestConsole() { )} - website?.name || 'Select website'} - value={website?.id} - onChange={handleChange} - style={{ width: 300 }} - > - {({ id, name }) => {name}} - + {website && ( <> diff --git a/components/pages/realtime/RealtimeDashboard.js b/components/pages/realtime/RealtimeDashboard.js index 8a9b77b4fa..b45d85d25a 100644 --- a/components/pages/realtime/RealtimeDashboard.js +++ b/components/pages/realtime/RealtimeDashboard.js @@ -7,43 +7,38 @@ import Page from 'components/layout/Page'; import RealtimeChart from 'components/metrics/RealtimeChart'; import RealtimeLog from 'components/pages/realtime/RealtimeLog'; import RealtimeHeader from 'components/pages/realtime/RealtimeHeader'; +import StickyHeader from 'components/helpers/StickyHeader'; +import PageHeader from 'components/layout/PageHeader'; import WorldMap from 'components/common/WorldMap'; import DataTable from 'components/metrics/DataTable'; -import RealtimeViews from 'components/pages/realtime/RealtimeViews'; +import RealtimeUrls from 'components/pages/realtime/RealtimeUrls'; import useApi from 'hooks/useApi'; import useLocale from 'hooks/useLocale'; import useCountryNames from 'hooks/useCountryNames'; import { percentFilter } from 'lib/filters'; import { labels } from 'components/messages'; -import { SHARE_TOKEN_HEADER, REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants'; +import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants'; import styles from './RealtimeDashboard.module.css'; -import StickyHeader from 'components/helpers/StickyHeader'; -import PageHeader from 'components/layout/PageHeader'; -import ActiveUsers from 'components/metrics/ActiveUsers'; +import WebsiteSelect from '../../input/WebsiteSelect'; +import { useRouter } from 'next/router'; -function mergeData(state, data, time) { +function mergeData(state = [], data, time) { const ids = state.map(({ __id }) => __id); return state .concat(data.filter(({ __id }) => !ids.includes(__id))) .filter(({ createdAt }) => new Date(createdAt).getTime() >= time); } -function filterWebsite(data, id) { - return data.filter(({ websiteId }) => websiteId === id); -} - -export default function RealtimeDashboard() { +export default function RealtimeDashboard({ websiteId }) { const { formatMessage } = useIntl(); const { locale } = useLocale(); + const router = useRouter(); const countryNames = useCountryNames(locale); - const [data, setData] = useState(); - const [websiteId, setWebsiteId] = useState(); + const [currentData, setCurrentData] = useState(); const { get, useQuery } = useApi(); - const { data: websites, isLoading } = useQuery(['websites:me'], () => get('/me/websites')); - - const { data: updates } = useQuery( - ['realtime:updates'], - () => get('/realtime/update', { startAt: data?.timestamp }), + const { data, isLoading, error } = useQuery( + ['realtime', websiteId], + () => get(`/realtime/${websiteId}`, { startAt: currentData?.timestamp }), { enabled: !!websiteId, retryInterval: REALTIME_INTERVAL, @@ -55,91 +50,67 @@ export default function RealtimeDashboard() { [countryNames], ); - const realtimeData = useMemo(() => { + useEffect(() => { if (data) { - const { pageviews, sessions, events } = data; + const { pageviews, sessions, events, timestamp } = data; + const time = subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(); - if (websiteId) { - const { id } = websites.find(n => n.id === websiteId); - return { - pageviews: filterWebsite(pageviews, id), - sessions: filterWebsite(sessions, id), - events: filterWebsite(events, id), - }; - } + setCurrentData(state => ({ + ...state, + pageviews: mergeData(state?.pageviews, pageviews, time), + sessions: mergeData(state?.sessions, sessions, time), + events: mergeData(state?.events, events, time), + timestamp, + })); } + }, [data]); - return data; - }, [data, websiteId]); - - const count = useMemo(() => { - if (data) { - const { sessions } = data; - return sessions.filter( - ({ createdAt }) => differenceInMinutes(new Date(), new Date(createdAt)) <= 5, - ).length; + const realtimeData = useMemo(() => { + if (!currentData) { + return { pageviews: [], sessions: [], events: [], countries: [] }; } - }, [data, websiteId]); - const countries = useMemo(() => { - if (realtimeData?.sessions) { - return percentFilter( - realtimeData.sessions - .reduce((arr, { country }) => { - if (country) { - const row = arr.find(({ x }) => x === country); + currentData.countries = percentFilter( + currentData.sessions + .reduce((arr, { country }) => { + if (country) { + const row = arr.find(({ x }) => x === country); - if (!row) { - arr.push({ x: country, y: 1 }); - } else { - row.y += 1; - } + if (!row) { + arr.push({ x: country, y: 1 }); + } else { + row.y += 1; } - return arr; - }, []) - .sort(firstBy('y', -1)), - ); - } - return []; - }, [realtimeData?.sessions]); + } + return arr; + }, []) + .sort(firstBy('y', -1)), + ); - useEffect(() => { - if (updates) { - const { pageviews, sessions, events, timestamp } = updates; - const time = subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(); + return currentData; + }, [currentData]); - setData(state => ({ - ...state, - pageviews: mergeData(state.pageviews, pageviews, time), - sessions: mergeData(state.sessions, sessions, time), - events: mergeData(state.events, events, time), - timestamp, - })); - } - }, [updates]); + const handleSelect = id => { + router.push(`/realtime/${id}`); + }; return ( - + - + - +
- + - + @@ -147,12 +118,12 @@ export default function RealtimeDashboard() { - +
diff --git a/components/pages/realtime/RealtimeHeader.js b/components/pages/realtime/RealtimeHeader.js index 69013a700f..2f34e766ae 100644 --- a/components/pages/realtime/RealtimeHeader.js +++ b/components/pages/realtime/RealtimeHeader.js @@ -1,18 +1,12 @@ import { useIntl } from 'react-intl'; -import { Dropdown, Item } from 'react-basics'; import MetricCard from 'components/metrics/MetricCard'; import { labels } from 'components/messages'; import styles from './RealtimeHeader.module.css'; -export default function RealtimeHeader({ data, websiteId, websites, onSelect }) { +export default function RealtimeHeader({ data = {} }) { const { formatMessage } = useIntl(); - const { pageviews, sessions, events, countries } = data; - const renderValue = value => { - return websites?.find(({ id }) => id === value)?.name; - }; - return (
@@ -25,20 +19,10 @@ export default function RealtimeHeader({ data, websiteId, websites, onSelect })
- - {item => {item.name}} -
); } diff --git a/components/pages/realtime/RealtimeHome.js b/components/pages/realtime/RealtimeHome.js new file mode 100644 index 0000000000..f54ed4deee --- /dev/null +++ b/components/pages/realtime/RealtimeHome.js @@ -0,0 +1,28 @@ +import { useEffect } from 'react'; +import { useRouter } from 'next/router'; +import { useIntl } from 'react-intl'; +import Page from 'components/layout/Page'; +import PageHeader from 'components/layout/PageHeader'; +import useApi from 'hooks/useApi'; +import { labels, messages } from 'components/messages'; +import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; + +export default function RealtimeHome() { + const { formatMessage } = useIntl(); + const { get, useQuery } = useApi(); + const router = useRouter(); + const { data, isLoading, error } = useQuery(['websites:me'], () => get('/me/websites')); + + useEffect(() => { + if (data?.length) { + router.push(`realtime/${data[0].id}`); + } + }, [data]); + + return ( + + + {data?.length === 0 && } + + ); +} diff --git a/components/pages/realtime/RealtimeLog.js b/components/pages/realtime/RealtimeLog.js index 8d293e37ca..25fba487bf 100644 --- a/components/pages/realtime/RealtimeLog.js +++ b/components/pages/realtime/RealtimeLog.js @@ -26,7 +26,7 @@ const TYPE_ICONS = { [TYPE_EVENT]: , }; -export default function RealtimeLog({ data, websites, websiteId }) { +export default function RealtimeLog({ data, websiteDomain }) { const { formatMessage } = useIntl(); const { locale } = useLocale(); const countryNames = useCountryNames(locale); @@ -92,10 +92,6 @@ export default function RealtimeLog({ data, websites, websiteId }) { return TYPE_ICONS[getType(row)]; } - function getWebsite({ websiteId }) { - return websites.find(n => n.id === websiteId); - } - function getDetail({ eventName, pageviewId, @@ -111,11 +107,10 @@ export default function RealtimeLog({ data, websites, websiteId }) { return
{eventName}
; } if (pageviewId) { - const domain = getWebsite({ websiteId })?.domain; return ( @@ -146,7 +141,7 @@ export default function RealtimeLog({ data, websites, websiteId }) { function getColor(row) { const { sessionId } = row; - return stringToColor(uuids[sessionId] || `${sessionId}${getWebsite(row)}`); + return stringToColor(uuids[sessionId] || `${sessionId}}`); } const Row = ({ index, style }) => { @@ -161,9 +156,6 @@ export default function RealtimeLog({ data, websites, websiteId }) { {getDetail(row)} - {!websiteId && websites.length > 1 && ( -
{getWebsite(row)?.domain}
- )} ); }; diff --git a/components/pages/realtime/RealtimeViews.js b/components/pages/realtime/RealtimeUrls.js similarity index 71% rename from components/pages/realtime/RealtimeViews.js rename to components/pages/realtime/RealtimeUrls.js index c1f93be1dc..8437af40f4 100644 --- a/components/pages/realtime/RealtimeViews.js +++ b/components/pages/realtime/RealtimeUrls.js @@ -1,5 +1,5 @@ -import { useMemo, useState, useCallback } from 'react'; -import { ButtonGroup, Button } from 'react-basics'; +import { useMemo, useState } from 'react'; +import { ButtonGroup, Button, Flexbox } from 'react-basics'; import { useIntl } from 'react-intl'; import firstBy from 'thenby'; import { percentFilter } from 'lib/filters'; @@ -7,18 +7,10 @@ import DataTable from 'components/metrics/DataTable'; import { FILTER_PAGES, FILTER_REFERRERS } from 'lib/constants'; import { labels } from 'components/messages'; -export default function RealtimeViews({ websiteId, data = {}, websites }) { +export default function RealtimeUrls({ websiteDomain, data = {} }) { const { formatMessage } = useIntl(); const { pageviews } = data; const [filter, setFilter] = useState(FILTER_REFERRERS); - const domains = useMemo(() => websites.map(({ domain }) => domain), [websites]); - const getDomain = useCallback( - id => - websites.length === 1 - ? websites[0]?.domain - : websites.find(({ websiteId }) => websiteId === id)?.domain, - [websites], - ); const buttons = [ { @@ -32,7 +24,7 @@ export default function RealtimeViews({ websiteId, data = {}, websites }) { ]; const renderLink = ({ x }) => { - const domain = x.startsWith('/') ? getDomain(websiteId) : ''; + const domain = x.startsWith('/') ? websiteDomain : ''; return (
{x} @@ -48,7 +40,7 @@ export default function RealtimeViews({ websiteId, data = {}, websites }) { if (referrer?.startsWith('http')) { const hostname = new URL(referrer).hostname.replace(/^www\./, ''); - if (hostname && !domains.includes(hostname)) { + if (hostname) { const row = arr.find(({ x }) => x === hostname); if (!row) { @@ -65,11 +57,8 @@ export default function RealtimeViews({ websiteId, data = {}, websites }) { const pages = percentFilter( pageviews - .reduce((arr, { url, websiteId }) => { + .reduce((arr, { url }) => { if (url?.startsWith('/')) { - if (!websiteId && websites.length > 1) { - url = `${getDomain(websiteId)}${url}`; - } const row = arr.find(({ x }) => x === url); if (!row) { @@ -91,9 +80,11 @@ export default function RealtimeViews({ websiteId, data = {}, websites }) { return ( <> - - {({ key, label }) => } - + + + {({ key, label }) => } + + {filter === FILTER_REFERRERS && ( { - const url = new URL(window.location.href); + const url = new URL(window?.location?.href); const locale = url.searchParams.get('locale'); if (locale) { diff --git a/hooks/useTheme.js b/hooks/useTheme.js index 5c21bf1ceb..c50f442f07 100644 --- a/hooks/useTheme.js +++ b/hooks/useTheme.js @@ -24,7 +24,7 @@ export default function useTheme() { }, [theme]); useEffect(() => { - const url = new URL(window.location.href); + const url = new URL(window?.location?.href); const theme = url.searchParams.get('theme'); if (['light', 'dark'].includes(theme)) { diff --git a/next.config.js b/next.config.js index eac47415f6..c654b086fa 100644 --- a/next.config.js +++ b/next.config.js @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ require('dotenv').config(); const pkg = require('./package.json'); diff --git a/pages/404.js b/pages/404.js index 9f13a5455f..12cb41133b 100644 --- a/pages/404.js +++ b/pages/404.js @@ -1,18 +1,20 @@ +import { Row, Column, Flexbox } from 'react-basics'; +import { useIntl } from 'react-intl'; import AppLayout from 'components/layout/AppLayout'; -import { useIntl, defineMessages } from 'react-intl'; - -const messages = defineMessages({ - notFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' }, -}); +import { labels } from 'components/messages'; export default function Custom404() { const { formatMessage } = useIntl(); return ( -
-

{formatMessage(messages.notFound)}

-
+ + + +

{formatMessage(labels.pageNotFound)}

+
+
+
); } diff --git a/pages/api/realtime/[id].ts b/pages/api/realtime/[id].ts new file mode 100644 index 0000000000..d8e6ad623c --- /dev/null +++ b/pages/api/realtime/[id].ts @@ -0,0 +1,20 @@ +import { subMinutes } from 'date-fns'; +import { RealtimeInit, NextApiRequestAuth } from 'lib/types'; +import { useAuth } from 'lib/middleware'; +import { NextApiResponse } from 'next'; +import { methodNotAllowed, ok } from 'next-basics'; +import { getRealtimeData } from 'queries'; + +export default async (req: NextApiRequestAuth, res: NextApiResponse) => { + await useAuth(req, res); + + if (req.method === 'GET') { + const { id } = req.query; + + const data = await getRealtimeData(id, subMinutes(new Date(), 30)); + + return ok(res, data); + } + + return methodNotAllowed(res); +}; diff --git a/pages/api/realtime/init.ts b/pages/api/realtime/init.ts deleted file mode 100644 index 1cb5746fb5..0000000000 --- a/pages/api/realtime/init.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { subMinutes } from 'date-fns'; -import { RealtimeInit, NextApiRequestAuth } from 'lib/types'; -import { secret } from 'lib/crypto'; -import { useAuth } from 'lib/middleware'; -import { NextApiResponse } from 'next'; -import { createToken, methodNotAllowed, ok } from 'next-basics'; -import { getRealtimeData, getUserWebsites } from 'queries'; - -export default async (req: NextApiRequestAuth, res: NextApiResponse) => { - await useAuth(req, res); - - if (req.method === 'GET') { - const { id: userId } = req.auth.user; - - const websites = await getUserWebsites(userId); - const ids = websites.map(({ id }) => id); - const token = createToken({ websites: ids }, secret()); - const data = await getRealtimeData(ids, subMinutes(new Date(), 30)); - - return ok(res, { - websites, - token, - data, - }); - } - - return methodNotAllowed(res); -}; diff --git a/pages/api/realtime/update.ts b/pages/api/realtime/update.ts deleted file mode 100644 index 8125d02d5a..0000000000 --- a/pages/api/realtime/update.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ok, methodNotAllowed, badRequest, parseToken } from 'next-basics'; -import { useAuth } from 'lib/middleware'; -import { getRealtimeData } from 'queries'; -import { SHARE_TOKEN_HEADER } from 'lib/constants'; -import { secret } from 'lib/crypto'; -import { NextApiRequestQueryBody, RealtimeUpdate } from 'lib/types'; -import { NextApiResponse } from 'next'; - -export interface InitUpdateRequestQuery { - startAt: string; -} - -export default async ( - req: NextApiRequestQueryBody, - res: NextApiResponse, -) => { - await useAuth(req, res); - - if (req.method === 'GET') { - const { startAt } = req.query; - - const token = req.headers[SHARE_TOKEN_HEADER]; - - if (!token) { - return badRequest(res); - } - - const { websites } = parseToken(token, secret()); - - const data = await getRealtimeData(websites, new Date(+startAt)); - - return ok(res, data); - } - - return methodNotAllowed(res); -}; diff --git a/pages/dashboard/[[...id]].js b/pages/dashboard/index.js similarity index 100% rename from pages/dashboard/[[...id]].js rename to pages/dashboard/index.js diff --git a/pages/realtime/[id]/index.js b/pages/realtime/[id]/index.js new file mode 100644 index 0000000000..3ef556be57 --- /dev/null +++ b/pages/realtime/[id]/index.js @@ -0,0 +1,18 @@ +import { useRouter } from 'next/router'; +import AppLayout from 'components/layout/AppLayout'; +import RealtimeDashboard from 'components/pages/realtime/RealtimeDashboard'; + +export default function RealtimeDetailsPage() { + const router = useRouter(); + const { id: websiteId } = router.query; + + if (!websiteId) { + return null; + } + + return ( + + + + ); +} diff --git a/pages/realtime.js b/pages/realtime/index.js similarity index 58% rename from pages/realtime.js rename to pages/realtime/index.js index 008255e6b5..fa0d86d63c 100644 --- a/pages/realtime.js +++ b/pages/realtime/index.js @@ -1,10 +1,10 @@ import AppLayout from 'components/layout/AppLayout'; -import RealtimeDashboard from 'components/pages/realtime/RealtimeDashboard'; +import RealtimeHome from 'components/pages/realtime/RealtimeHome'; export default function RealtimePage() { return ( - + ); } diff --git a/queries/analytics/event/getEvents.ts b/queries/analytics/event/getEvents.ts index 5d01824c3d..953b1a2878 100644 --- a/queries/analytics/event/getEvents.ts +++ b/queries/analytics/event/getEvents.ts @@ -3,19 +3,17 @@ import clickhouse from 'lib/clickhouse'; import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db'; import { EVENT_TYPE } from 'lib/constants'; -export function getEvents(...args: [websites: string[], startAt: Date]) { +export function getEvents(...args: [websiteId: string, startAt: Date]) { return runQuery({ [PRISMA]: () => relationalQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args), }); } -function relationalQuery(websites: string[], startAt: Date) { - return prisma.client.event.findMany({ +function relationalQuery(websiteId: string, startAt: Date) { + return prisma.client.websiteEvent.findMany({ where: { - websiteId: { - in: websites, - }, + websiteId, createdAt: { gte: startAt, }, @@ -23,7 +21,7 @@ function relationalQuery(websites: string[], startAt: Date) { }); } -function clickhouseQuery(websites: string[], startAt: Date) { +function clickhouseQuery(websiteId: string, startAt: Date) { const { rawQuery } = clickhouse; return rawQuery( @@ -36,10 +34,10 @@ function clickhouseQuery(websites: string[], startAt: Date) { event_name from event where event_type = ${EVENT_TYPE.customEvent} - and ${websites && websites.length > 0 ? `website_id in {websites:Array(UUID)}` : '0 = 0'} + and website_id = {websiteId:UUID} and created_at >= {startAt:DateTime('UTC')}`, { - websites, + websiteId, startAt, }, ); diff --git a/queries/analytics/pageview/getPageviews.ts b/queries/analytics/pageview/getPageviews.ts index eb60a1f528..c37f23c1a1 100644 --- a/queries/analytics/pageview/getPageviews.ts +++ b/queries/analytics/pageview/getPageviews.ts @@ -3,19 +3,17 @@ import clickhouse from 'lib/clickhouse'; import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db'; import { EVENT_TYPE } from 'lib/constants'; -export async function getPageviews(...args: [websites: string[], startAt: Date]) { +export async function getPageviews(...args: [websiteId: string, startAt: Date]) { return runQuery({ [PRISMA]: () => relationalQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args), }); } -async function relationalQuery(websites: string[], startAt: Date) { - return prisma.client.pageview.findMany({ +async function relationalQuery(websiteId: string, startAt: Date) { + return prisma.client.websiteEvent.findMany({ where: { - websiteId: { - in: websites, - }, + websiteId, createdAt: { gte: startAt, }, @@ -23,21 +21,21 @@ async function relationalQuery(websites: string[], startAt: Date) { }); } -async function clickhouseQuery(websites: string[], startAt: Date) { +async function clickhouseQuery(websiteId: string, startAt: Date) { const { rawQuery } = clickhouse; return rawQuery( `select - website_id, - session_id, - created_at, + website_id as websiteId, + session_id as sessionId, + created_at as createdAt, url from event where event_type = ${EVENT_TYPE.pageView} - and ${websites && websites.length > 0 ? `website_id in {websites:Array(UUID)}` : '0 = 0'} + and website_id = {websiteId:UUID} and created_at >= {startAt:DateTime('UTC')}`, { - websites, + websiteId, startAt, }, ); diff --git a/queries/analytics/session/getSessions.ts b/queries/analytics/session/getSessions.ts index 460572778b..1f28333b97 100644 --- a/queries/analytics/session/getSessions.ts +++ b/queries/analytics/session/getSessions.ts @@ -2,23 +2,17 @@ import prisma from 'lib/prisma'; import clickhouse from 'lib/clickhouse'; import { runQuery, PRISMA, CLICKHOUSE } from 'lib/db'; -export async function getSessions(...args: [websites: string[], startAt: Date]) { +export async function getSessions(...args: [websiteId: string, startAt: Date]) { return runQuery({ [PRISMA]: () => relationalQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args), }); } -async function relationalQuery(websites: string[], startAt: Date) { +async function relationalQuery(websiteId: string, startAt: Date) { return prisma.client.session.findMany({ where: { - ...(websites && websites.length > 0 - ? { - websiteId: { - in: websites, - }, - } - : {}), + websiteId, createdAt: { gte: startAt, }, @@ -26,7 +20,7 @@ async function relationalQuery(websites: string[], startAt: Date) { }); } -async function clickhouseQuery(websites: string[], startAt: Date) { +async function clickhouseQuery(websiteId: string, startAt: Date) { const { rawQuery } = clickhouse; return rawQuery( @@ -42,10 +36,10 @@ async function clickhouseQuery(websites: string[], startAt: Date) { language, country from event - where ${websites && websites.length > 0 ? `website_id in {websites:Array(UUID)}` : '0 = 0'} + where website_id = {websiteId:UUID} and created_at >= {startAt:DateTime('UTC')}`, { - websites, + websiteId, startAt, }, ); diff --git a/queries/analytics/stats/getRealtimeData.ts b/queries/analytics/stats/getRealtimeData.ts index 659f61455d..46cb674625 100644 --- a/queries/analytics/stats/getRealtimeData.ts +++ b/queries/analytics/stats/getRealtimeData.ts @@ -2,11 +2,11 @@ import { getPageviews } from '../pageview/getPageviews'; import { getSessions } from '../session/getSessions'; import { getEvents } from '../event/getEvents'; -export async function getRealtimeData(websites, time) { +export async function getRealtimeData(websiteId, time) { const [pageviews, sessions, events] = await Promise.all([ - getPageviews(websites, time), - getSessions(websites, time), - getEvents(websites, time), + getPageviews(websiteId, time), + getSessions(websiteId, time), + getEvents(websiteId, time), ]); return {