diff --git a/lib/date.js b/lib/date.js index 726eaa43..bf5dd90a 100644 --- a/lib/date.js +++ b/lib/date.js @@ -62,10 +62,10 @@ export function parseDateRange(value, locale = 'en-US') { } if (value?.startsWith?.('range')) { - const [, startAt, endAt] = value.split(':'); + const [, startTime, endTime] = value.split(':'); - const startDate = new Date(+startAt); - const endDate = new Date(+endAt); + const startDate = new Date(+startTime); + const endDate = new Date(+endTime); return { ...getDateRangeValues(startDate, endDate), diff --git a/lib/query.ts b/lib/query.ts index 70a189f4..09b77df8 100644 --- a/lib/query.ts +++ b/lib/query.ts @@ -10,7 +10,6 @@ export async function parseDateRangeQuery(req: NextApiRequest) { const { min, max } = await getWebsiteDateRange(websiteId as string); return { - websiteId, startDate: min, endDate: max, unit: getMinimumUnit(min, max), @@ -22,9 +21,8 @@ export async function parseDateRangeQuery(req: NextApiRequest) { const minUnit = getMinimumUnit(startDate, endDate); return { - websiteId, startDate, endDate, - unit: getAllowedUnits(startDate, endDate).includes(unit as string) ? unit : minUnit, + unit: (getAllowedUnits(startDate, endDate).includes(unit as string) ? unit : minUnit) as string, }; } diff --git a/pages/api/websites/[id]/events.ts b/pages/api/websites/[id]/events.ts index 12473da0..b9e3ac71 100644 --- a/pages/api/websites/[id]/events.ts +++ b/pages/api/websites/[id]/events.ts @@ -5,6 +5,7 @@ import moment from 'moment-timezone'; import { NextApiResponse } from 'next'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getEventMetrics } from 'queries'; +import { parseDateRangeQuery } from 'lib/query'; const unitTypes = ['year', 'month', 'hour', 'day']; @@ -25,7 +26,8 @@ export default async ( await useCors(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 (!(await canViewWebsite(req.auth, websiteId))) { @@ -35,8 +37,6 @@ export default async ( if (!moment.tz.zone(timezone) || !unitTypes.includes(unit)) { return badRequest(res); } - const startDate = new Date(+startAt); - const endDate = new Date(+endAt); const events = await getEventMetrics(websiteId, { startDate, diff --git a/pages/api/websites/[id]/pageviews.ts b/pages/api/websites/[id]/pageviews.ts index 7cdd8970..453c6733 100644 --- a/pages/api/websites/[id]/pageviews.ts +++ b/pages/api/websites/[id]/pageviews.ts @@ -3,9 +3,9 @@ import { NextApiResponse } from 'next'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { NextApiRequestQueryBody, WebsitePageviews } from 'lib/types'; import { canViewWebsite } from 'lib/auth'; -import { getAllowedUnits } from 'lib/date'; import { useAuth, useCors } from 'lib/middleware'; import { getPageviewStats } from 'queries'; +import { parseDateRangeQuery } from 'lib/query'; export interface WebsitePageviewRequestQuery { id: string; @@ -33,9 +33,6 @@ export default async ( const { id: websiteId, - startAt, - endAt, - unit, timezone, url, referrer, @@ -53,10 +50,9 @@ export default async ( return unauthorized(res); } - const startDate = new Date(+startAt); - const endDate = new Date(+endAt); + const { startDate, endDate, unit } = await parseDateRangeQuery(req); - if (!moment.tz.zone(timezone) || !getAllowedUnits(unit).includes(unit)) { + if (!moment.tz.zone(timezone)) { return badRequest(res); } diff --git a/pages/api/websites/[id]/stats.ts b/pages/api/websites/[id]/stats.ts index 1e2f2292..34347fe5 100644 --- a/pages/api/websites/[id]/stats.ts +++ b/pages/api/websites/[id]/stats.ts @@ -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 { useAuth, useCors } from 'lib/middleware'; import { NextApiRequestQueryBody, WebsiteStats } from 'lib/types'; -import { NextApiResponse } from 'next'; -import { methodNotAllowed, ok, unauthorized } from 'next-basics'; +import { parseDateRangeQuery } from 'lib/query'; import { getWebsiteStats } from 'queries'; export interface WebsiteStatsRequestQuery { @@ -31,8 +33,6 @@ export default async ( const { id: websiteId, - startAt, - endAt, url, referrer, title, @@ -51,12 +51,10 @@ export default async ( return unauthorized(res); } - const startDate = new Date(+startAt); - const endDate = new Date(+endAt); - - const distance = endAt - startAt; - const prevStartDate = new Date(+startAt - distance); - const prevEndDate = new Date(+endAt - distance); + const { startDate, endDate } = await parseDateRangeQuery(req); + const diff = differenceInMinutes(endDate, startDate); + const prevStartDate = addMinutes(startDate, -diff); + const prevEndDate = addMinutes(endDate, -diff); const metrics = await getWebsiteStats(websiteId, { startDate, diff --git a/queries/analytics/events/getEvents.ts b/queries/analytics/events/getEvents.ts index 2169ee61..17528d66 100644 --- a/queries/analytics/events/getEvents.ts +++ b/queries/analytics/events/getEvents.ts @@ -2,26 +2,26 @@ import clickhouse from 'lib/clickhouse'; import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db'; 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({ [PRISMA]: () => relationalQuery(...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({ where: { websiteId, eventType, createdAt: { - gte: startAt, + gte: startDate, }, }, }); } -function clickhouseQuery(websiteId: string, startAt: Date, eventType: number) { +function clickhouseQuery(websiteId: string, startDate: Date, eventType: number) { const { rawQuery } = clickhouse; return rawQuery( @@ -37,12 +37,12 @@ function clickhouseQuery(websiteId: string, startAt: Date, eventType: number) { event_name as eventName from website_event where website_id = {websiteId:UUID} - and created_at >= {startAt:DateTime} + and created_at >= {startDate:DateTime} and event_type = {eventType:UInt32} `, { websiteId, - startAt, + startDate, eventType, }, ); diff --git a/queries/analytics/pageviews/getPageviewStats.ts b/queries/analytics/pageviews/getPageviewStats.ts index 9dd7f54c..31b0ebdd 100644 --- a/queries/analytics/pageviews/getPageviewStats.ts +++ b/queries/analytics/pageviews/getPageviewStats.ts @@ -1,23 +1,22 @@ import clickhouse from 'lib/clickhouse'; import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db'; 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 { 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( - ...args: [ - websiteId: string, - criteria: { - startDate: Date; - endDate: Date; - timezone?: string; - unit?: string; - count?: string; - filters: object; - sessionKey?: string; - }, - ] + ...args: [websiteId: string, criteria: PageviewStatsCriteria] ) { return runQuery({ [PRISMA]: () => relationalQuery(...args), @@ -25,18 +24,7 @@ export async function getPageviewStats( }); } -async function relationalQuery( - websiteId: string, - criteria: { - startDate: Date; - endDate: Date; - timezone?: string; - unit?: string; - count?: string; - filters: object; - sessionKey?: string; - }, -) { +async function relationalQuery(websiteId: string, criteria: PageviewStatsCriteria) { const { startDate, endDate, @@ -73,18 +61,7 @@ async function relationalQuery( ); } -async function clickhouseQuery( - websiteId: string, - criteria: { - startDate: Date; - endDate: Date; - timezone?: string; - unit?: string; - count?: string; - filters: object; - sessionKey?: string; - }, -) { +async function clickhouseQuery(websiteId: string, criteria: PageviewStatsCriteria) { const { startDate, endDate, diff --git a/queries/analytics/sessions/getSessions.ts b/queries/analytics/sessions/getSessions.ts index 750b6249..6936f902 100644 --- a/queries/analytics/sessions/getSessions.ts +++ b/queries/analytics/sessions/getSessions.ts @@ -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({ where: { websiteId, createdAt: { - gte: startAt, + gte: startDate, }, }, }); } -async function clickhouseQuery(websiteId: string, startAt: Date) { +async function clickhouseQuery(websiteId: string, startDate: Date) { const { rawQuery } = clickhouse; return rawQuery( @@ -42,11 +42,11 @@ async function clickhouseQuery(websiteId: string, startAt: Date) { city from website_event where website_id = {websiteId:UUID} - and created_at >= {startAt:DateTime} + and created_at >= {startDate:DateTime} `, { websiteId, - startAt, + startDate, }, ); } diff --git a/queries/analytics/stats/getWebsiteDateRange.ts b/queries/analytics/stats/getWebsiteDateRange.ts index 1637a4fe..1f94c398 100644 --- a/queries/analytics/stats/getWebsiteDateRange.ts +++ b/queries/analytics/stats/getWebsiteDateRange.ts @@ -14,6 +14,7 @@ export async function getWebsiteDateRange(...args: [websiteId: string]) { async function relationalQuery(websiteId: string) { const { rawQuery } = prisma; + const website = await loadWebsite(websiteId); return rawQuery( ` @@ -21,12 +22,10 @@ async function relationalQuery(websiteId: string) { min(created_at) as min, max(created_at) as max from website_event - join website - on website_event.website_id = website.website_id - where website.website_id = {{websiteId::uuid}} - and website_event.created_at >= coalesce(website.reset_at, website.created_at) + where website_id = {{websiteId::uuid}} + and created_at >= {{startDate}} `, - { 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} 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)) }, ); }