diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.tsx b/src/app/(main)/websites/[id]/realtime/Realtime.tsx index e77af289..34e29281 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.tsx +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -19,9 +19,9 @@ import { RealtimeData } from 'lib/types'; import styles from './Realtime.module.css'; function mergeData(state = [], data = [], time: number) { - const ids = state.map(({ __id }) => __id); + const ids = state.map(({ id }) => id); return state - .concat(data.filter(({ __id }) => !ids.includes(__id))) + .concat(data.filter(({ id }) => !ids.includes(id))) .filter(({ timestamp }) => timestamp >= time); } @@ -38,21 +38,18 @@ export function Realtime({ websiteId }) { useEffect(() => { if (data) { - if (!currentData) { - setCurrentData(data); - } else { - const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE); - const time = date.getTime(); + const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE); + const time = date.getTime(); + const { pageviews, sessions, events, timestamp } = data; - setCurrentData(state => ({ - pageviews: mergeData(state?.pageviews, data.pageviews, time), - sessions: mergeData(state?.sessions, data.sessions, time), - events: mergeData(state?.events, data.events, time), - timestamp: data.timestamp, - })); - } + setCurrentData(state => ({ + pageviews: mergeData(state?.pageviews, pageviews, time), + sessions: mergeData(state?.sessions, sessions, time), + events: mergeData(state?.events, events, time), + timestamp, + })); } - }, [data]); + }, [data, currentData]); const realtimeData: RealtimeData = useMemo(() => { if (!currentData) { diff --git a/src/pages/api/realtime/[id].ts b/src/pages/api/realtime/[id].ts index 212d4a0f..0edd589d 100644 --- a/src/pages/api/realtime/[id].ts +++ b/src/pages/api/realtime/[id].ts @@ -1,4 +1,4 @@ -import { subMinutes } from 'date-fns'; +import { startOfMinute, subMinutes } from 'date-fns'; import { canViewWebsite } from 'lib/auth'; import { useAuth, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, RealtimeInit } from 'lib/types'; @@ -6,6 +6,8 @@ import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getRealtimeData } from 'queries'; import * as yup from 'yup'; +import { REALTIME_RANGE } from 'lib/constants'; + export interface RealtimeRequestQuery { id: string; startAt: number; @@ -32,7 +34,7 @@ export default async ( return unauthorized(res); } - let startTime = subMinutes(new Date(), 30); + let startTime = subMinutes(startOfMinute(new Date()), REALTIME_RANGE); if (+startAt > startTime.getTime()) { startTime = new Date(+startAt); diff --git a/src/queries/analytics/events/getEvents.ts b/src/queries/analytics/events/getEvents.ts index fe074ec2..9ef27973 100644 --- a/src/queries/analytics/events/getEvents.ts +++ b/src/queries/analytics/events/getEvents.ts @@ -18,6 +18,9 @@ function relationalQuery(websiteId: string, startDate: Date, eventType: number) gte: startDate, }, }, + orderBy: { + createdAt: 'asc', + }, }); } @@ -39,6 +42,7 @@ function clickhouseQuery(websiteId: string, startDate: Date, eventType: number) where website_id = {websiteId:UUID} and created_at >= {startDate:DateTime64} and event_type = {eventType:UInt32} + order by created_at asc `, { websiteId, diff --git a/src/queries/analytics/getRealtimeData.ts b/src/queries/analytics/getRealtimeData.ts index 337e7475..868a5c70 100644 --- a/src/queries/analytics/getRealtimeData.ts +++ b/src/queries/analytics/getRealtimeData.ts @@ -1,4 +1,3 @@ -import { md5 } from 'next-basics'; import { getSessions, getEvents } from 'queries/index'; import { EVENT_TYPE } from 'lib/constants'; @@ -9,18 +8,35 @@ export async function getRealtimeData(websiteId: string, startDate: Date) { getEvents(websiteId, startDate, EVENT_TYPE.customEvent), ]); - const decorate = (id: string, data: any[]) => { - return data.map((props: { [key: string]: any }) => ({ - ...props, - __id: md5(id, ...Object.values(props)), - __type: id, - timestamp: props.timestamp ? props.timestamp * 1000 : new Date(props.createdAt).getTime(), + const decorate = (type: string, data: any[]) => { + return data.map((values: { [key: string]: any }) => ({ + ...values, + __type: type, + timestamp: values.timestamp ? values.timestamp * 1000 : new Date(values.createdAt).getTime(), })); }; + const set = new Set(); + const uniques = (type: string, data: any[]) => { + return data.reduce((arr, values: { [key: string]: any }) => { + if (!set.has(values.id)) { + set.add(values.id); + + return arr.concat({ + ...values, + __type: type, + timestamp: values.timestamp + ? values.timestamp * 1000 + : new Date(values.createdAt).getTime(), + }); + } + return arr; + }, []); + }; + return { pageviews: decorate('pageview', pageviews), - sessions: decorate('session', sessions), + sessions: uniques('session', sessions), events: decorate('event', events), timestamp: Date.now(), }; diff --git a/src/queries/analytics/sessions/getSessions.ts b/src/queries/analytics/sessions/getSessions.ts index d67edd5e..b92e3af9 100644 --- a/src/queries/analytics/sessions/getSessions.ts +++ b/src/queries/analytics/sessions/getSessions.ts @@ -17,6 +17,9 @@ async function relationalQuery(websiteId: string, startDate: Date) { gte: startDate, }, }, + orderBy: { + createdAt: 'asc', + }, }); } @@ -25,7 +28,7 @@ async function clickhouseQuery(websiteId: string, startDate: Date) { return rawQuery( ` - select distinct + select session_id as id, website_id as websiteId, created_at as createdAt, @@ -43,6 +46,7 @@ async function clickhouseQuery(websiteId: string, startDate: Date) { from website_event where website_id = {websiteId:UUID} and created_at >= {startDate:DateTime64} + order by created_at asc `, { websiteId,