From 09af33c77e6374e51f89a34845fe8883adaa2040 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 25 Jul 2023 23:59:08 -0700 Subject: [PATCH] Added parseDateRangeQuery function. --- lib/auth.ts | 2 +- lib/date.js | 33 ++++++--- lib/load.ts | 51 ++++++++++++++ lib/middleware.ts | 1 + lib/query.ts | 69 +++++++------------ lib/session.ts | 2 +- pages/api/websites/[id]/metrics.ts | 6 +- .../analytics/eventData/getEventDataEvents.ts | 13 ++-- .../analytics/eventData/getEventDataFields.ts | 12 ++-- queries/analytics/events/getEventMetrics.ts | 8 +-- .../analytics/pageviews/getPageviewMetrics.ts | 8 +-- .../analytics/pageviews/getPageviewStats.ts | 8 +-- .../analytics/sessions/getSessionMetrics.ts | 8 +-- .../analytics/stats/getWebsiteDateRange.ts | 6 +- queries/analytics/stats/getWebsiteStats.ts | 10 +-- 15 files changed, 139 insertions(+), 98 deletions(-) create mode 100644 lib/load.ts diff --git a/lib/auth.ts b/lib/auth.ts index bf01a1ab..cfd5c4ce 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -13,7 +13,7 @@ import { import { getTeamUser } from 'queries'; import { getTeamWebsite, getTeamWebsiteByTeamMemberId } from 'queries/admin/teamWebsite'; import { validate } from 'uuid'; -import { loadWebsite } from './query'; +import { loadWebsite } from './load'; import { Auth } from './types'; const log = debug('umami:auth'); diff --git a/lib/date.js b/lib/date.js index 649ae9f1..726eaa43 100644 --- a/lib/date.js +++ b/lib/date.js @@ -26,9 +26,20 @@ import { differenceInCalendarMonths, differenceInCalendarYears, format, + max, + min, + isDate, } from 'date-fns'; import { getDateLocale } from 'lib/lang'; +const dateFuncs = { + minute: [differenceInMinutes, addMinutes, startOfMinute], + hour: [differenceInHours, addHours, startOfHour], + day: [differenceInCalendarDays, addDays, startOfDay], + month: [differenceInCalendarMonths, addMonths, startOfMonth], + year: [differenceInCalendarYears, addYears, startOfYear], +}; + export function getTimezone() { return moment.tz.guess(); } @@ -155,10 +166,12 @@ export function parseDateRange(value, locale = 'en-US') { } } -export function getAllowedUnits(unit) { +export function getAllowedUnits(startDate, endDate) { const units = ['minute', 'hour', 'day', 'month', 'year']; + const minUnit = getMinimumUnit(startDate, endDate); + const index = units.indexOf(minUnit); - return units.splice(units.indexOf(unit)); + return index >= 0 ? units.splice(index) : []; } export function getMinimumUnit(startDate, endDate) { @@ -196,14 +209,6 @@ export function getDateFromString(str) { return new Date(year, month - 1, day); } -const dateFuncs = { - minute: [differenceInMinutes, addMinutes, startOfMinute], - hour: [differenceInHours, addHours, startOfHour], - day: [differenceInCalendarDays, addDays, startOfDay], - month: [differenceInCalendarMonths, addMonths, startOfMonth], - year: [differenceInCalendarYears, addYears, startOfYear], -}; - export function getDateArray(data, startDate, endDate, unit) { const arr = []; const [diff, add, normalize] = dateFuncs[unit]; @@ -249,3 +254,11 @@ export function dateFormat(date, str, locale = 'en-US') { locale: getDateLocale(locale), }); } + +export function maxDate(...args) { + return max(args.filter(n => isDate(n))); +} + +export function minDate(...args) { + return min(args.filter(n => isDate(n))); +} diff --git a/lib/load.ts b/lib/load.ts new file mode 100644 index 00000000..4ce18b09 --- /dev/null +++ b/lib/load.ts @@ -0,0 +1,51 @@ +import cache from 'lib/cache'; +import { getWebsite, getSession, getUser } from 'queries'; +import { User, Website, Session } from '@prisma/client'; + +export async function loadWebsite(websiteId: string): Promise { + let website; + + if (cache.enabled) { + website = await cache.fetchWebsite(websiteId); + } else { + website = await getWebsite({ id: websiteId }); + } + + if (!website || website.deletedAt) { + return null; + } + + return website; +} + +export async function loadSession(sessionId: string): Promise { + let session; + + if (cache.enabled) { + session = await cache.fetchSession(sessionId); + } else { + session = await getSession({ id: sessionId }); + } + + if (!session) { + return null; + } + + return session; +} + +export async function loadUser(userId: string): Promise { + let user; + + if (cache.enabled) { + user = await cache.fetchUser(userId); + } else { + user = await getUser({ id: userId }); + } + + if (!user || user.deletedAt) { + return null; + } + + return user; +} diff --git a/lib/middleware.ts b/lib/middleware.ts index 1fd13b09..4185f80b 100644 --- a/lib/middleware.ts +++ b/lib/middleware.ts @@ -73,5 +73,6 @@ export const useAuth = createMiddleware(async (req, res, next) => { } (req as any).auth = { user, token, shareToken, authKey }; + next(); }); diff --git a/lib/query.ts b/lib/query.ts index 4ce18b09..70a189f4 100644 --- a/lib/query.ts +++ b/lib/query.ts @@ -1,51 +1,30 @@ -import cache from 'lib/cache'; -import { getWebsite, getSession, getUser } from 'queries'; -import { User, Website, Session } from '@prisma/client'; +import { NextApiRequest } from 'next'; +import { getAllowedUnits, getMinimumUnit } from './date'; +import { getWebsiteDateRange } from '../queries'; -export async function loadWebsite(websiteId: string): Promise { - let website; +export async function parseDateRangeQuery(req: NextApiRequest) { + const { id: websiteId, startAt, endAt, unit } = req.query; - if (cache.enabled) { - website = await cache.fetchWebsite(websiteId); - } else { - website = await getWebsite({ id: websiteId }); + // All-time + if (+startAt === 0 && +endAt === 1) { + const { min, max } = await getWebsiteDateRange(websiteId as string); + + return { + websiteId, + startDate: min, + endDate: max, + unit: getMinimumUnit(min, max), + }; } - if (!website || website.deletedAt) { - return null; - } + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + const minUnit = getMinimumUnit(startDate, endDate); - return website; -} - -export async function loadSession(sessionId: string): Promise { - let session; - - if (cache.enabled) { - session = await cache.fetchSession(sessionId); - } else { - session = await getSession({ id: sessionId }); - } - - if (!session) { - return null; - } - - return session; -} - -export async function loadUser(userId: string): Promise { - let user; - - if (cache.enabled) { - user = await cache.fetchUser(userId); - } else { - user = await getUser({ id: userId }); - } - - if (!user || user.deletedAt) { - return null; - } - - return user; + return { + websiteId, + startDate, + endDate, + unit: getAllowedUnits(startDate, endDate).includes(unit as string) ? unit : minUnit, + }; } diff --git a/lib/session.ts b/lib/session.ts index 9bd5ba4d..1f693bbd 100644 --- a/lib/session.ts +++ b/lib/session.ts @@ -5,7 +5,7 @@ import { CollectRequestBody, NextApiRequestCollect } from 'pages/api/send'; import { createSession } from 'queries'; import { validate } from 'uuid'; import cache from './cache'; -import { loadSession, loadWebsite } from './query'; +import { loadSession, loadWebsite } from './load'; export async function findSession(req: NextApiRequestCollect) { const { payload } = getJsonBody(req); diff --git a/pages/api/websites/[id]/metrics.ts b/pages/api/websites/[id]/metrics.ts index 5cf818a0..4c6fb270 100644 --- a/pages/api/websites/[id]/metrics.ts +++ b/pages/api/websites/[id]/metrics.ts @@ -5,6 +5,7 @@ import { canViewWebsite } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; import { SESSION_COLUMNS, EVENT_COLUMNS, FILTER_COLUMNS } from 'lib/constants'; import { getPageviewMetrics, getSessionMetrics } from 'queries'; +import { parseDateRangeQuery } from 'lib/query'; export interface WebsiteMetricsRequestQuery { id: string; @@ -34,8 +35,6 @@ export default async ( const { id: websiteId, type, - startAt, - endAt, url, referrer, title, @@ -54,8 +53,7 @@ export default async ( return unauthorized(res); } - const startDate = new Date(+startAt); - const endDate = new Date(+endAt); + const { startDate, endDate } = await parseDateRangeQuery(req); if (SESSION_COLUMNS.includes(type)) { const column = FILTER_COLUMNS[type] || type; diff --git a/queries/analytics/eventData/getEventDataEvents.ts b/queries/analytics/eventData/getEventDataEvents.ts index 03eecb9b..7a926eaf 100644 --- a/queries/analytics/eventData/getEventDataEvents.ts +++ b/queries/analytics/eventData/getEventDataEvents.ts @@ -2,9 +2,8 @@ import prisma from 'lib/prisma'; import clickhouse from 'lib/clickhouse'; import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db'; import { WebsiteEventDataFields } from 'lib/types'; -import { loadWebsite } from 'lib/query'; -import { DEFAULT_RESET_DATE } from 'lib/constants'; -import { max } from 'date-fns'; +import { loadWebsite } from 'lib/load'; +import { maxDate } from 'lib/date'; export async function getEventDataEvents( ...args: [ @@ -48,7 +47,7 @@ async function relationalQuery( group by ed.event_key, ed.string_value order by 3 desc, 2 desc, 1 asc `, - { ...filters, websiteId, startDate: max([startDate, website.resetAt]), endDate }, + { ...filters, websiteId, startDate: maxDate(startDate, website.resetAt), endDate }, ); } return rawQuery( @@ -67,7 +66,7 @@ async function relationalQuery( group by we.event_name, ed.event_key, ed.string_value order by 3 desc, 2 desc, 1 asc `, - { websiteId, field, startDate: max([startDate, website.resetAt]), endDate }, + { websiteId, field, startDate: maxDate(startDate, website.resetAt), endDate }, ); } @@ -98,7 +97,7 @@ async function clickhouseQuery( order by 1 asc, 2 asc, 3 asc, 4 desc limit 100 `, - { ...filters, websiteId, startDate: max([startDate, website.resetAt]), endDate }, + { ...filters, websiteId, startDate: maxDate(startDate, website.resetAt), endDate }, ); } @@ -116,6 +115,6 @@ async function clickhouseQuery( order by 1 asc, 2 asc limit 100 `, - { websiteId, startDate: max([startDate, website.resetAt]), endDate }, + { websiteId, startDate: maxDate(startDate, website.resetAt), endDate }, ); } diff --git a/queries/analytics/eventData/getEventDataFields.ts b/queries/analytics/eventData/getEventDataFields.ts index 65d2669d..516c58d0 100644 --- a/queries/analytics/eventData/getEventDataFields.ts +++ b/queries/analytics/eventData/getEventDataFields.ts @@ -2,8 +2,8 @@ import prisma from 'lib/prisma'; import clickhouse from 'lib/clickhouse'; import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db'; import { WebsiteEventDataFields } from 'lib/types'; -import { loadWebsite } from 'lib/query'; -import { max } from 'date-fns'; +import { loadWebsite } from 'lib/load'; +import { maxDate } from 'lib/date'; export async function getEventDataFields( ...args: [websiteId: string, startDate: Date, endDate: Date, field?: string] @@ -33,7 +33,7 @@ async function relationalQuery(websiteId: string, startDate: Date, endDate: Date order by 3 desc, 2 desc, 1 asc limit 100 `, - { websiteId, field, startDate: max([startDate, website.resetAt]), endDate }, + { websiteId, field, startDate: maxDate(startDate, website.resetAt), endDate }, ); } @@ -50,7 +50,7 @@ async function relationalQuery(websiteId: string, startDate: Date, endDate: Date order by 3 desc, 2 asc, 1 asc limit 100 `, - { websiteId, startDate: max([startDate, website.resetAt]), endDate }, + { websiteId, startDate: maxDate(startDate, website.resetAt), endDate }, ); } @@ -73,7 +73,7 @@ async function clickhouseQuery(websiteId: string, startDate: Date, endDate: Date order by 3 desc, 2 desc, 1 asc limit 100 `, - { websiteId, field, startDate: max([startDate, website.resetAt]), endDate }, + { websiteId, field, startDate: maxDate(startDate, website.resetAt), endDate }, ); } @@ -90,6 +90,6 @@ async function clickhouseQuery(websiteId: string, startDate: Date, endDate: Date order by 3 desc, 2 asc, 1 asc limit 100 `, - { websiteId, startDate: max([startDate, website.resetAt]), endDate }, + { websiteId, startDate: maxDate(startDate, website.resetAt), endDate }, ); } diff --git a/queries/analytics/events/getEventMetrics.ts b/queries/analytics/events/getEventMetrics.ts index 0dcb68e7..37044d1b 100644 --- a/queries/analytics/events/getEventMetrics.ts +++ b/queries/analytics/events/getEventMetrics.ts @@ -3,8 +3,8 @@ import clickhouse from 'lib/clickhouse'; import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db'; import { WebsiteEventMetric } from 'lib/types'; import { DEFAULT_RESET_DATE, EVENT_TYPE } from 'lib/constants'; -import { loadWebsite } from 'lib/query'; -import { max } from 'date-fns'; +import { loadWebsite } from 'lib/load'; +import { maxDate } from 'lib/date'; export async function getEventMetrics( ...args: [ @@ -67,7 +67,7 @@ async function relationalQuery( { ...filters, websiteId, - startDate: max([startDate, website.resetAt]), + startDate: maxDate(startDate, website.resetAt), endDate, eventType: EVENT_TYPE.customEvent, }, @@ -114,7 +114,7 @@ async function clickhouseQuery( { ...filters, websiteId, - startDate: max([startDate, website.resetAt]), + startDate: maxDate(startDate, website.resetAt), endDate, eventType: EVENT_TYPE.customEvent, }, diff --git a/queries/analytics/pageviews/getPageviewMetrics.ts b/queries/analytics/pageviews/getPageviewMetrics.ts index d792b82f..677de980 100644 --- a/queries/analytics/pageviews/getPageviewMetrics.ts +++ b/queries/analytics/pageviews/getPageviewMetrics.ts @@ -2,8 +2,8 @@ import prisma from 'lib/prisma'; import clickhouse from 'lib/clickhouse'; import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db'; import { EVENT_TYPE } from 'lib/constants'; -import { loadWebsite } from 'lib/query'; -import { max } from 'date-fns'; +import { loadWebsite } from 'lib/load'; +import { maxDate } from 'lib/date'; export async function getPageviewMetrics( ...args: [ @@ -36,7 +36,7 @@ async function relationalQuery( const website = await loadWebsite(websiteId); const params: any = { websiteId, - startDate: max([startDate, website.resetAt]), + startDate: maxDate(startDate, website.resetAt), endDate, eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView, }; @@ -84,7 +84,7 @@ async function clickhouseQuery( const website = await loadWebsite(websiteId); const params = { websiteId, - startDate: max([startDate, website.resetAt]), + startDate: maxDate(startDate, website.resetAt), endDate, eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView, domain: undefined, diff --git a/queries/analytics/pageviews/getPageviewStats.ts b/queries/analytics/pageviews/getPageviewStats.ts index f0aed17d..9dd7f54c 100644 --- a/queries/analytics/pageviews/getPageviewStats.ts +++ b/queries/analytics/pageviews/getPageviewStats.ts @@ -2,8 +2,8 @@ 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 { loadWebsite } from 'lib/query'; -import { max } from 'date-fns'; +import { loadWebsite } from 'lib/load'; +import { maxDate } from 'lib/date'; export async function getPageviewStats( ...args: [ @@ -66,7 +66,7 @@ async function relationalQuery( { ...filters, websiteId, - startDate: max([startDate, website.resetAt]), + startDate: maxDate(startDate, website.resetAt), endDate, eventType: EVENT_TYPE.pageView, }, @@ -118,7 +118,7 @@ async function clickhouseQuery( { ...filters, websiteId, - startDate: max([startDate, website.resetAt]), + startDate: maxDate(startDate, website.resetAt), endDate, eventType: EVENT_TYPE.pageView, }, diff --git a/queries/analytics/sessions/getSessionMetrics.ts b/queries/analytics/sessions/getSessionMetrics.ts index 4a28afe1..e037176b 100644 --- a/queries/analytics/sessions/getSessionMetrics.ts +++ b/queries/analytics/sessions/getSessionMetrics.ts @@ -2,8 +2,8 @@ import prisma from 'lib/prisma'; import clickhouse from 'lib/clickhouse'; import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db'; import { DEFAULT_RESET_DATE, EVENT_TYPE } from 'lib/constants'; -import { loadWebsite } from 'lib/query'; -import { max } from 'date-fns'; +import { loadWebsite } from 'lib/load'; +import { maxDate } from 'lib/date'; export async function getSessionMetrics( ...args: [ @@ -42,7 +42,7 @@ async function relationalQuery( group by 1 order by 2 desc limit 100`, - { ...filters, websiteId, startDate: max([startDate, website.resetAt]), endDate }, + { ...filters, websiteId, startDate: maxDate(startDate, website.resetAt), endDate }, ); } @@ -71,7 +71,7 @@ async function clickhouseQuery( { ...filters, websiteId, - startDate: max([startDate, website.resetAt]), + startDate: maxDate(startDate, website.resetAt), endDate, eventType: EVENT_TYPE.pageView, }, diff --git a/queries/analytics/stats/getWebsiteDateRange.ts b/queries/analytics/stats/getWebsiteDateRange.ts index accb4021..1637a4fe 100644 --- a/queries/analytics/stats/getWebsiteDateRange.ts +++ b/queries/analytics/stats/getWebsiteDateRange.ts @@ -1,9 +1,9 @@ import prisma from 'lib/prisma'; import clickhouse from 'lib/clickhouse'; import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db'; -import { loadWebsite } from 'lib/query'; +import { loadWebsite } from 'lib/load'; import { DEFAULT_RESET_DATE } from 'lib/constants'; -import { max } from 'date-fns'; +import { maxDate } from 'lib/date'; export async function getWebsiteDateRange(...args: [websiteId: string]) { return runQuery({ @@ -43,6 +43,6 @@ async function clickhouseQuery(websiteId: string) { where website_id = {websiteId:UUID} and created_at >= {startDate:DateTime} `, - { websiteId, startDate: max([new Date('2000-01-01'), website.resetAt]) }, + { websiteId, startDate: maxDate(new Date(DEFAULT_RESET_DATE), website.resetAt) }, ); } diff --git a/queries/analytics/stats/getWebsiteStats.ts b/queries/analytics/stats/getWebsiteStats.ts index fac9b8e0..f44abafa 100644 --- a/queries/analytics/stats/getWebsiteStats.ts +++ b/queries/analytics/stats/getWebsiteStats.ts @@ -1,9 +1,9 @@ import prisma from 'lib/prisma'; import clickhouse from 'lib/clickhouse'; import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db'; -import { DEFAULT_RESET_DATE, EVENT_TYPE } from 'lib/constants'; -import { loadWebsite } from 'lib/query'; -import { max } from 'date-fns'; +import { EVENT_TYPE } from 'lib/constants'; +import { loadWebsite } from 'lib/load'; +import { maxDate } from 'lib/date'; export async function getWebsiteStats( ...args: [ @@ -53,7 +53,7 @@ async function relationalQuery( { ...filters, websiteId, - startDate: max([startDate, website.resetAt]), + startDate: maxDate(startDate, website.resetAt), endDate, eventType: EVENT_TYPE.pageView, }, @@ -94,7 +94,7 @@ async function clickhouseQuery( { ...filters, websiteId, - startDate: max([startDate, website.resetAt]), + startDate: maxDate(startDate, website.resetAt), endDate, eventType: EVENT_TYPE.pageView, },