Convert realtime components to TS.

This commit is contained in:
Mike Cao 2023-12-09 00:35:54 -08:00
parent e67282d7d8
commit 8d31f43f0f
12 changed files with 75 additions and 51 deletions

View File

@ -6,7 +6,7 @@ import { EventDataMetricsBar } from './EventDataMetricsBar';
import { useDateRange, useApi, useNavigation } from 'components/hooks'; import { useDateRange, useApi, useNavigation } from 'components/hooks';
import styles from './WebsiteEventData.module.css'; import styles from './WebsiteEventData.module.css';
function useData(websiteId, event) { function useData(websiteId: string, event: string) {
const [dateRange] = useDateRange(websiteId); const [dateRange] = useDateRange(websiteId);
const { startDate, endDate } = dateRange; const { startDate, endDate } = dateRange;
const { get, useQuery } = useApi(); const { get, useQuery } = useApi();

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import { useMemo, useState, useEffect } from 'react'; import { useMemo, useState, useEffect } from 'react';
import { subMinutes, startOfMinute } from 'date-fns'; import { subMinutes, startOfMinute } from 'date-fns';
import firstBy from 'thenby'; import thenby from 'thenby';
import { Grid, GridRow } from 'components/layout/Grid'; import { Grid, GridRow } from 'components/layout/Grid';
import Page from 'components/layout/Page'; import Page from 'components/layout/Page';
import RealtimeChart from 'components/metrics/RealtimeChart'; import RealtimeChart from 'components/metrics/RealtimeChart';
@ -15,9 +15,10 @@ import useApi from 'components/hooks/useApi';
import { percentFilter } from 'lib/filters'; import { percentFilter } from 'lib/filters';
import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants'; import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants';
import { useWebsite } from 'components/hooks'; import { useWebsite } from 'components/hooks';
import { RealtimeData } from 'lib/types';
import styles from './Realtime.module.css'; import styles from './Realtime.module.css';
function mergeData(state = [], data = [], time) { 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)))
@ -25,7 +26,7 @@ function mergeData(state = [], data = [], time) {
} }
export function Realtime({ websiteId }) { export function Realtime({ websiteId }) {
const [currentData, setCurrentData] = useState(); const [currentData, setCurrentData] = useState<RealtimeData>();
const { get, useQuery } = useApi(); const { get, useQuery } = useApi();
const { data: website } = useWebsite(websiteId); const { data: website } = useWebsite(websiteId);
const { data, isLoading, error } = useQuery({ const { data, isLoading, error } = useQuery({
@ -33,7 +34,6 @@ export function Realtime({ websiteId }) {
queryFn: () => get(`/realtime/${websiteId}`, { startAt: currentData?.timestamp || 0 }), queryFn: () => get(`/realtime/${websiteId}`, { startAt: currentData?.timestamp || 0 }),
enabled: !!(websiteId && website), enabled: !!(websiteId && website),
refetchInterval: REALTIME_INTERVAL, refetchInterval: REALTIME_INTERVAL,
cache: false,
}); });
useEffect(() => { useEffect(() => {
@ -50,9 +50,9 @@ export function Realtime({ websiteId }) {
} }
}, [data]); }, [data]);
const realtimeData = useMemo(() => { const realtimeData: RealtimeData = useMemo(() => {
if (!currentData) { if (!currentData) {
return { pageviews: [], sessions: [], events: [], countries: [], visitors: [] }; return { pageviews: [], sessions: [], events: [], countries: [], visitors: [], timestamp: 0 };
} }
currentData.countries = percentFilter( currentData.countries = percentFilter(
@ -75,7 +75,7 @@ export function Realtime({ websiteId }) {
} }
return arr; return arr;
}, []) }, [])
.sort(firstBy('y', -1)), .sort(thenby.firstBy('y', -1)),
); );
currentData.visitors = currentData.sessions.reduce((arr, val) => { currentData.visitors = currentData.sessions.reduce((arr, val) => {
@ -89,18 +89,18 @@ export function Realtime({ websiteId }) {
}, [currentData]); }, [currentData]);
if (isLoading || error) { if (isLoading || error) {
return <Page loading={isLoading} error={error} />; return <Page isLoading={isLoading} error={error} />;
} }
return ( return (
<> <>
<WebsiteHeader websiteId={websiteId} /> <WebsiteHeader websiteId={websiteId} />
<RealtimeHeader websiteId={websiteId} data={currentData} /> <RealtimeHeader data={currentData} />
<RealtimeChart className={styles.chart} data={realtimeData} unit="minute" /> <RealtimeChart className={styles.chart} data={realtimeData} unit="minute" />
<Grid> <Grid>
<GridRow columns="one-two"> <GridRow columns="one-two">
<RealtimeUrls websiteId={websiteId} websiteDomain={website?.domain} data={realtimeData} /> <RealtimeUrls websiteDomain={website?.domain} data={realtimeData} />
<RealtimeLog websiteId={websiteId} websiteDomain={website?.domain} data={realtimeData} /> <RealtimeLog websiteDomain={website?.domain} data={realtimeData} />
</GridRow> </GridRow>
<GridRow columns="one-two"> <GridRow columns="one-two">
<RealtimeCountries data={realtimeData?.countries} /> <RealtimeCountries data={realtimeData?.countries} />

View File

@ -1,10 +1,11 @@
import MetricCard from 'components/metrics/MetricCard'; import MetricCard from 'components/metrics/MetricCard';
import useMessages from 'components/hooks/useMessages'; import useMessages from 'components/hooks/useMessages';
import { RealtimeData } from 'lib/types';
import styles from './RealtimeHeader.module.css'; import styles from './RealtimeHeader.module.css';
export function RealtimeHeader({ data = {} }) { export function RealtimeHeader({ data }: { data: RealtimeData }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { pageviews, visitors, events, countries } = data; const { pageviews, visitors, events, countries } = data || {};
return ( return (
<div className={styles.header}> <div className={styles.header}>

View File

@ -1,7 +1,7 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { StatusLight, Icon, Text } from 'react-basics'; import { StatusLight, Icon, Text } from 'react-basics';
import { FixedSizeList } from 'react-window'; import { FixedSizeList } from 'react-window';
import firstBy from 'thenby'; import thenby from 'thenby';
import FilterButtons from 'components/common/FilterButtons'; import FilterButtons from 'components/common/FilterButtons';
import Empty from 'components/common/Empty'; import Empty from 'components/common/Empty';
import useLocale from 'components/hooks/useLocale'; import useLocale from 'components/hooks/useLocale';
@ -130,7 +130,7 @@ export function RealtimeLog({ data, websiteDomain }) {
} }
const { pageviews, visitors, events } = data; const { pageviews, visitors, events } = data;
const logs = [...pageviews, ...visitors, ...events].sort(firstBy('createdAt', -1)); const logs = [...pageviews, ...visitors, ...events].sort(thenby.firstBy('createdAt', -1));
if (filter !== TYPE_ALL) { if (filter !== TYPE_ALL) {
return logs.filter(({ __type }) => __type === filter); return logs.filter(({ __type }) => __type === filter);

View File

@ -1,15 +1,22 @@
import { useMemo, useState } from 'react'; import { Key, useMemo, useState } from 'react';
import { ButtonGroup, Button, Flexbox } from 'react-basics'; import { ButtonGroup, Button, Flexbox } from 'react-basics';
import firstBy from 'thenby'; import thenby from 'thenby';
import { percentFilter } from 'lib/filters'; import { percentFilter } from 'lib/filters';
import ListTable from 'components/metrics/ListTable'; import ListTable from 'components/metrics/ListTable';
import { FILTER_PAGES, FILTER_REFERRERS } from 'lib/constants'; import { FILTER_PAGES, FILTER_REFERRERS } from 'lib/constants';
import useMessages from 'components/hooks/useMessages'; import useMessages from 'components/hooks/useMessages';
import { RealtimeData } from 'lib/types';
export function RealtimeUrls({ websiteDomain, data = {} }) { export function RealtimeUrls({
websiteDomain,
data,
}: {
websiteDomain: string;
data: RealtimeData;
}) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { pageviews } = data; const { pageviews } = data || {};
const [filter, setFilter] = useState(FILTER_REFERRERS); const [filter, setFilter] = useState<Key>(FILTER_REFERRERS);
const limit = 15; const limit = 15;
const buttons = [ const buttons = [
@ -48,7 +55,7 @@ export function RealtimeUrls({ websiteDomain, data = {} }) {
} }
return arr; return arr;
}, []) }, [])
.sort(firstBy('y', -1)) .sort(thenby.firstBy('y', -1))
.slice(0, limit), .slice(0, limit),
); );
@ -64,7 +71,7 @@ export function RealtimeUrls({ websiteDomain, data = {} }) {
} }
return arr; return arr;
}, []) }, [])
.sort(firstBy('y', -1)) .sort(thenby.firstBy('y', -1))
.slice(0, limit), .slice(0, limit),
); );

View File

@ -1,9 +1,10 @@
import { getMinimumUnit, parseDateRange } from 'lib/date'; import { getMinimumUnit, parseDateRange } from 'lib/date';
import { setItem } from 'next-basics'; import { setItem } from 'next-basics';
import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE } from 'lib/constants'; import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE } from 'lib/constants';
import useLocale from './useLocale';
import websiteStore, { setWebsiteDateRange } from 'store/websites'; import websiteStore, { setWebsiteDateRange } from 'store/websites';
import appStore, { setDateRange } from 'store/app'; import appStore, { setDateRange } from 'store/app';
import { DateRange } from 'lib/types';
import useLocale from './useLocale';
import useApi from './useApi'; import useApi from './useApi';
export function useDateRange(websiteId?: string) { export function useDateRange(websiteId?: string) {
@ -14,9 +15,9 @@ export function useDateRange(websiteId?: string) {
const globalConfig = appStore(state => state.dateRange); const globalConfig = appStore(state => state.dateRange);
const dateRange = parseDateRange(websiteConfig || globalConfig || defaultConfig, locale); const dateRange = parseDateRange(websiteConfig || globalConfig || defaultConfig, locale);
const saveDateRange = async value => { const saveDateRange = async (value: DateRange | string) => {
if (websiteId) { if (websiteId) {
let dateRange = value; let dateRange: DateRange | string = value;
if (typeof value === 'string') { if (typeof value === 'string') {
if (value === 'all') { if (value === 'all') {
@ -37,14 +38,17 @@ export function useDateRange(websiteId?: string) {
} }
} }
setWebsiteDateRange(websiteId, dateRange); setWebsiteDateRange(websiteId, dateRange as DateRange);
} else { } else {
setItem(DATE_RANGE_CONFIG, value); setItem(DATE_RANGE_CONFIG, value);
setDateRange(value); setDateRange(value);
} }
}; };
return [dateRange, saveDateRange]; return [dateRange, saveDateRange] as [
{ startDate: Date; endDate: Date },
(value: string | DateRange) => void,
];
} }
export default useDateRange; export default useDateRange;

View File

@ -3,6 +3,7 @@ import { format, startOfMinute, subMinutes, isBefore } from 'date-fns';
import PageviewsChart from './PageviewsChart'; import PageviewsChart from './PageviewsChart';
import { getDateArray } from 'lib/date'; import { getDateArray } from 'lib/date';
import { DEFAULT_ANIMATION_DURATION, REALTIME_RANGE } from 'lib/constants'; import { DEFAULT_ANIMATION_DURATION, REALTIME_RANGE } from 'lib/constants';
import { RealtimeData } from 'lib/types';
function mapData(data: any[]) { function mapData(data: any[]) {
let last = 0; let last = 0;
@ -24,11 +25,9 @@ function mapData(data: any[]) {
} }
export interface RealtimeChartProps { export interface RealtimeChartProps {
data: { data: RealtimeData;
pageviews: any[];
visitors: any[];
};
unit: string; unit: string;
className?: string;
} }
export function RealtimeChart({ data, unit, ...props }: RealtimeChartProps) { export function RealtimeChart({ data, unit, ...props }: RealtimeChartProps) {

View File

@ -32,6 +32,7 @@ import {
subWeeks, subWeeks,
} from 'date-fns'; } from 'date-fns';
import { getDateLocale } from 'lib/lang'; import { getDateLocale } from 'lib/lang';
import { DateRange } from 'lib/types';
export const TIME_UNIT = { export const TIME_UNIT = {
minute: 'minute', minute: 'minute',
@ -54,13 +55,13 @@ export function getTimezone() {
return moment.tz.guess(); return moment.tz.guess();
} }
export function getLocalTime(t) { export function getLocalTime(t: string | number | Date) {
return addMinutes(new Date(t), new Date().getTimezoneOffset()); return addMinutes(new Date(t), new Date().getTimezoneOffset());
} }
export function parseDateRange(value, locale = 'en-US') { export function parseDateRange(value: string | object, locale = 'en-US'): DateRange {
if (typeof value === 'object') { if (typeof value === 'object') {
return value; return value as DateRange;
} }
if (value === 'all') { if (value === 'all') {
@ -93,7 +94,7 @@ export function parseDateRange(value, locale = 'en-US') {
if (!match) return null; if (!match) return null;
const { num, unit } = match.groups; const { num, unit } = match.groups;
const selectedUnit = { num, unit }; const selectedUnit = { num: +num, unit };
if (+num === 1) { if (+num === 1) {
switch (unit) { switch (unit) {
@ -172,7 +173,7 @@ export function parseDateRange(value, locale = 'en-US') {
switch (unit) { switch (unit) {
case 'day': case 'day':
return { return {
startDate: subDays(startOfDay(now), num - 1), startDate: subDays(startOfDay(now), +num - 1),
endDate: endOfDay(now), endDate: endOfDay(now),
unit, unit,
value, value,
@ -180,7 +181,7 @@ export function parseDateRange(value, locale = 'en-US') {
}; };
case 'hour': case 'hour':
return { return {
startDate: subHours(startOfHour(now), num - 1), startDate: subHours(startOfHour(now), +num - 1),
endDate: endOfHour(now), endDate: endOfHour(now),
unit, unit,
value, value,
@ -189,7 +190,10 @@ export function parseDateRange(value, locale = 'en-US') {
} }
} }
export function incrementDateRange(value, increment) { export function incrementDateRange(
value: { startDate: any; endDate: any; selectedUnit: any },
increment: number,
) {
const { startDate, endDate, selectedUnit } = value; const { startDate, endDate, selectedUnit } = value;
const { num, unit } = selectedUnit; const { num, unit } = selectedUnit;
@ -235,7 +239,7 @@ export function incrementDateRange(value, increment) {
} }
} }
export function getAllowedUnits(startDate, endDate) { export function getAllowedUnits(startDate: Date, endDate: Date) {
const units = ['minute', 'hour', 'day', 'month', 'year']; const units = ['minute', 'hour', 'day', 'month', 'year'];
const minUnit = getMinimumUnit(startDate, endDate); const minUnit = getMinimumUnit(startDate, endDate);
const index = units.indexOf(minUnit === 'year' ? 'month' : minUnit); const index = units.indexOf(minUnit === 'year' ? 'month' : minUnit);
@ -243,7 +247,7 @@ export function getAllowedUnits(startDate, endDate) {
return index >= 0 ? units.splice(index) : []; return index >= 0 ? units.splice(index) : [];
} }
export function getMinimumUnit(startDate, endDate) { export function getMinimumUnit(startDate: number | Date, endDate: number | Date) {
if (differenceInMinutes(endDate, startDate) <= 60) { if (differenceInMinutes(endDate, startDate) <= 60) {
return 'minute'; return 'minute';
} else if (differenceInHours(endDate, startDate) <= 48) { } else if (differenceInHours(endDate, startDate) <= 48) {
@ -257,25 +261,25 @@ export function getMinimumUnit(startDate, endDate) {
return 'year'; return 'year';
} }
export function getDateFromString(str) { export function getDateFromString(str: string) {
const [ymd, hms] = str.split(' '); const [ymd, hms] = str.split(' ');
const [year, month, day] = ymd.split('-'); const [year, month, day] = ymd.split('-');
if (hms) { if (hms) {
const [hour, min, sec] = hms.split(':'); const [hour, min, sec] = hms.split(':');
return new Date(year, month - 1, day, hour, min, sec); return new Date(+year, +month - 1, +day, +hour, +min, +sec);
} }
return new Date(year, month - 1, day); return new Date(+year, +month - 1, +day);
} }
export function getDateArray(data, startDate, endDate, unit) { export function getDateArray(data: any[], startDate: Date, endDate: Date, unit: string) {
const arr = []; const arr = [];
const [diff, add, normalize] = dateFuncs[unit]; const [diff, add, normalize] = dateFuncs[unit];
const n = diff(endDate, startDate) + 1; const n = diff(endDate, startDate) + 1;
function findData(date) { function findData(date: Date) {
const d = data.find(({ x }) => { const d = data.find(({ x }) => {
return normalize(getDateFromString(x)).getTime() === date.getTime(); return normalize(getDateFromString(x)).getTime() === date.getTime();
}); });
@ -293,7 +297,7 @@ export function getDateArray(data, startDate, endDate, unit) {
return arr; return arr;
} }
export function getDateLength(startDate, endDate, unit) { export function getDateLength(startDate: Date, endDate: Date, unit: string | number) {
const [diff] = dateFuncs[unit]; const [diff] = dateFuncs[unit];
return diff(endDate, startDate) + 1; return diff(endDate, startDate) + 1;
} }
@ -310,7 +314,7 @@ export const CUSTOM_FORMATS = {
}, },
}; };
export function formatDate(date, str, locale = 'en-US') { export function formatDate(date: string | number | Date, str: string, locale = 'en-US') {
return format( return format(
typeof date === 'string' ? new Date(date) : date, typeof date === 'string' ? new Date(date) : date,
CUSTOM_FORMATS?.[locale]?.[str] || str, CUSTOM_FORMATS?.[locale]?.[str] || str,
@ -320,10 +324,10 @@ export function formatDate(date, str, locale = 'en-US') {
); );
} }
export function maxDate(...args) { export function maxDate(...args: Date[]) {
return max(args.filter(n => isDate(n))); return max(args.filter(n => isDate(n)));
} }
export function minDate(...args) { export function minDate(...args: any[]) {
return min(args.filter(n => isDate(n))); return min(args.filter(n => isDate(n)));
} }

View File

@ -180,7 +180,7 @@ export interface DateRange {
endDate: Date; endDate: Date;
value: string; value: string;
unit?: TimeUnit; unit?: TimeUnit;
selectedUnit?: TimeUnit; selectedUnit?: { num: number; unit: TimeUnit };
} }
export interface QueryFilters { export interface QueryFilters {
@ -207,3 +207,12 @@ export interface QueryOptions {
joinSession?: boolean; joinSession?: boolean;
columns?: { [key: string]: string }; columns?: { [key: string]: string };
} }
export interface RealtimeData {
pageviews: any[];
sessions: any[];
events: any[];
timestamp: number;
countries?: any[];
visitors?: any[];
}