Compare commits

..

No commits in common. "6751bf88bbc610de8d7f3bda11abb1aeb11e25b0" and "9fbcec46af355034a4c072dbe9018bef2d8a61b7" have entirely different histories.

11 changed files with 81 additions and 151 deletions

View file

@ -1,5 +1,5 @@
import { LoadingPanel } from '@/components/common/LoadingPanel'; import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useDateRange, useTimezone } from '@/components/hooks'; import { useDateRange } 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,8 +11,7 @@ export function WebsiteChart({
websiteId: string; websiteId: string;
compareMode?: boolean; compareMode?: boolean;
}) { }) {
const { timezone } = useTimezone(); const { dateRange, dateCompare } = useDateRange();
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,

View file

@ -5,13 +5,13 @@ export function useDateParameters() {
const { const {
dateRange: { startDate, endDate, unit }, dateRange: { startDate, endDate, unit },
} = useDateRange(); } = useDateRange();
const { timezone, localToUtc, canonicalizeTimezone } = useTimezone(); const { timezone, toUtc, canonicalizeTimezone } = useTimezone();
return { return {
startAt: +localToUtc(startDate), startAt: +toUtc(startDate),
endAt: +localToUtc(endDate), endAt: +toUtc(endDate),
startDate: localToUtc(startDate).toISOString(), startDate: toUtc(startDate).toISOString(),
endDate: localToUtc(endDate).toISOString(), endDate: toUtc(endDate).toISOString(),
unit, unit,
timezone: canonicalizeTimezone(timezone), timezone: canonicalizeTimezone(timezone),
}; };

View file

@ -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; timezone?: string } = {}) { export function useDateRange(options: { ignoreOffset?: boolean } = {}) {
const { const {
query: { date = '', offset = 0, compare = 'prev' }, query: { date = '', offset = 0, compare = 'prev' },
} = useNavigation(); } = useNavigation();
@ -15,7 +15,6 @@ export function useDateRange(options: { ignoreOffset?: boolean; timezone?: strin
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

View file

@ -3,13 +3,11 @@ 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) => {
@ -28,38 +26,6 @@ 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);
}; };
@ -68,28 +34,16 @@ 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,
localTimeZone,
toUtc,
fromUtc,
localToUtc,
localFromUtc,
saveTimezone, saveTimezone,
formatTimezoneDate, formatTimezoneDate,
formatSeriesTimezone, toUtc,
fromUtc,
canonicalizeTimezone, canonicalizeTimezone,
}; };
} }

View file

@ -1,11 +1,6 @@
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 { import { useDateRange, useLocale, useWebsiteEventsSeriesQuery } from '@/components/hooks';
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';
@ -18,10 +13,9 @@ 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({ timezone: timezone }); } = useDateRange();
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);
@ -39,32 +33,20 @@ export function EventsChart({ websiteId, focusLabel }: EventsChartProps) {
return obj; return obj;
}, {}); }, {});
if (!map || Object.keys(map).length === 0) { return {
return { datasets: Object.keys(map).map((key, index) => {
datasets: [ const color = colord(CHART_COLORS[index % CHART_COLORS.length]);
{ return {
data: generateTimeSeries([], startDate, endDate, unit, dateLocale), label: key,
lineTension: 0, data: generateTimeSeries(map[key], startDate, endDate, unit, dateLocale),
borderWidth: 1, lineTension: 0,
}, backgroundColor: color.alpha(0.6).toRgbString(),
], borderColor: color.alpha(0.7).toRgbString(),
}; borderWidth: 1,
} else { };
return { }),
datasets: Object.keys(map).map((key, index) => { focusLabel,
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(() => {

View file

@ -25,16 +25,16 @@ export function MetricLabel({ type, data }: MetricLabelProps) {
const { getRegionName } = useRegionNames(locale); const { getRegionName } = useRegionNames(locale);
const { label, country, domain } = data; const { label, country, domain } = data;
const isType = ['browser', 'country', 'device', 'os'].includes(type);
switch (type) { switch (type) {
case 'browser': case 'browser':
case 'os':
return ( return (
<FilterLink <FilterLink
type={type} type="browser"
value={label} value={label}
label={formatValue(label, type)} label={formatValue(label, 'browser')}
icon={<TypeIcon type={type} value={label} />} icon={<TypeIcon type="browser" value={label} />}
/> />
); );
@ -100,7 +100,7 @@ export function MetricLabel({ type, data }: MetricLabelProps) {
type="device" type="device"
value={labels[label] && label} value={labels[label] && label}
label={formatValue(label, 'device')} label={formatValue(label, 'device')}
icon={<TypeIcon type="device" value={label} />} icon={<TypeIcon type="device" value={label?.toLowerCase()} />}
/> />
); );
@ -141,6 +141,14 @@ export function MetricLabel({ type, data }: MetricLabelProps) {
<FilterLink <FilterLink
type={type} type={type}
value={label} value={label}
icon={
isType && (
<TypeIcon
type={type as 'browser' | 'country' | 'device' | 'os'}
value={label?.toLowerCase()?.replaceAll(/\W/g, '-')}
/>
)
}
/> />
); );
} }

View file

@ -3,7 +3,6 @@ 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;
@ -12,7 +11,6 @@ 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);
@ -23,8 +21,8 @@ export function RealtimeChart({ data, unit, ...props }: RealtimeChartProps) {
} }
return { return {
pageviews: formatSeriesTimezone(data.series.views, 'x', timezone), pageviews: data.series.views,
sessions: formatSeriesTimezone(data.series.visitors, 'x', timezone), sessions: data.series.visitors,
}; };
}, [data, startDate, endDate, unit]); }, [data, startDate, endDate, unit]);
@ -40,8 +38,8 @@ export function RealtimeChart({ data, unit, ...props }: RealtimeChartProps) {
return ( return (
<PageviewsChart <PageviewsChart
{...props} {...props}
minDate={fromUtc(startDate)} minDate={startDate}
maxDate={fromUtc(endDate)} maxDate={endDate}
unit={unit} unit={unit}
data={chartData} data={chartData}
animationDuration={animationDuration} animationDuration={animationDuration}

View file

@ -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(field: string, unit: string, timezone?: string) { function getDateStringSQL(data: any, unit: string = 'utc', timezone?: string) {
if (timezone) { if (timezone) {
return `formatDateTime(${field}, '${CLICKHOUSE_DATE_FORMATS[unit]}', '${timezone}')`; return `formatDateTime(${data}, '${CLICKHOUSE_DATE_FORMATS[unit]}', '${timezone}')`;
} }
return `formatDateTime(${field}, '${CLICKHOUSE_DATE_FORMATS[unit]}')`; return `formatDateTime(${data}, '${CLICKHOUSE_DATE_FORMATS[unit]}')`;
} }
function getDateSQL(field: string, unit: string, timezone?: string) { function getDateSQL(field: string, unit: string, timezone?: string) {

View file

@ -1,45 +1,44 @@
import { getDateLocale } from '@/lib/lang';
import { DateRange } from '@/lib/types';
import { import {
addDays,
addHours,
addMinutes, addMinutes,
addHours,
addDays,
addMonths, addMonths,
addWeeks,
addYears, addYears,
differenceInCalendarDays, subMinutes,
differenceInCalendarMonths, subHours,
differenceInCalendarWeeks, subDays,
differenceInCalendarYears, subMonths,
differenceInHours, subYears,
differenceInMinutes, startOfMinute,
endOfDay, startOfHour,
startOfDay,
startOfWeek,
startOfMonth,
startOfYear,
endOfHour, endOfHour,
endOfMinute, endOfDay,
endOfMonth,
endOfWeek, endOfWeek,
endOfMonth,
endOfYear, endOfYear,
differenceInMinutes,
differenceInHours,
differenceInCalendarDays,
differenceInCalendarWeeks,
differenceInCalendarMonths,
differenceInCalendarYears,
format, format,
isBefore,
isDate,
isEqual,
isSameDay,
max, max,
min, min,
startOfDay, isDate,
startOfHour, addWeeks,
startOfMinute,
startOfMonth,
startOfWeek,
startOfYear,
subDays,
subHours,
subMinutes,
subMonths,
subWeeks, subWeeks,
subYears, endOfMinute,
isSameDay,
isBefore,
isEqual,
} from 'date-fns'; } from 'date-fns';
import { utcToZonedTime } from 'date-fns-tz'; import { getDateLocale } from '@/lib/lang';
import { DateRange } from '@/lib/types';
export const TIME_UNIT = { export const TIME_UNIT = {
minute: 'minute', minute: 'minute',
@ -136,7 +135,7 @@ export function parseDateValue(value: string) {
return { num: +num, unit }; return { num: +num, unit };
} }
export function parseDateRange(value: string, locale = 'en-US', timezone?: string): DateRange { export function parseDateRange(value: string, locale = 'en-US'): DateRange {
if (typeof value !== 'string') { if (typeof value !== 'string') {
return null; return null;
} }
@ -157,8 +156,7 @@ export function parseDateRange(value: string, locale = 'en-US', timezone?: strin
}; };
} }
const date = new Date(); const now = 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);

View file

@ -27,14 +27,6 @@ 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}'`;
} }
@ -48,11 +40,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 && timezone !== 'utc') { if (timezone) {
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_UTC[unit]}')`; return `to_char(date_trunc('${unit}', ${field}), '${DATE_FORMATS[unit]}')`;
} }
function getDateWeeklySQL(field: string, timezone?: string) { function getDateWeeklySQL(field: string, timezone?: string) {

View file

@ -1,7 +1,7 @@
import { QueryFilters } from '@/lib/types';
import { getRealtimeActivity } from '@/queries/sql/getRealtimeActivity';
import { getPageviewStats } from '@/queries/sql/pageviews/getPageviewStats'; import { getPageviewStats } from '@/queries/sql/pageviews/getPageviewStats';
import { getRealtimeActivity } from '@/queries/sql/getRealtimeActivity';
import { getSessionStats } from '@/queries/sql/sessions/getSessionStats'; import { getSessionStats } from '@/queries/sql/sessions/getSessionStats';
import { QueryFilters } from '@/lib/types';
function increment(data: object, key: string) { function increment(data: object, key: string) {
if (key) { if (key) {