From 9b501d03e9b60ecb1d56e3862db7a7bd3c8e3fb7 Mon Sep 17 00:00:00 2001 From: Maxime-J Date: Sat, 9 Sep 2023 09:38:16 +0000 Subject: [PATCH 1/9] update date range parser and date picker --- src/components/metrics/DatePickerForm.js | 6 +++--- src/lib/date.ts | 12 +++--------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/components/metrics/DatePickerForm.js b/src/components/metrics/DatePickerForm.js index fa45b64a..9eb3c52e 100644 --- a/src/components/metrics/DatePickerForm.js +++ b/src/components/metrics/DatePickerForm.js @@ -1,6 +1,6 @@ import { useState } from 'react'; import { Button, ButtonGroup, Calendar } from 'react-basics'; -import { isAfter, isBefore, isSameDay } from 'date-fns'; +import { isAfter, isBefore, isSameDay, startOfDay, endOfDay } from 'date-fns'; import useLocale from 'components/hooks/useLocale'; import { getDateLocale } from 'lib/lang'; import { FILTER_DAY, FILTER_RANGE } from 'lib/constants'; @@ -31,9 +31,9 @@ export function DatePickerForm({ const handleSave = () => { if (selected === FILTER_DAY) { - onChange(`range:${singleDate.getTime()}:${singleDate.getTime()}`); + onChange(`range:${startOfDay(singleDate).getTime()}:${endOfDay(singleDate).getTime()}`); } else { - onChange(`range:${startDate.getTime()}:${endDate.getTime()}`); + onChange(`range:${startOfDay(startDate).getTime()}:${endOfDay(endDate).getTime()}`); } }; diff --git a/src/lib/date.ts b/src/lib/date.ts index 14f0e13c..bfbfc0b9 100644 --- a/src/lib/date.ts +++ b/src/lib/date.ts @@ -78,7 +78,9 @@ export function parseDateRange(value, locale = 'en-US') { const endDate = new Date(+endTime); return { - ...getDateRangeValues(startDate, endDate), + startDate, + endDate, + unit: getMinimumUnit(startDate, endDate), value, }; } @@ -255,14 +257,6 @@ export function getMinimumUnit(startDate, endDate) { return 'year'; } -export function getDateRangeValues(startDate, endDate) { - return { - startDate: startOfDay(startDate), - endDate: endOfDay(endDate), - unit: getMinimumUnit(startDate, endDate), - }; -} - export function getDateFromString(str) { const [ymd, hms] = str.split(' '); const [year, month, day] = ymd.split('-'); From eec871dc4aff6a67e8fda4a757a856b179d329c7 Mon Sep 17 00:00:00 2001 From: Maxime-J Date: Sat, 9 Sep 2023 09:51:58 +0000 Subject: [PATCH 2/9] use available dateLocale --- src/components/input/MonthSelect.js | 7 +++---- src/components/metrics/DatePickerForm.js | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/input/MonthSelect.js b/src/components/input/MonthSelect.js index fb1bf535..312c6854 100644 --- a/src/components/input/MonthSelect.js +++ b/src/components/input/MonthSelect.js @@ -12,11 +12,10 @@ import { startOfMonth, endOfMonth } from 'date-fns'; import Icons from 'components/icons'; import { useLocale } from 'components/hooks'; import { formatDate } from 'lib/date'; -import { getDateLocale } from 'lib/lang'; import styles from './MonthSelect.module.css'; export function MonthSelect({ date = new Date(), onChange }) { - const { locale } = useLocale(); + const { locale, dateLocale } = useLocale(); const month = formatDate(date, 'MMMM', locale); const year = date.getFullYear(); const ref = useRef(); @@ -40,7 +39,7 @@ export function MonthSelect({ date = new Date(), onChange }) { {close => ( )} @@ -57,7 +56,7 @@ export function MonthSelect({ date = new Date(), onChange }) { {close => ( )} diff --git a/src/components/metrics/DatePickerForm.js b/src/components/metrics/DatePickerForm.js index 9eb3c52e..2dcca77a 100644 --- a/src/components/metrics/DatePickerForm.js +++ b/src/components/metrics/DatePickerForm.js @@ -2,7 +2,6 @@ import { useState } from 'react'; import { Button, ButtonGroup, Calendar } from 'react-basics'; import { isAfter, isBefore, isSameDay, startOfDay, endOfDay } from 'date-fns'; import useLocale from 'components/hooks/useLocale'; -import { getDateLocale } from 'lib/lang'; import { FILTER_DAY, FILTER_RANGE } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; import styles from './DatePickerForm.module.css'; @@ -21,7 +20,7 @@ export function DatePickerForm({ const [singleDate, setSingleDate] = useState(defaultStartDate); const [startDate, setStartDate] = useState(defaultStartDate); const [endDate, setEndDate] = useState(defaultEndDate); - const { locale } = useLocale(); + const { dateLocale } = useLocale(); const { formatMessage, labels } = useMessages(); const disabled = @@ -60,14 +59,14 @@ export function DatePickerForm({ date={startDate} minDate={minDate} maxDate={endDate} - locale={getDateLocale(locale)} + locale={dateLocale} onChange={setStartDate} /> From d457e6ea16f8861a8e8ff1ccc9848dbc88137bdd Mon Sep 17 00:00:00 2001 From: Maxime-J Date: Sat, 9 Sep 2023 09:53:22 +0000 Subject: [PATCH 3/9] localize single day calendar in date picker --- src/components/metrics/DatePickerForm.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/metrics/DatePickerForm.js b/src/components/metrics/DatePickerForm.js index 2dcca77a..5e1906c3 100644 --- a/src/components/metrics/DatePickerForm.js +++ b/src/components/metrics/DatePickerForm.js @@ -50,6 +50,7 @@ export function DatePickerForm({ date={singleDate} minDate={minDate} maxDate={maxDate} + locale={dateLocale} onChange={setSingleDate} /> )} From e73865b880a4f4d502f2dcd44c0cf935ce6937f3 Mon Sep 17 00:00:00 2001 From: AkashRajpurohit Date: Sun, 17 Sep 2023 19:34:35 +0530 Subject: [PATCH 4/9] fix: :bug: gulp error so that error in sending data does not affect the action triggering it --- src/tracker/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tracker/index.js b/src/tracker/index.js index 1686df42..491eef7d 100644 --- a/src/tracker/index.js +++ b/src/tracker/index.js @@ -187,7 +187,8 @@ headers, }) .then(res => res.text()) - .then(text => (cache = text)); + .then(text => (cache = text)) + .catch(() => {}); // no-op, gulp error }; const track = (obj, data) => { From 2ccb8d0a3cdfc5c47e78498e803874fbafe01e78 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 22 Sep 2023 17:24:15 -0700 Subject: [PATCH 5/9] Bump version v2.7.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6617b455..79960eb2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "2.6.2", + "version": "2.7.0", "description": "A simple, fast, privacy-focused alternative to Google Analytics.", "author": "Mike Cao ", "license": "MIT", From ce2a83a09fb10cf28d4c408b5b08ae8b0e4afc25 Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Mon, 25 Sep 2023 13:19:56 -0700 Subject: [PATCH 6/9] More yup validations. --- src/lib/yup.ts | 17 +++++---- src/pages/api/reports/retention.ts | 7 ++-- src/pages/api/teams/[id]/users/[userId].ts | 1 + src/pages/api/teams/[id]/users/index.ts | 17 +++++---- src/pages/api/websites/[id]/events.ts | 6 ++-- src/pages/api/websites/[id]/index.ts | 10 +++--- src/pages/api/websites/[id]/metrics.ts | 12 +++++++ src/pages/api/websites/[id]/pageviews.ts | 31 ++++++++++------ src/pages/api/websites/[id]/reports.ts | 2 ++ src/pages/api/websites/[id]/reset.ts | 5 ++- src/pages/api/websites/[id]/stats.ts | 35 +++++++++++++------ src/queries/analytics/reports/getRetention.ts | 6 ++-- 12 files changed, 99 insertions(+), 50 deletions(-) diff --git a/src/lib/yup.ts b/src/lib/yup.ts index a9d21028..8b2eceee 100644 --- a/src/lib/yup.ts +++ b/src/lib/yup.ts @@ -1,11 +1,10 @@ +import moment from 'moment'; import * as yup from 'yup'; -export function getDateRangeValidation() { - return { - startAt: yup.number().integer().required(), - endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), - }; -} +export const DateRangeValidation = { + startAt: yup.number().integer().required(), + endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), +}; // ex: /funnel|insights|retention/i export function getFilterValidation(matchRegex) { @@ -17,3 +16,9 @@ export function getFilterValidation(matchRegex) { orderBy: yup.string(), }; } + +export const TimezoneTest = yup.string().test( + 'timezone', + () => `Invalid timezone`, + value => !moment.tz.zone(value), +); diff --git a/src/pages/api/reports/retention.ts b/src/pages/api/reports/retention.ts index 4006ab12..c7a5e9af 100644 --- a/src/pages/api/reports/retention.ts +++ b/src/pages/api/reports/retention.ts @@ -1,6 +1,7 @@ import { canViewWebsite } from 'lib/auth'; import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody } from 'lib/types'; +import { TimezoneTest } from 'lib/yup'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getRetention } from 'queries'; @@ -8,7 +9,7 @@ import * as yup from 'yup'; export interface RetentionRequestBody { websiteId: string; - dateRange: { startDate: string; endDate: string }; + dateRange: { startDate: string; endDate: string; timezone: string }; } const schema = { @@ -19,6 +20,7 @@ const schema = { .shape({ startDate: yup.date().required(), endDate: yup.date().required(), + timezone: TimezoneTest, }) .required(), }), @@ -37,7 +39,7 @@ export default async ( if (req.method === 'POST') { const { websiteId, - dateRange: { startDate, endDate }, + dateRange: { startDate, endDate, timezone }, } = req.body; if (!(await canViewWebsite(req.auth, websiteId))) { @@ -47,6 +49,7 @@ export default async ( const data = await getRetention(websiteId, { startDate: new Date(startDate), endDate: new Date(endDate), + timezone, }); return ok(res, data); diff --git a/src/pages/api/teams/[id]/users/[userId].ts b/src/pages/api/teams/[id]/users/[userId].ts index adb635d5..107aba64 100644 --- a/src/pages/api/teams/[id]/users/[userId].ts +++ b/src/pages/api/teams/[id]/users/[userId].ts @@ -5,6 +5,7 @@ import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { deleteTeamUser } from 'queries'; import * as yup from 'yup'; + export interface TeamUserRequestQuery { id: string; userId: string; diff --git a/src/pages/api/teams/[id]/users/index.ts b/src/pages/api/teams/[id]/users/index.ts index d0efba25..36e9f320 100644 --- a/src/pages/api/teams/[id]/users/index.ts +++ b/src/pages/api/teams/[id]/users/index.ts @@ -1,24 +1,27 @@ import { canViewTeam } from 'lib/auth'; -import { useAuth } from 'lib/middleware'; +import { useAuth, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getUsersByTeamId } from 'queries'; - +import * as yup from 'yup'; export interface TeamUserRequestQuery extends SearchFilter { id: string; } -export interface TeamUserRequestBody { - email: string; - roleId: string; -} +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + }), +}; export default async ( - req: NextApiRequestQueryBody, + req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); const { id: teamId } = req.query; diff --git a/src/pages/api/websites/[id]/events.ts b/src/pages/api/websites/[id]/events.ts index 427cb40e..422200f8 100644 --- a/src/pages/api/websites/[id]/events.ts +++ b/src/pages/api/websites/[id]/events.ts @@ -6,6 +6,8 @@ import { NextApiResponse } from 'next'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getEventMetrics } from 'queries'; import { parseDateRangeQuery } from 'lib/query'; +import * as yup from 'yup'; +import { TimezoneTest } from 'lib/yup'; const unitTypes = ['year', 'month', 'hour', 'day']; @@ -18,15 +20,13 @@ export interface WebsiteEventsRequestQuery { url: string; } -import * as yup from 'yup'; - const schema = { GET: yup.object().shape({ id: yup.string().uuid().required(), startAt: yup.number().integer().required(), endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), unit: yup.string().required(), - timezone: yup.string().required(), + timezone: TimezoneTest.required(), url: yup.string(), }), }; diff --git a/src/pages/api/websites/[id]/index.ts b/src/pages/api/websites/[id]/index.ts index 0e5aacce..e7c7e004 100644 --- a/src/pages/api/websites/[id]/index.ts +++ b/src/pages/api/websites/[id]/index.ts @@ -22,6 +22,12 @@ const schema = { GET: yup.object().shape({ id: yup.string().uuid().required(), }), + POST: yup.object().shape({ + id: yup.string().uuid().required(), + name: yup.string().required(), + domain: yup.string().required(), + shareId: yup.string().matches(SHARE_ID_REGEX, { excludeEmptyString: true }), + }), }; export default async ( @@ -55,10 +61,6 @@ export default async ( let website; - if (shareId && !shareId.match(SHARE_ID_REGEX)) { - return serverError(res, 'Invalid share ID.'); - } - try { website = await updateWebsite(websiteId, { name, domain, shareId }); } catch (e: any) { diff --git a/src/pages/api/websites/[id]/metrics.ts b/src/pages/api/websites/[id]/metrics.ts index b8c37339..89f90fc4 100644 --- a/src/pages/api/websites/[id]/metrics.ts +++ b/src/pages/api/websites/[id]/metrics.ts @@ -33,6 +33,18 @@ const schema = { type: yup.string().required(), startAt: yup.number().required(), endAt: yup.number().required(), + url: yup.string(), + referrer: yup.string(), + title: yup.string(), + query: yup.string(), + os: yup.string(), + browser: yup.string(), + device: yup.string(), + country: yup.string(), + region: yup.string(), + city: yup.string(), + language: yup.string(), + event: yup.string(), }), }; diff --git a/src/pages/api/websites/[id]/pageviews.ts b/src/pages/api/websites/[id]/pageviews.ts index 9985ca89..8c10ffeb 100644 --- a/src/pages/api/websites/[id]/pageviews.ts +++ b/src/pages/api/websites/[id]/pageviews.ts @@ -1,18 +1,17 @@ -import moment from 'moment-timezone'; -import { NextApiResponse } from 'next'; -import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { NextApiRequestQueryBody, WebsitePageviews } from 'lib/types'; import { canViewWebsite } from 'lib/auth'; import { useAuth, useCors, useValidate } from 'lib/middleware'; -import { getPageviewStats, getSessionStats } from 'queries'; import { parseDateRangeQuery } from 'lib/query'; +import { NextApiRequestQueryBody, WebsitePageviews } from 'lib/types'; +import { NextApiResponse } from 'next'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; +import { getPageviewStats, getSessionStats } from 'queries'; export interface WebsitePageviewRequestQuery { id: string; startAt: number; endAt: number; - unit: string; - timezone: string; + unit?: string; + timezone?: string; url?: string; referrer?: string; title?: string; @@ -24,10 +23,24 @@ export interface WebsitePageviewRequestQuery { city?: string; } +import { TimezoneTest } from 'lib/yup'; import * as yup from 'yup'; const schema = { GET: yup.object().shape({ id: yup.string().uuid().required(), + startAt: yup.number().required(), + endAt: yup.number().required(), + unit: yup.string(), + timezone: TimezoneTest, + url: yup.string(), + referrer: yup.string(), + title: yup.string(), + os: yup.string(), + browser: yup.string(), + device: yup.string(), + country: yup.string(), + region: yup.string(), + city: yup.string(), }), }; @@ -62,10 +75,6 @@ export default async ( const { startDate, endDate, unit } = await parseDateRangeQuery(req); - if (!moment.tz.zone(timezone)) { - return badRequest(res); - } - const filters = { startDate, endDate, diff --git a/src/pages/api/websites/[id]/reports.ts b/src/pages/api/websites/[id]/reports.ts index 2c7707e8..36e97a46 100644 --- a/src/pages/api/websites/[id]/reports.ts +++ b/src/pages/api/websites/[id]/reports.ts @@ -1,6 +1,7 @@ import { canViewWebsite } from 'lib/auth'; import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, ReportSearchFilterType, SearchFilter } from 'lib/types'; +import { getFilterValidation } from 'lib/yup'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getReportsByWebsiteId } from 'queries'; @@ -13,6 +14,7 @@ import * as yup from 'yup'; const schema = { GET: yup.object().shape({ id: yup.string().uuid().required(), + ...getFilterValidation(/All|Name|Description|Type|Username|Website Name|Website Domain/i), }), }; diff --git a/src/pages/api/websites/[id]/reset.ts b/src/pages/api/websites/[id]/reset.ts index cfd5e767..b17fdade 100644 --- a/src/pages/api/websites/[id]/reset.ts +++ b/src/pages/api/websites/[id]/reset.ts @@ -4,14 +4,14 @@ import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { resetWebsite } from 'queries'; +import * as yup from 'yup'; export interface WebsiteResetRequestQuery { id: string; } -import * as yup from 'yup'; const schema = { - GET: yup.object().shape({ + POST: yup.object().shape({ id: yup.string().uuid().required(), }), }; @@ -22,7 +22,6 @@ export default async ( ) => { await useCors(req, res); await useAuth(req, res); - req.yup = schema; await useValidate(req, res); diff --git a/src/pages/api/websites/[id]/stats.ts b/src/pages/api/websites/[id]/stats.ts index caf54910..e0c71e40 100644 --- a/src/pages/api/websites/[id]/stats.ts +++ b/src/pages/api/websites/[id]/stats.ts @@ -11,23 +11,36 @@ export interface WebsiteStatsRequestQuery { id: string; startAt: number; endAt: number; - url: string; - referrer: string; - title: string; - query: string; - event: string; - os: string; - browser: string; - device: string; - country: string; - region: string; - city: string; + url?: string; + referrer?: string; + title?: string; + query?: string; + event?: string; + os?: string; + browser?: string; + device?: string; + country?: string; + region?: string; + city?: string; } import * as yup from 'yup'; const schema = { GET: yup.object().shape({ id: yup.string().uuid().required(), + startAt: yup.number().required(), + endAt: yup.number().required(), + url: yup.string(), + referrer: yup.string(), + title: yup.string(), + query: yup.string(), + event: yup.string(), + os: yup.string(), + browser: yup.string(), + device: yup.string(), + country: yup.string(), + region: yup.string(), + city: yup.string(), }), }; diff --git a/src/queries/analytics/reports/getRetention.ts b/src/queries/analytics/reports/getRetention.ts index 3c384b6e..7526644f 100644 --- a/src/queries/analytics/reports/getRetention.ts +++ b/src/queries/analytics/reports/getRetention.ts @@ -8,7 +8,7 @@ export async function getRetention( filters: { startDate: Date; endDate: Date; - timezone: string; + timezone?: string; }, ] ) { @@ -23,7 +23,7 @@ async function relationalQuery( filters: { startDate: Date; endDate: Date; - timezone: string; + timezone?: string; }, ): Promise< { @@ -103,7 +103,7 @@ async function clickhouseQuery( filters: { startDate: Date; endDate: Date; - timezone: string; + timezone?: string; }, ): Promise< { From e6eb9a487e9e0eac32d707b01d07c802dd8e014c Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Mon, 25 Sep 2023 13:31:25 -0700 Subject: [PATCH 7/9] Create unit test. --- src/lib/constants.ts | 2 ++ src/lib/yup.ts | 9 ++++++++- src/pages/api/websites/[id]/events.ts | 25 +++++++++--------------- src/pages/api/websites/[id]/pageviews.ts | 4 ++-- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 888c1484..a548826a 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -30,6 +30,8 @@ export const FILTER_RANGE = 'filter-range'; export const FILTER_REFERRERS = 'filter-referrers'; export const FILTER_PAGES = 'filter-pages'; +export const UNIT_TYPES = ['year', 'month', 'hour', 'day']; + export const USER_FILTER_TYPES = { all: 'All', username: 'Username', diff --git a/src/lib/yup.ts b/src/lib/yup.ts index 8b2eceee..a2ea46d8 100644 --- a/src/lib/yup.ts +++ b/src/lib/yup.ts @@ -1,5 +1,6 @@ import moment from 'moment'; import * as yup from 'yup'; +import { UNIT_TYPES } from './constants'; export const DateRangeValidation = { startAt: yup.number().integer().required(), @@ -20,5 +21,11 @@ export function getFilterValidation(matchRegex) { export const TimezoneTest = yup.string().test( 'timezone', () => `Invalid timezone`, - value => !moment.tz.zone(value), + value => !value || !moment.tz.zone(value), +); + +export const UnitTypeTest = yup.string().test( + 'unit', + () => `Invalid unit`, + value => !value || !UNIT_TYPES.includes(value), ); diff --git a/src/pages/api/websites/[id]/events.ts b/src/pages/api/websites/[id]/events.ts index 422200f8..32288aa5 100644 --- a/src/pages/api/websites/[id]/events.ts +++ b/src/pages/api/websites/[id]/events.ts @@ -1,22 +1,19 @@ -import { WebsiteMetric, NextApiRequestQueryBody } from 'lib/types'; import { canViewWebsite } from 'lib/auth'; import { useAuth, useCors, useValidate } from 'lib/middleware'; -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'; +import { NextApiRequestQueryBody, WebsiteMetric } from 'lib/types'; +import { TimezoneTest, UnitTypeTest } from 'lib/yup'; +import { NextApiResponse } from 'next'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; +import { getEventMetrics } from 'queries'; import * as yup from 'yup'; -import { TimezoneTest } from 'lib/yup'; - -const unitTypes = ['year', 'month', 'hour', 'day']; export interface WebsiteEventsRequestQuery { id: string; startAt: string; endAt: string; - unit: string; - timezone: string; + unit?: string; + timezone?: string; url: string; } @@ -25,8 +22,8 @@ const schema = { id: yup.string().uuid().required(), startAt: yup.number().integer().required(), endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), - unit: yup.string().required(), - timezone: TimezoneTest.required(), + unit: UnitTypeTest, + timezone: TimezoneTest, url: yup.string(), }), }; @@ -49,10 +46,6 @@ export default async ( return unauthorized(res); } - if (!moment.tz.zone(timezone) || !unitTypes.includes(unit)) { - return badRequest(res); - } - const events = await getEventMetrics(websiteId, { startDate, endDate, diff --git a/src/pages/api/websites/[id]/pageviews.ts b/src/pages/api/websites/[id]/pageviews.ts index 8c10ffeb..0f034cc2 100644 --- a/src/pages/api/websites/[id]/pageviews.ts +++ b/src/pages/api/websites/[id]/pageviews.ts @@ -23,14 +23,14 @@ export interface WebsitePageviewRequestQuery { city?: string; } -import { TimezoneTest } from 'lib/yup'; +import { TimezoneTest, UnitTypeTest } from 'lib/yup'; import * as yup from 'yup'; const schema = { GET: yup.object().shape({ id: yup.string().uuid().required(), startAt: yup.number().required(), endAt: yup.number().required(), - unit: yup.string(), + unit: UnitTypeTest, timezone: TimezoneTest, url: yup.string(), referrer: yup.string(), From febf085aca77eb130669d4505798e253062602c2 Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Tue, 26 Sep 2023 12:30:35 -0700 Subject: [PATCH 8/9] Fix yup. --- src/lib/yup.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/yup.ts b/src/lib/yup.ts index a2ea46d8..6c19b089 100644 --- a/src/lib/yup.ts +++ b/src/lib/yup.ts @@ -21,11 +21,11 @@ export function getFilterValidation(matchRegex) { export const TimezoneTest = yup.string().test( 'timezone', () => `Invalid timezone`, - value => !value || !moment.tz.zone(value), + value => moment.tz.zone(value) !== null, ); export const UnitTypeTest = yup.string().test( 'unit', () => `Invalid unit`, - value => !value || !UNIT_TYPES.includes(value), + value => UNIT_TYPES.includes(value), ); From 8e8bf41eb3b2f0bc5034918030ecd08ecd76cf5a Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 26 Sep 2023 13:29:49 -0700 Subject: [PATCH 9/9] css updates for pager / page --- src/components/common/Pager.module.css | 1 + src/components/layout/Page.module.css | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/common/Pager.module.css b/src/components/common/Pager.module.css index 99eb70ce..70fe2019 100644 --- a/src/components/common/Pager.module.css +++ b/src/components/common/Pager.module.css @@ -1,5 +1,6 @@ .container { margin-top: 20px; + margin-bottom: 20px; } .text { diff --git a/src/components/layout/Page.module.css b/src/components/layout/Page.module.css index c546971b..100be5bb 100644 --- a/src/components/layout/Page.module.css +++ b/src/components/layout/Page.module.css @@ -4,4 +4,5 @@ flex-direction: column; background: var(--base50); position: relative; + height: 100%; }