Added parseDateRangeQuery function.

This commit is contained in:
Mike Cao 2023-07-26 09:55:54 -07:00
parent 09af33c77e
commit 1648707fc7
9 changed files with 48 additions and 80 deletions

View File

@ -62,10 +62,10 @@ export function parseDateRange(value, locale = 'en-US') {
} }
if (value?.startsWith?.('range')) { if (value?.startsWith?.('range')) {
const [, startAt, endAt] = value.split(':'); const [, startTime, endTime] = value.split(':');
const startDate = new Date(+startAt); const startDate = new Date(+startTime);
const endDate = new Date(+endAt); const endDate = new Date(+endTime);
return { return {
...getDateRangeValues(startDate, endDate), ...getDateRangeValues(startDate, endDate),

View File

@ -10,7 +10,6 @@ export async function parseDateRangeQuery(req: NextApiRequest) {
const { min, max } = await getWebsiteDateRange(websiteId as string); const { min, max } = await getWebsiteDateRange(websiteId as string);
return { return {
websiteId,
startDate: min, startDate: min,
endDate: max, endDate: max,
unit: getMinimumUnit(min, max), unit: getMinimumUnit(min, max),
@ -22,9 +21,8 @@ export async function parseDateRangeQuery(req: NextApiRequest) {
const minUnit = getMinimumUnit(startDate, endDate); const minUnit = getMinimumUnit(startDate, endDate);
return { return {
websiteId,
startDate, startDate,
endDate, endDate,
unit: getAllowedUnits(startDate, endDate).includes(unit as string) ? unit : minUnit, unit: (getAllowedUnits(startDate, endDate).includes(unit as string) ? unit : minUnit) as string,
}; };
} }

View File

@ -5,6 +5,7 @@ import moment from 'moment-timezone';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getEventMetrics } from 'queries'; import { getEventMetrics } from 'queries';
import { parseDateRangeQuery } from 'lib/query';
const unitTypes = ['year', 'month', 'hour', 'day']; const unitTypes = ['year', 'month', 'hour', 'day'];
@ -25,7 +26,8 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
const { id: websiteId, startAt, endAt, unit, timezone, url, eventName } = req.query; const { id: websiteId, timezone, url, eventName } = req.query;
const { startDate, endDate, unit } = await parseDateRangeQuery(req);
if (req.method === 'GET') { if (req.method === 'GET') {
if (!(await canViewWebsite(req.auth, websiteId))) { if (!(await canViewWebsite(req.auth, websiteId))) {
@ -35,8 +37,6 @@ export default async (
if (!moment.tz.zone(timezone) || !unitTypes.includes(unit)) { if (!moment.tz.zone(timezone) || !unitTypes.includes(unit)) {
return badRequest(res); return badRequest(res);
} }
const startDate = new Date(+startAt);
const endDate = new Date(+endAt);
const events = await getEventMetrics(websiteId, { const events = await getEventMetrics(websiteId, {
startDate, startDate,

View File

@ -3,9 +3,9 @@ import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { NextApiRequestQueryBody, WebsitePageviews } from 'lib/types'; import { NextApiRequestQueryBody, WebsitePageviews } from 'lib/types';
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { getAllowedUnits } from 'lib/date';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors } from 'lib/middleware';
import { getPageviewStats } from 'queries'; import { getPageviewStats } from 'queries';
import { parseDateRangeQuery } from 'lib/query';
export interface WebsitePageviewRequestQuery { export interface WebsitePageviewRequestQuery {
id: string; id: string;
@ -33,9 +33,6 @@ export default async (
const { const {
id: websiteId, id: websiteId,
startAt,
endAt,
unit,
timezone, timezone,
url, url,
referrer, referrer,
@ -53,10 +50,9 @@ export default async (
return unauthorized(res); return unauthorized(res);
} }
const startDate = new Date(+startAt); const { startDate, endDate, unit } = await parseDateRangeQuery(req);
const endDate = new Date(+endAt);
if (!moment.tz.zone(timezone) || !getAllowedUnits(unit).includes(unit)) { if (!moment.tz.zone(timezone)) {
return badRequest(res); return badRequest(res);
} }

View File

@ -1,8 +1,10 @@
import { addMinutes, differenceInMinutes } from 'date-fns';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors } from 'lib/middleware';
import { NextApiRequestQueryBody, WebsiteStats } from 'lib/types'; import { NextApiRequestQueryBody, WebsiteStats } from 'lib/types';
import { NextApiResponse } from 'next'; import { parseDateRangeQuery } from 'lib/query';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getWebsiteStats } from 'queries'; import { getWebsiteStats } from 'queries';
export interface WebsiteStatsRequestQuery { export interface WebsiteStatsRequestQuery {
@ -31,8 +33,6 @@ export default async (
const { const {
id: websiteId, id: websiteId,
startAt,
endAt,
url, url,
referrer, referrer,
title, title,
@ -51,12 +51,10 @@ export default async (
return unauthorized(res); return unauthorized(res);
} }
const startDate = new Date(+startAt); const { startDate, endDate } = await parseDateRangeQuery(req);
const endDate = new Date(+endAt); const diff = differenceInMinutes(endDate, startDate);
const prevStartDate = addMinutes(startDate, -diff);
const distance = endAt - startAt; const prevEndDate = addMinutes(endDate, -diff);
const prevStartDate = new Date(+startAt - distance);
const prevEndDate = new Date(+endAt - distance);
const metrics = await getWebsiteStats(websiteId, { const metrics = await getWebsiteStats(websiteId, {
startDate, startDate,

View File

@ -2,26 +2,26 @@ import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db'; import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma'; import prisma from 'lib/prisma';
export function getEvents(...args: [websiteId: string, startAt: Date, eventType: number]) { export function getEvents(...args: [websiteId: string, startDate: Date, eventType: number]) {
return runQuery({ return runQuery({
[PRISMA]: () => relationalQuery(...args), [PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args),
}); });
} }
function relationalQuery(websiteId: string, startAt: Date, eventType: number) { function relationalQuery(websiteId: string, startDate: Date, eventType: number) {
return prisma.client.websiteEvent.findMany({ return prisma.client.websiteEvent.findMany({
where: { where: {
websiteId, websiteId,
eventType, eventType,
createdAt: { createdAt: {
gte: startAt, gte: startDate,
}, },
}, },
}); });
} }
function clickhouseQuery(websiteId: string, startAt: Date, eventType: number) { function clickhouseQuery(websiteId: string, startDate: Date, eventType: number) {
const { rawQuery } = clickhouse; const { rawQuery } = clickhouse;
return rawQuery( return rawQuery(
@ -37,12 +37,12 @@ function clickhouseQuery(websiteId: string, startAt: Date, eventType: number) {
event_name as eventName event_name as eventName
from website_event from website_event
where website_id = {websiteId:UUID} where website_id = {websiteId:UUID}
and created_at >= {startAt:DateTime} and created_at >= {startDate:DateTime}
and event_type = {eventType:UInt32} and event_type = {eventType:UInt32}
`, `,
{ {
websiteId, websiteId,
startAt, startDate,
eventType, eventType,
}, },
); );

View File

@ -1,23 +1,22 @@
import clickhouse from 'lib/clickhouse'; import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db'; import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma'; import prisma from 'lib/prisma';
import { DEFAULT_RESET_DATE, EVENT_TYPE } from 'lib/constants'; import { EVENT_TYPE } from 'lib/constants';
import { loadWebsite } from 'lib/load'; import { loadWebsite } from 'lib/load';
import { maxDate } from 'lib/date'; import { maxDate } from 'lib/date';
export interface PageviewStatsCriteria {
startDate: Date;
endDate: Date;
timezone?: string;
unit?: string;
count?: string;
filters: object;
sessionKey?: string;
}
export async function getPageviewStats( export async function getPageviewStats(
...args: [ ...args: [websiteId: string, criteria: PageviewStatsCriteria]
websiteId: string,
criteria: {
startDate: Date;
endDate: Date;
timezone?: string;
unit?: string;
count?: string;
filters: object;
sessionKey?: string;
},
]
) { ) {
return runQuery({ return runQuery({
[PRISMA]: () => relationalQuery(...args), [PRISMA]: () => relationalQuery(...args),
@ -25,18 +24,7 @@ export async function getPageviewStats(
}); });
} }
async function relationalQuery( async function relationalQuery(websiteId: string, criteria: PageviewStatsCriteria) {
websiteId: string,
criteria: {
startDate: Date;
endDate: Date;
timezone?: string;
unit?: string;
count?: string;
filters: object;
sessionKey?: string;
},
) {
const { const {
startDate, startDate,
endDate, endDate,
@ -73,18 +61,7 @@ async function relationalQuery(
); );
} }
async function clickhouseQuery( async function clickhouseQuery(websiteId: string, criteria: PageviewStatsCriteria) {
websiteId: string,
criteria: {
startDate: Date;
endDate: Date;
timezone?: string;
unit?: string;
count?: string;
filters: object;
sessionKey?: string;
},
) {
const { const {
startDate, startDate,
endDate, endDate,

View File

@ -9,18 +9,18 @@ export async function getSessions(...args: [websiteId: string, startAt: Date]) {
}); });
} }
async function relationalQuery(websiteId: string, startAt: Date) { async function relationalQuery(websiteId: string, startDate: Date) {
return prisma.client.session.findMany({ return prisma.client.session.findMany({
where: { where: {
websiteId, websiteId,
createdAt: { createdAt: {
gte: startAt, gte: startDate,
}, },
}, },
}); });
} }
async function clickhouseQuery(websiteId: string, startAt: Date) { async function clickhouseQuery(websiteId: string, startDate: Date) {
const { rawQuery } = clickhouse; const { rawQuery } = clickhouse;
return rawQuery( return rawQuery(
@ -42,11 +42,11 @@ async function clickhouseQuery(websiteId: string, startAt: Date) {
city city
from website_event from website_event
where website_id = {websiteId:UUID} where website_id = {websiteId:UUID}
and created_at >= {startAt:DateTime} and created_at >= {startDate:DateTime}
`, `,
{ {
websiteId, websiteId,
startAt, startDate,
}, },
); );
} }

View File

@ -14,6 +14,7 @@ export async function getWebsiteDateRange(...args: [websiteId: string]) {
async function relationalQuery(websiteId: string) { async function relationalQuery(websiteId: string) {
const { rawQuery } = prisma; const { rawQuery } = prisma;
const website = await loadWebsite(websiteId);
return rawQuery( return rawQuery(
` `
@ -21,12 +22,10 @@ async function relationalQuery(websiteId: string) {
min(created_at) as min, min(created_at) as min,
max(created_at) as max max(created_at) as max
from website_event from website_event
join website where website_id = {{websiteId::uuid}}
on website_event.website_id = website.website_id and created_at >= {{startDate}}
where website.website_id = {{websiteId::uuid}}
and website_event.created_at >= coalesce(website.reset_at, website.created_at)
`, `,
{ websiteId }, { websiteId, startDate: maxDate(new Date(DEFAULT_RESET_DATE), new Date(website.resetAt)) },
); );
} }
@ -43,6 +42,6 @@ async function clickhouseQuery(websiteId: string) {
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), website.resetAt) }, { websiteId, startDate: maxDate(new Date(DEFAULT_RESET_DATE), new Date(website.resetAt)) },
); );
} }