Fixed realtime chart rendering.

This commit is contained in:
Mike Cao 2023-12-09 20:55:50 -08:00
parent 7a5f28870f
commit 92a513e4d0
5 changed files with 49 additions and 26 deletions

View File

@ -19,9 +19,9 @@ import { RealtimeData } from 'lib/types';
import styles from './Realtime.module.css'; import styles from './Realtime.module.css';
function mergeData(state = [], data = [], time: number) { function mergeData(state = [], data = [], time: number) {
const ids = state.map(({ __id }) => __id); const ids = state.map(({ id }) => id);
return state return state
.concat(data.filter(({ __id }) => !ids.includes(__id))) .concat(data.filter(({ id }) => !ids.includes(id)))
.filter(({ timestamp }) => timestamp >= time); .filter(({ timestamp }) => timestamp >= time);
} }
@ -38,21 +38,18 @@ export function Realtime({ websiteId }) {
useEffect(() => { useEffect(() => {
if (data) { if (data) {
if (!currentData) { const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE);
setCurrentData(data); const time = date.getTime();
} else { const { pageviews, sessions, events, timestamp } = data;
const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE);
const time = date.getTime();
setCurrentData(state => ({ setCurrentData(state => ({
pageviews: mergeData(state?.pageviews, data.pageviews, time), pageviews: mergeData(state?.pageviews, pageviews, time),
sessions: mergeData(state?.sessions, data.sessions, time), sessions: mergeData(state?.sessions, sessions, time),
events: mergeData(state?.events, data.events, time), events: mergeData(state?.events, events, time),
timestamp: data.timestamp, timestamp,
})); }));
}
} }
}, [data]); }, [data, currentData]);
const realtimeData: RealtimeData = useMemo(() => { const realtimeData: RealtimeData = useMemo(() => {
if (!currentData) { if (!currentData) {

View File

@ -1,4 +1,4 @@
import { subMinutes } from 'date-fns'; import { startOfMinute, subMinutes } from 'date-fns';
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useAuth, useValidate } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, RealtimeInit } from 'lib/types'; import { NextApiRequestQueryBody, RealtimeInit } from 'lib/types';
@ -6,6 +6,8 @@ import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getRealtimeData } from 'queries'; import { getRealtimeData } from 'queries';
import * as yup from 'yup'; import * as yup from 'yup';
import { REALTIME_RANGE } from 'lib/constants';
export interface RealtimeRequestQuery { export interface RealtimeRequestQuery {
id: string; id: string;
startAt: number; startAt: number;
@ -32,7 +34,7 @@ export default async (
return unauthorized(res); return unauthorized(res);
} }
let startTime = subMinutes(new Date(), 30); let startTime = subMinutes(startOfMinute(new Date()), REALTIME_RANGE);
if (+startAt > startTime.getTime()) { if (+startAt > startTime.getTime()) {
startTime = new Date(+startAt); startTime = new Date(+startAt);

View File

@ -18,6 +18,9 @@ function relationalQuery(websiteId: string, startDate: Date, eventType: number)
gte: startDate, gte: startDate,
}, },
}, },
orderBy: {
createdAt: 'asc',
},
}); });
} }
@ -39,6 +42,7 @@ function clickhouseQuery(websiteId: string, startDate: Date, eventType: number)
where website_id = {websiteId:UUID} where website_id = {websiteId:UUID}
and created_at >= {startDate:DateTime64} and created_at >= {startDate:DateTime64}
and event_type = {eventType:UInt32} and event_type = {eventType:UInt32}
order by created_at asc
`, `,
{ {
websiteId, websiteId,

View File

@ -1,4 +1,3 @@
import { md5 } from 'next-basics';
import { getSessions, getEvents } from 'queries/index'; import { getSessions, getEvents } from 'queries/index';
import { EVENT_TYPE } from 'lib/constants'; import { EVENT_TYPE } from 'lib/constants';
@ -9,18 +8,35 @@ export async function getRealtimeData(websiteId: string, startDate: Date) {
getEvents(websiteId, startDate, EVENT_TYPE.customEvent), getEvents(websiteId, startDate, EVENT_TYPE.customEvent),
]); ]);
const decorate = (id: string, data: any[]) => { const decorate = (type: string, data: any[]) => {
return data.map((props: { [key: string]: any }) => ({ return data.map((values: { [key: string]: any }) => ({
...props, ...values,
__id: md5(id, ...Object.values(props)), __type: type,
__type: id, timestamp: values.timestamp ? values.timestamp * 1000 : new Date(values.createdAt).getTime(),
timestamp: props.timestamp ? props.timestamp * 1000 : new Date(props.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 { return {
pageviews: decorate('pageview', pageviews), pageviews: decorate('pageview', pageviews),
sessions: decorate('session', sessions), sessions: uniques('session', sessions),
events: decorate('event', events), events: decorate('event', events),
timestamp: Date.now(), timestamp: Date.now(),
}; };

View File

@ -17,6 +17,9 @@ async function relationalQuery(websiteId: string, startDate: Date) {
gte: startDate, gte: startDate,
}, },
}, },
orderBy: {
createdAt: 'asc',
},
}); });
} }
@ -25,7 +28,7 @@ async function clickhouseQuery(websiteId: string, startDate: Date) {
return rawQuery( return rawQuery(
` `
select distinct select
session_id as id, session_id as id,
website_id as websiteId, website_id as websiteId,
created_at as createdAt, created_at as createdAt,
@ -43,6 +46,7 @@ async function clickhouseQuery(websiteId: string, startDate: Date) {
from website_event from website_event
where website_id = {websiteId:UUID} where website_id = {websiteId:UUID}
and created_at >= {startDate:DateTime64} and created_at >= {startDate:DateTime64}
order by created_at asc
`, `,
{ {
websiteId, websiteId,