Refactored functionality for all time date range.

This commit is contained in:
Mike Cao 2023-07-26 13:42:55 -07:00
parent c3973f5fb5
commit d06808985b
11 changed files with 91 additions and 33 deletions

View File

@ -10,13 +10,9 @@ export function RefreshButton({ websiteId, isLoading }) {
function handleClick() { function handleClick() {
if (!isLoading && dateRange) { if (!isLoading && dateRange) {
if (/^\d+/.test(dateRange.value)) {
setWebsiteDateRange(websiteId, dateRange.value);
} else {
setWebsiteDateRange(websiteId, dateRange); setWebsiteDateRange(websiteId, dateRange);
} }
} }
}
return ( return (
<TooltipPopup label={formatMessage(labels.refresh)}> <TooltipPopup label={formatMessage(labels.refresh)}>

View File

@ -1,7 +1,7 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import PageviewsChart from 'components/metrics/PageviewsChart'; import PageviewsChart from 'components/metrics/PageviewsChart';
import { useApi, useDateRange, useTimezone, usePageQuery } from 'hooks'; import { useApi, useDateRange, useTimezone, usePageQuery } from 'hooks';
import { getDateArray, getDateLength } from 'lib/date'; import { getDateArray } from 'lib/date';
export function WebsiteChart({ websiteId }) { export function WebsiteChart({ websiteId }) {
const [dateRange] = useDateRange(websiteId); const [dateRange] = useDateRange(websiteId);
@ -43,7 +43,7 @@ export function WebsiteChart({ websiteId }) {
}; };
} }
return { pageviews: [], sessions: [] }; return { pageviews: [], sessions: [] };
}, [data, startDate, endDate, unit, modified]); }, [data, startDate, endDate, unit]);
return <PageviewsChart websiteId={websiteId} data={chartData} unit={unit} loading={isLoading} />; return <PageviewsChart websiteId={websiteId} data={chartData} unit={unit} loading={isLoading} />;
} }

View File

@ -42,9 +42,9 @@ export function WebsiteHeader({ websiteId, showLinks = true, children }) {
<Column className={styles.title} variant="two"> <Column className={styles.title} variant="two">
<Favicon domain={domain} /> <Favicon domain={domain} />
<Text>{name}</Text> <Text>{name}</Text>
<ActiveUsers websiteId={websiteId} />
</Column> </Column>
<Column className={styles.actions} variant="two"> <Column className={styles.actions} variant="two">
<ActiveUsers websiteId={websiteId} />
{showLinks && ( {showLinks && (
<Flexbox alignItems="center"> <Flexbox alignItems="center">
{links.map(({ label, icon, path }) => { {links.map(({ label, icon, path }) => {

View File

@ -1,20 +1,43 @@
import { 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 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 useApi from './useApi';
export function useDateRange(websiteId) { export function useDateRange(websiteId) {
const { get } = useApi();
const { locale } = useLocale(); const { locale } = useLocale();
const websiteConfig = websiteStore(state => state[websiteId]?.dateRange); const websiteConfig = websiteStore(state => state[websiteId]?.dateRange);
const defaultConfig = DEFAULT_DATE_RANGE; const defaultConfig = DEFAULT_DATE_RANGE;
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 = value => { const saveDateRange = async value => {
if (websiteId) { if (websiteId) {
setWebsiteDateRange(websiteId, value); let dateRange = value;
if (typeof value === 'string') {
if (value === 'all') {
const result = await get(`/websites/${websiteId}/daterange`);
const { mindate, maxdate } = result;
const startDate = new Date(mindate);
const endDate = new Date(maxdate);
dateRange = {
startDate,
endDate,
unit: getMinimumUnit(startDate, endDate),
value,
};
} else {
dateRange = parseDateRange(value, locale);
}
}
setWebsiteDateRange(websiteId, dateRange);
} else { } else {
setItem(DATE_RANGE_CONFIG, value); setItem(DATE_RANGE_CONFIG, value);
setDateRange(value); setDateRange(value);

View File

@ -122,7 +122,7 @@ async function findUnique(data) {
throw `${data.length} records found when expecting 1.`; throw `${data.length} records found when expecting 1.`;
} }
return data[0] ?? null; return findFirst(data);
} }
async function findFirst(data) { async function findFirst(data) {

View File

@ -7,12 +7,15 @@ export async function parseDateRangeQuery(req: NextApiRequest) {
// All-time // All-time
if (+startAt === 0 && +endAt === 1) { if (+startAt === 0 && +endAt === 1) {
const { min, max } = await getWebsiteDateRange(websiteId as string); const result = await getWebsiteDateRange(websiteId as string);
const { min, max } = result[0];
const startDate = new Date(min);
const endDate = new Date(max);
return { return {
startDate: min, startDate,
endDate: max, endDate,
unit: getMinimumUnit(min, max), unit: getMinimumUnit(startDate, endDate),
}; };
} }

View File

@ -137,3 +137,10 @@ export interface RealtimeUpdate {
events: any[]; events: any[];
timestamp: number; timestamp: number;
} }
export interface DateRange {
startDate: Date;
endDate: Date;
unit: string;
value: string;
}

View File

@ -0,0 +1,32 @@
import { WebsiteActive, NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getWebsiteDateRange } from 'queries';
export interface WebsiteDateRangeRequestQuery {
id: string;
}
export default async (
req: NextApiRequestQueryBody<WebsiteDateRangeRequestQuery>,
res: NextApiResponse<WebsiteActive>,
) => {
await useCors(req, res);
await useAuth(req, res);
const { id: websiteId } = req.query;
if (req.method === 'GET') {
if (!(await canViewWebsite(req.auth, websiteId))) {
return unauthorized(res);
}
const result = await getWebsiteDateRange(websiteId);
return ok(res, result);
}
return methodNotAllowed(res);
};

View File

@ -73,6 +73,7 @@ export default async (
city, city,
}, },
}); });
const prevPeriod = await getWebsiteStats(websiteId, { const prevPeriod = await getWebsiteStats(websiteId, {
startDate: prevStartDate, startDate: prevStartDate,
endDate: prevEndDate, endDate: prevEndDate,

View File

@ -16,32 +16,36 @@ async function relationalQuery(websiteId: string) {
const { rawQuery } = prisma; const { rawQuery } = prisma;
const website = await loadWebsite(websiteId); const website = await loadWebsite(websiteId);
return rawQuery( const result = await rawQuery(
` `
select select
min(created_at) as min, min(created_at) as mindate,
max(created_at) as max max(created_at) as maxdate
from website_event from website_event
where website_id = {{websiteId::uuid}} where website_id = {{websiteId::uuid}}
and created_at >= {{startDate}} and created_at >= {{startDate}}
`, `,
{ websiteId, startDate: maxDate(new Date(DEFAULT_RESET_DATE), new Date(website.resetAt)) }, { websiteId, startDate: maxDate(new Date(DEFAULT_RESET_DATE), new Date(website.resetAt)) },
); );
return result[0] ?? null;
} }
async function clickhouseQuery(websiteId: string) { async function clickhouseQuery(websiteId: string) {
const { rawQuery } = clickhouse; const { rawQuery } = clickhouse;
const website = await loadWebsite(websiteId); const website = await loadWebsite(websiteId);
return rawQuery( const result = await rawQuery(
` `
select select
min(created_at) as min, min(created_at) as mindate,
max(created_at) as max max(created_at) as maxdate
from website_event from website_event
where website_id = {websiteId:UUID} where website_id = {websiteId:UUID}
and created_at >= {startDate:DateTime} and created_at >= {startDate:DateTime}
`, `,
{ websiteId, startDate: maxDate(new Date(DEFAULT_RESET_DATE), new Date(website.resetAt)) }, { websiteId, startDate: maxDate(new Date(DEFAULT_RESET_DATE), new Date(website.resetAt)) },
); );
return result[0] ?? null;
} }

View File

@ -1,28 +1,20 @@
import { create } from 'zustand'; import { create } from 'zustand';
import produce from 'immer'; import produce from 'immer';
import app from './app'; import { DateRange } from 'lib/types';
import { parseDateRange } from 'lib/date';
const store = create(() => ({})); const store = create(() => ({}));
export function getWebsiteDateRange(websiteId) { export function getWebsiteDateRange(websiteId: string) {
return store.getState()?.[websiteId]; return store.getState()?.[websiteId];
} }
export function setWebsiteDateRange(websiteId, value) { export function setWebsiteDateRange(websiteId: string, dateRange: DateRange) {
store.setState( store.setState(
produce(state => { produce(state => {
if (!state[websiteId]) { if (!state[websiteId]) {
state[websiteId] = {}; state[websiteId] = {};
} }
let dateRange = value;
if (typeof value === 'string') {
const { locale } = app.getState();
dateRange = parseDateRange(value, locale);
}
state[websiteId].dateRange = { ...dateRange, modified: Date.now() }; state[websiteId].dateRange = { ...dateRange, modified: Date.now() };
return state; return state;