mirror of
https://github.com/umami-software/umami.git
synced 2025-12-08 05:12:36 +01:00
Compare commits
2 commits
81bedec6d5
...
5ded9abbfe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ded9abbfe | ||
|
|
6751bf88bb |
13 changed files with 168 additions and 87 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "umami",
|
"name": "umami",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"description": "A modern, privacy-focused alternative to Google Analytics.",
|
"description": "A modern, privacy-focused alternative to Google Analytics.",
|
||||||
"author": "Umami Software, Inc. <hello@umami.is>",
|
"author": "Umami Software, Inc. <hello@umami.is>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -78,7 +78,7 @@
|
||||||
"@react-spring/web": "^10.0.3",
|
"@react-spring/web": "^10.0.3",
|
||||||
"@svgr/cli": "^8.1.0",
|
"@svgr/cli": "^8.1.0",
|
||||||
"@tanstack/react-query": "^5.90.5",
|
"@tanstack/react-query": "^5.90.5",
|
||||||
"@umami/react-zen": "^0.206.0",
|
"@umami/react-zen": "^0.207.0",
|
||||||
"@umami/redis-client": "^0.29.0",
|
"@umami/redis-client": "^0.29.0",
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
|
|
|
||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
|
|
@ -45,8 +45,8 @@ importers:
|
||||||
specifier: ^5.90.5
|
specifier: ^5.90.5
|
||||||
version: 5.90.5(react@19.2.0)
|
version: 5.90.5(react@19.2.0)
|
||||||
'@umami/react-zen':
|
'@umami/react-zen':
|
||||||
specifier: ^0.206.0
|
specifier: ^0.207.0
|
||||||
version: 0.206.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0))
|
version: 0.207.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0))
|
||||||
'@umami/redis-client':
|
'@umami/redis-client':
|
||||||
specifier: ^0.29.0
|
specifier: ^0.29.0
|
||||||
version: 0.29.0
|
version: 0.29.0
|
||||||
|
|
@ -2935,8 +2935,8 @@ packages:
|
||||||
resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==}
|
resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@umami/react-zen@0.206.0':
|
'@umami/react-zen@0.207.0':
|
||||||
resolution: {integrity: sha512-9XM3Oj1akdyuwkMT1SldrJOyrMACP9TLJApZ/9ocmPuET4B7vpPxRoxv8OpEVlBaDw5nmlJfIvefsNMBLt1OQg==}
|
resolution: {integrity: sha512-TUllF5mKQ+IBepIgT0xvJo/bGBEdPDfGiATyjuuxe2/SwO3LDINbmPUSFpWrjxtUaigiEI0JaQsklSLkirEKPg==}
|
||||||
|
|
||||||
'@umami/redis-client@0.29.0':
|
'@umami/redis-client@0.29.0':
|
||||||
resolution: {integrity: sha512-Jaqh++jskqDB7ny75pfC02OvKp1JTS4asGDsFrRL3qy8sxL3PAl9+/mybCJe4/6vWrXDJKqpgkSfUDJq2bFjyw==}
|
resolution: {integrity: sha512-Jaqh++jskqDB7ny75pfC02OvKp1JTS4asGDsFrRL3qy8sxL3PAl9+/mybCJe4/6vWrXDJKqpgkSfUDJq2bFjyw==}
|
||||||
|
|
@ -10697,7 +10697,7 @@ snapshots:
|
||||||
'@typescript-eslint/types': 8.46.2
|
'@typescript-eslint/types': 8.46.2
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
|
|
||||||
'@umami/react-zen@0.206.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0))':
|
'@umami/react-zen@0.207.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fontsource/jetbrains-mono': 5.2.8
|
'@fontsource/jetbrains-mono': 5.2.8
|
||||||
'@internationalized/date': 3.10.0
|
'@internationalized/date': 3.10.0
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||||
import { useDateRange } from '@/components/hooks';
|
import { useDateRange, useTimezone } from '@/components/hooks';
|
||||||
import { useWebsitePageviewsQuery } from '@/components/hooks/queries/useWebsitePageviewsQuery';
|
import { useWebsitePageviewsQuery } from '@/components/hooks/queries/useWebsitePageviewsQuery';
|
||||||
import { PageviewsChart } from '@/components/metrics/PageviewsChart';
|
import { PageviewsChart } from '@/components/metrics/PageviewsChart';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
@ -11,7 +11,8 @@ export function WebsiteChart({
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
compareMode?: boolean;
|
compareMode?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { dateRange, dateCompare } = useDateRange();
|
const { timezone } = useTimezone();
|
||||||
|
const { dateRange, dateCompare } = useDateRange({ timezone: timezone });
|
||||||
const { startDate, endDate, unit, value } = dateRange;
|
const { startDate, endDate, unit, value } = dateRange;
|
||||||
const { data, isLoading, isFetching, error } = useWebsitePageviewsQuery({
|
const { data, isLoading, isFetching, error } = useWebsitePageviewsQuery({
|
||||||
websiteId,
|
websiteId,
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ export function useDateParameters() {
|
||||||
const {
|
const {
|
||||||
dateRange: { startDate, endDate, unit },
|
dateRange: { startDate, endDate, unit },
|
||||||
} = useDateRange();
|
} = useDateRange();
|
||||||
const { timezone, toUtc, canonicalizeTimezone } = useTimezone();
|
const { timezone, localToUtc, canonicalizeTimezone } = useTimezone();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startAt: +toUtc(startDate),
|
startAt: +localToUtc(startDate),
|
||||||
endAt: +toUtc(endDate),
|
endAt: +localToUtc(endDate),
|
||||||
startDate: toUtc(startDate).toISOString(),
|
startDate: localToUtc(startDate).toISOString(),
|
||||||
endDate: toUtc(endDate).toISOString(),
|
endDate: localToUtc(endDate).toISOString(),
|
||||||
unit,
|
unit,
|
||||||
timezone: canonicalizeTimezone(timezone),
|
timezone: canonicalizeTimezone(timezone),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { useLocale } from '@/components/hooks/useLocale';
|
||||||
import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants';
|
import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants';
|
||||||
import { getItem } from '@/lib/storage';
|
import { getItem } from '@/lib/storage';
|
||||||
|
|
||||||
export function useDateRange(options: { ignoreOffset?: boolean } = {}) {
|
export function useDateRange(options: { ignoreOffset?: boolean; timezone?: string } = {}) {
|
||||||
const {
|
const {
|
||||||
query: { date = '', offset = 0, compare = 'prev' },
|
query: { date = '', offset = 0, compare = 'prev' },
|
||||||
} = useNavigation();
|
} = useNavigation();
|
||||||
|
|
@ -15,6 +15,7 @@ export function useDateRange(options: { ignoreOffset?: boolean } = {}) {
|
||||||
const dateRangeObject = parseDateRange(
|
const dateRangeObject = parseDateRange(
|
||||||
date || getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE,
|
date || getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE,
|
||||||
locale,
|
locale,
|
||||||
|
options.timezone,
|
||||||
);
|
);
|
||||||
|
|
||||||
return !options.ignoreOffset && offset
|
return !options.ignoreOffset && offset
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ import { TIMEZONE_CONFIG, TIMEZONE_LEGACY } from '@/lib/constants';
|
||||||
import { formatInTimeZone, zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz';
|
import { formatInTimeZone, zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz';
|
||||||
import { useApp, setTimezone } from '@/store/app';
|
import { useApp, setTimezone } from '@/store/app';
|
||||||
import { useLocale } from './useLocale';
|
import { useLocale } from './useLocale';
|
||||||
|
import { getTimezone } from '@/lib/date';
|
||||||
|
|
||||||
const selector = (state: { timezone: string }) => state.timezone;
|
const selector = (state: { timezone: string }) => state.timezone;
|
||||||
|
|
||||||
export function useTimezone() {
|
export function useTimezone() {
|
||||||
const timezone = useApp(selector);
|
const timezone = useApp(selector);
|
||||||
|
const localTimeZone = getTimezone();
|
||||||
const { dateLocale } = useLocale();
|
const { dateLocale } = useLocale();
|
||||||
|
|
||||||
const saveTimezone = (value: string) => {
|
const saveTimezone = (value: string) => {
|
||||||
|
|
@ -26,6 +28,38 @@ export function useTimezone() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatSeriesTimezone = (data: any, column: string, timezone: string) => {
|
||||||
|
return data.map(item => {
|
||||||
|
const date = new Date(item[column]);
|
||||||
|
|
||||||
|
const format = new Intl.DateTimeFormat('en-US', {
|
||||||
|
timeZone: timezone,
|
||||||
|
hour12: false,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
});
|
||||||
|
|
||||||
|
const parts = format.formatToParts(date);
|
||||||
|
const get = type => parts.find(p => p.type === type)?.value;
|
||||||
|
|
||||||
|
const year = get('year');
|
||||||
|
const month = get('month');
|
||||||
|
const day = get('day');
|
||||||
|
const hour = get('hour');
|
||||||
|
const minute = get('minute');
|
||||||
|
const second = get('second');
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
[column]: `${year}-${month}-${day} ${hour}:${minute}:${second}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const toUtc = (date: Date | string | number) => {
|
const toUtc = (date: Date | string | number) => {
|
||||||
return zonedTimeToUtc(date, timezone);
|
return zonedTimeToUtc(date, timezone);
|
||||||
};
|
};
|
||||||
|
|
@ -34,16 +68,28 @@ export function useTimezone() {
|
||||||
return utcToZonedTime(date, timezone);
|
return utcToZonedTime(date, timezone);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const localToUtc = (date: Date | string | number) => {
|
||||||
|
return zonedTimeToUtc(date, localTimeZone);
|
||||||
|
};
|
||||||
|
|
||||||
|
const localFromUtc = (date: Date | string | number) => {
|
||||||
|
return utcToZonedTime(date, localTimeZone);
|
||||||
|
};
|
||||||
|
|
||||||
const canonicalizeTimezone = (timezone: string): string => {
|
const canonicalizeTimezone = (timezone: string): string => {
|
||||||
return TIMEZONE_LEGACY[timezone] ?? timezone;
|
return TIMEZONE_LEGACY[timezone] ?? timezone;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timezone,
|
timezone,
|
||||||
saveTimezone,
|
localTimeZone,
|
||||||
formatTimezoneDate,
|
|
||||||
toUtc,
|
toUtc,
|
||||||
fromUtc,
|
fromUtc,
|
||||||
|
localToUtc,
|
||||||
|
localFromUtc,
|
||||||
|
saveTimezone,
|
||||||
|
formatTimezoneDate,
|
||||||
|
formatSeriesTimezone,
|
||||||
canonicalizeTimezone,
|
canonicalizeTimezone,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
import { BarChart, BarChartProps } from '@/components/charts/BarChart';
|
import { BarChart, BarChartProps } from '@/components/charts/BarChart';
|
||||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||||
import { useDateRange, useLocale, useWebsiteEventsSeriesQuery } from '@/components/hooks';
|
import {
|
||||||
|
useDateRange,
|
||||||
|
useLocale,
|
||||||
|
useTimezone,
|
||||||
|
useWebsiteEventsSeriesQuery,
|
||||||
|
} from '@/components/hooks';
|
||||||
import { renderDateLabels } from '@/lib/charts';
|
import { renderDateLabels } from '@/lib/charts';
|
||||||
import { CHART_COLORS } from '@/lib/constants';
|
import { CHART_COLORS } from '@/lib/constants';
|
||||||
import { generateTimeSeries } from '@/lib/date';
|
import { generateTimeSeries } from '@/lib/date';
|
||||||
|
|
@ -13,9 +18,10 @@ export interface EventsChartProps extends BarChartProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EventsChart({ websiteId, focusLabel }: EventsChartProps) {
|
export function EventsChart({ websiteId, focusLabel }: EventsChartProps) {
|
||||||
|
const { timezone } = useTimezone();
|
||||||
const {
|
const {
|
||||||
dateRange: { startDate, endDate, unit },
|
dateRange: { startDate, endDate, unit },
|
||||||
} = useDateRange();
|
} = useDateRange({ timezone: timezone });
|
||||||
const { locale, dateLocale } = useLocale();
|
const { locale, dateLocale } = useLocale();
|
||||||
const { data, isLoading, error } = useWebsiteEventsSeriesQuery(websiteId);
|
const { data, isLoading, error } = useWebsiteEventsSeriesQuery(websiteId);
|
||||||
const [label, setLabel] = useState<string>(focusLabel);
|
const [label, setLabel] = useState<string>(focusLabel);
|
||||||
|
|
@ -33,20 +39,32 @@ export function EventsChart({ websiteId, focusLabel }: EventsChartProps) {
|
||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
return {
|
if (!map || Object.keys(map).length === 0) {
|
||||||
datasets: Object.keys(map).map((key, index) => {
|
return {
|
||||||
const color = colord(CHART_COLORS[index % CHART_COLORS.length]);
|
datasets: [
|
||||||
return {
|
{
|
||||||
label: key,
|
data: generateTimeSeries([], startDate, endDate, unit, dateLocale),
|
||||||
data: generateTimeSeries(map[key], startDate, endDate, unit, dateLocale),
|
lineTension: 0,
|
||||||
lineTension: 0,
|
borderWidth: 1,
|
||||||
backgroundColor: color.alpha(0.6).toRgbString(),
|
},
|
||||||
borderColor: color.alpha(0.7).toRgbString(),
|
],
|
||||||
borderWidth: 1,
|
};
|
||||||
};
|
} else {
|
||||||
}),
|
return {
|
||||||
focusLabel,
|
datasets: Object.keys(map).map((key, index) => {
|
||||||
};
|
const color = colord(CHART_COLORS[index % CHART_COLORS.length]);
|
||||||
|
return {
|
||||||
|
label: key,
|
||||||
|
data: generateTimeSeries(map[key], startDate, endDate, unit, dateLocale),
|
||||||
|
lineTension: 0,
|
||||||
|
backgroundColor: color.alpha(0.6).toRgbString(),
|
||||||
|
borderColor: color.alpha(0.7).toRgbString(),
|
||||||
|
borderWidth: 1,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
focusLabel,
|
||||||
|
};
|
||||||
|
}
|
||||||
}, [data, startDate, endDate, unit, focusLabel]);
|
}, [data, startDate, endDate, unit, focusLabel]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { startOfMinute, subMinutes, isBefore } from 'date-fns';
|
||||||
import { PageviewsChart } from './PageviewsChart';
|
import { PageviewsChart } from './PageviewsChart';
|
||||||
import { DEFAULT_ANIMATION_DURATION, REALTIME_RANGE } from '@/lib/constants';
|
import { DEFAULT_ANIMATION_DURATION, REALTIME_RANGE } from '@/lib/constants';
|
||||||
import { RealtimeData } from '@/lib/types';
|
import { RealtimeData } from '@/lib/types';
|
||||||
|
import { useTimezone } from '@/components/hooks';
|
||||||
|
|
||||||
export interface RealtimeChartProps {
|
export interface RealtimeChartProps {
|
||||||
data: RealtimeData;
|
data: RealtimeData;
|
||||||
|
|
@ -11,6 +12,7 @@ export interface RealtimeChartProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RealtimeChart({ data, unit, ...props }: RealtimeChartProps) {
|
export function RealtimeChart({ data, unit, ...props }: RealtimeChartProps) {
|
||||||
|
const { formatSeriesTimezone, fromUtc, timezone } = useTimezone();
|
||||||
const endDate = startOfMinute(new Date());
|
const endDate = startOfMinute(new Date());
|
||||||
const startDate = subMinutes(endDate, REALTIME_RANGE);
|
const startDate = subMinutes(endDate, REALTIME_RANGE);
|
||||||
const prevEndDate = useRef(endDate);
|
const prevEndDate = useRef(endDate);
|
||||||
|
|
@ -21,8 +23,8 @@ export function RealtimeChart({ data, unit, ...props }: RealtimeChartProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pageviews: data.series.views,
|
pageviews: formatSeriesTimezone(data.series.views, 'x', timezone),
|
||||||
sessions: data.series.visitors,
|
sessions: formatSeriesTimezone(data.series.visitors, 'x', timezone),
|
||||||
};
|
};
|
||||||
}, [data, startDate, endDate, unit]);
|
}, [data, startDate, endDate, unit]);
|
||||||
|
|
||||||
|
|
@ -38,8 +40,8 @@ export function RealtimeChart({ data, unit, ...props }: RealtimeChartProps) {
|
||||||
return (
|
return (
|
||||||
<PageviewsChart
|
<PageviewsChart
|
||||||
{...props}
|
{...props}
|
||||||
minDate={startDate}
|
minDate={fromUtc(startDate)}
|
||||||
maxDate={endDate}
|
maxDate={fromUtc(endDate)}
|
||||||
unit={unit}
|
unit={unit}
|
||||||
data={chartData}
|
data={chartData}
|
||||||
animationDuration={animationDuration}
|
animationDuration={animationDuration}
|
||||||
|
|
|
||||||
|
|
@ -51,12 +51,12 @@ function getUTCString(date?: Date | string | number) {
|
||||||
return formatInTimeZone(date || new Date(), 'UTC', 'yyyy-MM-dd HH:mm:ss');
|
return formatInTimeZone(date || new Date(), 'UTC', 'yyyy-MM-dd HH:mm:ss');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDateStringSQL(data: any, unit: string = 'utc', timezone?: string) {
|
function getDateStringSQL(field: string, unit: string, timezone?: string) {
|
||||||
if (timezone) {
|
if (timezone) {
|
||||||
return `formatDateTime(${data}, '${CLICKHOUSE_DATE_FORMATS[unit]}', '${timezone}')`;
|
return `formatDateTime(${field}, '${CLICKHOUSE_DATE_FORMATS[unit]}', '${timezone}')`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `formatDateTime(${data}, '${CLICKHOUSE_DATE_FORMATS[unit]}')`;
|
return `formatDateTime(${field}, '${CLICKHOUSE_DATE_FORMATS[unit]}')`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDateSQL(field: string, unit: string, timezone?: string) {
|
function getDateSQL(field: string, unit: string, timezone?: string) {
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,45 @@
|
||||||
import {
|
|
||||||
addMinutes,
|
|
||||||
addHours,
|
|
||||||
addDays,
|
|
||||||
addMonths,
|
|
||||||
addYears,
|
|
||||||
subMinutes,
|
|
||||||
subHours,
|
|
||||||
subDays,
|
|
||||||
subMonths,
|
|
||||||
subYears,
|
|
||||||
startOfMinute,
|
|
||||||
startOfHour,
|
|
||||||
startOfDay,
|
|
||||||
startOfWeek,
|
|
||||||
startOfMonth,
|
|
||||||
startOfYear,
|
|
||||||
endOfHour,
|
|
||||||
endOfDay,
|
|
||||||
endOfWeek,
|
|
||||||
endOfMonth,
|
|
||||||
endOfYear,
|
|
||||||
differenceInMinutes,
|
|
||||||
differenceInHours,
|
|
||||||
differenceInCalendarDays,
|
|
||||||
differenceInCalendarWeeks,
|
|
||||||
differenceInCalendarMonths,
|
|
||||||
differenceInCalendarYears,
|
|
||||||
format,
|
|
||||||
max,
|
|
||||||
min,
|
|
||||||
isDate,
|
|
||||||
addWeeks,
|
|
||||||
subWeeks,
|
|
||||||
endOfMinute,
|
|
||||||
isSameDay,
|
|
||||||
isBefore,
|
|
||||||
isEqual,
|
|
||||||
} from 'date-fns';
|
|
||||||
import { getDateLocale } from '@/lib/lang';
|
import { getDateLocale } from '@/lib/lang';
|
||||||
import { DateRange } from '@/lib/types';
|
import { DateRange } from '@/lib/types';
|
||||||
|
import {
|
||||||
|
addDays,
|
||||||
|
addHours,
|
||||||
|
addMinutes,
|
||||||
|
addMonths,
|
||||||
|
addWeeks,
|
||||||
|
addYears,
|
||||||
|
differenceInCalendarDays,
|
||||||
|
differenceInCalendarMonths,
|
||||||
|
differenceInCalendarWeeks,
|
||||||
|
differenceInCalendarYears,
|
||||||
|
differenceInHours,
|
||||||
|
differenceInMinutes,
|
||||||
|
endOfDay,
|
||||||
|
endOfHour,
|
||||||
|
endOfMinute,
|
||||||
|
endOfMonth,
|
||||||
|
endOfWeek,
|
||||||
|
endOfYear,
|
||||||
|
format,
|
||||||
|
isBefore,
|
||||||
|
isDate,
|
||||||
|
isEqual,
|
||||||
|
isSameDay,
|
||||||
|
max,
|
||||||
|
min,
|
||||||
|
startOfDay,
|
||||||
|
startOfHour,
|
||||||
|
startOfMinute,
|
||||||
|
startOfMonth,
|
||||||
|
startOfWeek,
|
||||||
|
startOfYear,
|
||||||
|
subDays,
|
||||||
|
subHours,
|
||||||
|
subMinutes,
|
||||||
|
subMonths,
|
||||||
|
subWeeks,
|
||||||
|
subYears,
|
||||||
|
} from 'date-fns';
|
||||||
|
import { utcToZonedTime } from 'date-fns-tz';
|
||||||
|
|
||||||
export const TIME_UNIT = {
|
export const TIME_UNIT = {
|
||||||
minute: 'minute',
|
minute: 'minute',
|
||||||
|
|
@ -135,7 +136,7 @@ export function parseDateValue(value: string) {
|
||||||
return { num: +num, unit };
|
return { num: +num, unit };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseDateRange(value: string, locale = 'en-US'): DateRange {
|
export function parseDateRange(value: string, locale = 'en-US', timezone?: string): DateRange {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +157,8 @@ export function parseDateRange(value: string, locale = 'en-US'): DateRange {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = new Date();
|
const date = new Date();
|
||||||
|
const now = timezone ? utcToZonedTime(date, timezone) : date;
|
||||||
const dateLocale = getDateLocale(locale);
|
const dateLocale = getDateLocale(locale);
|
||||||
const { num = 1, unit } = parseDateValue(value);
|
const { num = 1, unit } = parseDateValue(value);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,14 @@ const DATE_FORMATS = {
|
||||||
year: 'YYYY-01-01 HH24:00:00',
|
year: 'YYYY-01-01 HH24:00:00',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DATE_FORMATS_UTC = {
|
||||||
|
minute: 'YYYY-MM-DD"T"HH24:MI:00"Z"',
|
||||||
|
hour: 'YYYY-MM-DD"T"HH24:00:00"Z"',
|
||||||
|
day: 'YYYY-MM-DD"T"HH24:00:00"Z"',
|
||||||
|
month: 'YYYY-MM-01"T"HH24:00:00"Z"',
|
||||||
|
year: 'YYYY-01-01"T"HH24:00:00"Z"',
|
||||||
|
};
|
||||||
|
|
||||||
function getAddIntervalQuery(field: string, interval: string): string {
|
function getAddIntervalQuery(field: string, interval: string): string {
|
||||||
return `${field} + interval '${interval}'`;
|
return `${field} + interval '${interval}'`;
|
||||||
}
|
}
|
||||||
|
|
@ -40,11 +48,11 @@ function getCastColumnQuery(field: string, type: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDateSQL(field: string, unit: string, timezone?: string): string {
|
function getDateSQL(field: string, unit: string, timezone?: string): string {
|
||||||
if (timezone) {
|
if (timezone && timezone !== 'utc') {
|
||||||
return `to_char(date_trunc('${unit}', ${field} at time zone '${timezone}'), '${DATE_FORMATS[unit]}')`;
|
return `to_char(date_trunc('${unit}', ${field} at time zone '${timezone}'), '${DATE_FORMATS[unit]}')`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `to_char(date_trunc('${unit}', ${field}), '${DATE_FORMATS[unit]}')`;
|
return `to_char(date_trunc('${unit}', ${field}), '${DATE_FORMATS_UTC[unit]}')`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDateWeeklySQL(field: string, timezone?: string) {
|
function getDateWeeklySQL(field: string, timezone?: string) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { getPageviewStats } from '@/queries/sql/pageviews/getPageviewStats';
|
|
||||||
import { getRealtimeActivity } from '@/queries/sql/getRealtimeActivity';
|
|
||||||
import { getSessionStats } from '@/queries/sql/sessions/getSessionStats';
|
|
||||||
import { QueryFilters } from '@/lib/types';
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
import { getRealtimeActivity } from '@/queries/sql/getRealtimeActivity';
|
||||||
|
import { getPageviewStats } from '@/queries/sql/pageviews/getPageviewStats';
|
||||||
|
import { getSessionStats } from '@/queries/sql/sessions/getSessionStats';
|
||||||
|
|
||||||
function increment(data: object, key: string) {
|
function increment(data: object, key: string) {
|
||||||
if (key) {
|
if (key) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
const _false = 'false';
|
const _false = 'false';
|
||||||
const _true = 'true';
|
const _true = 'true';
|
||||||
const attr = currentScript.getAttribute.bind(currentScript);
|
const attr = currentScript.getAttribute.bind(currentScript);
|
||||||
|
|
||||||
const website = attr(_data + 'website-id');
|
const website = attr(_data + 'website-id');
|
||||||
const hostUrl = attr(_data + 'host-url');
|
const hostUrl = attr(_data + 'host-url');
|
||||||
const beforeSend = attr(_data + 'before-send');
|
const beforeSend = attr(_data + 'before-send');
|
||||||
|
|
@ -27,6 +28,8 @@
|
||||||
const excludeSearch = attr(_data + 'exclude-search') === _true;
|
const excludeSearch = attr(_data + 'exclude-search') === _true;
|
||||||
const excludeHash = attr(_data + 'exclude-hash') === _true;
|
const excludeHash = attr(_data + 'exclude-hash') === _true;
|
||||||
const domain = attr(_data + 'domains') || '';
|
const domain = attr(_data + 'domains') || '';
|
||||||
|
const credentials = attr(_data + 'fetch-credentials') || 'omit';
|
||||||
|
|
||||||
const domains = domain.split(',').map(n => n.trim());
|
const domains = domain.split(',').map(n => n.trim());
|
||||||
const host =
|
const host =
|
||||||
hostUrl || '__COLLECT_API_HOST__' || currentScript.src.split('/').slice(0, -1).join('/');
|
hostUrl || '__COLLECT_API_HOST__' || currentScript.src.split('/').slice(0, -1).join('/');
|
||||||
|
|
@ -165,7 +168,7 @@
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
...(typeof cache !== 'undefined' && { 'x-umami-cache': cache }),
|
...(typeof cache !== 'undefined' && { 'x-umami-cache': cache }),
|
||||||
},
|
},
|
||||||
credentials: 'omit',
|
credentials,
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue