Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/dev' into dev
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/components/common/Pager.module.css
#	src/lib/constants.ts
#	src/lib/yup.ts
#	src/pages/api/teams/[id]/users/index.ts
#	src/pages/api/websites/[id]/reports.ts
  • Loading branch information
mikecao committed Sep 27, 2023
2 parents 7e626dc + 8e8bf41 commit 40cfcd4
Show file tree
Hide file tree
Showing 19 changed files with 134 additions and 91 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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 <mike@mikecao.com>",
"license": "MIT",
Expand Down
7 changes: 3 additions & 4 deletions src/components/input/MonthSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -40,7 +39,7 @@ export function MonthSelect({ date = new Date(), onChange }) {
{close => (
<CalendarMonthSelect
date={date}
locale={getDateLocale(locale)}
locale={dateLocale}
onSelect={handleChange.bind(null, close)}
/>
)}
Expand All @@ -57,7 +56,7 @@ export function MonthSelect({ date = new Date(), onChange }) {
{close => (
<CalendarYearSelect
date={date}
locale={getDateLocale(locale)}
locale={dateLocale}
onSelect={handleChange.bind(null, close)}
/>
)}
Expand Down
1 change: 1 addition & 0 deletions src/components/layout/Page.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
flex-direction: column;
background: var(--base50);
position: relative;
height: 100%;
}
14 changes: 7 additions & 7 deletions src/components/metrics/DatePickerForm.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
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';
import useMessages from 'components/hooks/useMessages';
import styles from './DatePickerForm.module.css';
Expand All @@ -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 =
Expand All @@ -31,9 +30,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()}`);
}
};

Expand All @@ -51,6 +50,7 @@ export function DatePickerForm({
date={singleDate}
minDate={minDate}
maxDate={maxDate}
locale={dateLocale}
onChange={setSingleDate}
/>
)}
Expand All @@ -60,14 +60,14 @@ export function DatePickerForm({
date={startDate}
minDate={minDate}
maxDate={endDate}
locale={getDateLocale(locale)}
locale={dateLocale}
onChange={setStartDate}
/>
<Calendar
date={endDate}
minDate={startDate}
maxDate={maxDate}
locale={getDateLocale(locale)}
locale={dateLocale}
onChange={setEndDate}
/>
</>
Expand Down
2 changes: 1 addition & 1 deletion src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const FILTER_DAY = 'filter-day';
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 EVENT_COLUMNS = ['url', 'referrer', 'title', 'query', 'event'];

export const SESSION_COLUMNS = [
Expand Down
12 changes: 3 additions & 9 deletions src/lib/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
}
Expand Down Expand Up @@ -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('-');
Expand Down
15 changes: 15 additions & 0 deletions src/lib/yup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import moment from 'moment';
import * as yup from 'yup';
import { UNIT_TYPES } from './constants';

export const TimezoneTest = yup.string().test(
'timezone',
() => `Invalid timezone`,
value => moment.tz.zone(value) !== null,
);

export const UnitTypeTest = yup.string().test(
'unit',
() => `Invalid unit`,
value => UNIT_TYPES.includes(value),
);
7 changes: 5 additions & 2 deletions src/pages/api/reports/retention.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
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';
import * as yup from 'yup';

export interface RetentionRequestBody {
websiteId: string;
dateRange: { startDate: string; endDate: string };
dateRange: { startDate: string; endDate: string; timezone: string };
}

const schema = {
Expand All @@ -19,6 +20,7 @@ const schema = {
.shape({
startDate: yup.date().required(),
endDate: yup.date().required(),
timezone: TimezoneTest,
})
.required(),
}),
Expand All @@ -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))) {
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/pages/api/teams/[id]/users/[userId].ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
16 changes: 10 additions & 6 deletions src/pages/api/teams/[id]/users/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as yup from 'yup';
import { canViewTeam } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
Expand All @@ -9,16 +10,19 @@ 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<TeamUserRequestQuery, TeamUserRequestBody>,
req: NextApiRequestQueryBody<TeamUserRequestQuery, any>,
res: NextApiResponse,
) => {
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);

const { id: teamId } = req.query;

Expand Down
25 changes: 9 additions & 16 deletions src/pages/api/websites/[id]/events.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
import { WebsiteMetric, NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import moment from 'moment-timezone';
import { parseDateRangeQuery } from 'lib/query';
import { NextApiRequestQueryBody, WebsiteMetric } from 'lib/types';
import { TimezoneTest, UnitTypeTest } from 'lib/yup';
import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getEventMetrics } from 'queries';
import { parseDateRangeQuery } from 'lib/query';

const unitTypes = ['year', 'month', 'hour', 'day'];
import * as yup from 'yup';

export interface WebsiteEventsRequestQuery {
id: string;
startAt: string;
endAt: string;
unit: string;
timezone: string;
unit?: string;
timezone?: string;
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(),
unit: UnitTypeTest,
timezone: TimezoneTest,
url: yup.string(),
}),
};
Expand All @@ -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,
Expand Down
10 changes: 6 additions & 4 deletions src/pages/api/websites/[id]/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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) {
Expand Down
12 changes: 12 additions & 0 deletions src/pages/api/websites/[id]/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}),
};

Expand Down
31 changes: 20 additions & 11 deletions src/pages/api/websites/[id]/pageviews.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -24,10 +23,24 @@ export interface WebsitePageviewRequestQuery {
city?: string;
}

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: UnitTypeTest,
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(),
}),
};

Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 40cfcd4

Please sign in to comment.