improved date range selector with hotkeys
This commit is contained in:
@@ -2,6 +2,59 @@ import { isSameDay, isSameMonth } from 'date-fns';
|
||||
|
||||
export const NOT_SET_VALUE = '(not set)';
|
||||
|
||||
export const timeWindows = {
|
||||
'30min': {
|
||||
key: '30min',
|
||||
label: 'Last 30 min',
|
||||
shortcut: 'R',
|
||||
},
|
||||
lastHour: {
|
||||
key: 'lastHour',
|
||||
label: 'Last hour',
|
||||
shortcut: 'H',
|
||||
},
|
||||
today: {
|
||||
key: 'today',
|
||||
label: 'Today',
|
||||
shortcut: 'D',
|
||||
},
|
||||
'7d': {
|
||||
key: '7d',
|
||||
label: 'Last 7 days',
|
||||
shortcut: 'W',
|
||||
},
|
||||
'30d': {
|
||||
key: '30d',
|
||||
label: 'Last 30 days',
|
||||
shortcut: 'T',
|
||||
},
|
||||
monthToDate: {
|
||||
key: 'monthToDate',
|
||||
label: 'Month to Date',
|
||||
shortcut: 'M',
|
||||
},
|
||||
lastMonth: {
|
||||
key: 'lastMonth',
|
||||
label: 'Last Month',
|
||||
shortcut: 'P',
|
||||
},
|
||||
yearToDate: {
|
||||
key: 'yearToDate',
|
||||
label: 'Year to Date',
|
||||
shortcut: 'Y',
|
||||
},
|
||||
lastYear: {
|
||||
key: 'lastYear',
|
||||
label: 'Last year',
|
||||
shortcut: 'U',
|
||||
},
|
||||
custom: {
|
||||
key: 'custom',
|
||||
label: 'Custom range',
|
||||
shortcut: 'C',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const ProjectTypeNames = {
|
||||
website: 'Website',
|
||||
app: 'App',
|
||||
@@ -64,7 +117,7 @@ export const alphabetIds = [
|
||||
'J',
|
||||
] as const;
|
||||
|
||||
export const timeRanges = {
|
||||
export const deprecated_timeRanges = {
|
||||
'30min': '30min',
|
||||
'1h': '1h',
|
||||
today: 'today',
|
||||
@@ -84,26 +137,29 @@ export const metrics = {
|
||||
max: 'max',
|
||||
} as const;
|
||||
|
||||
export function isMinuteIntervalEnabledByRange(range: keyof typeof timeRanges) {
|
||||
return range === '30min' || range === '1h';
|
||||
export function isMinuteIntervalEnabledByRange(
|
||||
range: keyof typeof timeWindows
|
||||
) {
|
||||
return range === '30min' || range === 'lastHour';
|
||||
}
|
||||
|
||||
export function isHourIntervalEnabledByRange(range: keyof typeof timeRanges) {
|
||||
return (
|
||||
isMinuteIntervalEnabledByRange(range) ||
|
||||
range === 'today' ||
|
||||
range === '24h'
|
||||
);
|
||||
export function isHourIntervalEnabledByRange(range: keyof typeof timeWindows) {
|
||||
return isMinuteIntervalEnabledByRange(range) || range === 'today';
|
||||
}
|
||||
|
||||
export function getDefaultIntervalByRange(
|
||||
range: keyof typeof timeRanges
|
||||
range: keyof typeof timeWindows
|
||||
): keyof typeof intervals {
|
||||
if (range === '30min' || range === '1h') {
|
||||
if (range === '30min' || range === 'lastHour') {
|
||||
return 'minute';
|
||||
} else if (range === 'today' || range === '24h') {
|
||||
} else if (range === 'today') {
|
||||
return 'hour';
|
||||
} else if (range === '7d' || range === '14d' || range === '1m') {
|
||||
} else if (
|
||||
range === '7d' ||
|
||||
range === '30d' ||
|
||||
range === 'lastMonth' ||
|
||||
range === 'monthToDate'
|
||||
) {
|
||||
return 'day';
|
||||
}
|
||||
return 'month';
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "reports" ALTER COLUMN "range" SET DEFAULT '30d';
|
||||
@@ -163,7 +163,7 @@ model Report {
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
name String
|
||||
interval Interval
|
||||
range String @default("1m")
|
||||
range String @default("30d")
|
||||
chartType ChartType
|
||||
lineType String @default("monotone")
|
||||
breakdowns Json
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { alphabetIds, lineTypes, timeRanges } from '@openpanel/constants';
|
||||
import {
|
||||
alphabetIds,
|
||||
deprecated_timeRanges,
|
||||
lineTypes,
|
||||
} from '@openpanel/constants';
|
||||
import type {
|
||||
IChartBreakdown,
|
||||
IChartEvent,
|
||||
@@ -52,7 +56,10 @@ export function transformReport(
|
||||
lineType: (report.lineType as IChartLineType) ?? lineTypes.monotone,
|
||||
interval: report.interval,
|
||||
name: report.name || 'Untitled',
|
||||
range: (report.range as IChartRange) ?? timeRanges['1m'],
|
||||
range:
|
||||
report.range in deprecated_timeRanges
|
||||
? '30d'
|
||||
: (report.range as IChartRange),
|
||||
previous: report.previous ?? false,
|
||||
formula: report.formula ?? undefined,
|
||||
metric: report.metric ?? 'sum',
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { subDays } from 'date-fns';
|
||||
import {
|
||||
endOfDay,
|
||||
endOfYear,
|
||||
startOfDay,
|
||||
startOfMonth,
|
||||
startOfYear,
|
||||
subDays,
|
||||
subMinutes,
|
||||
subMonths,
|
||||
subYears,
|
||||
} from 'date-fns';
|
||||
import * as mathjs from 'mathjs';
|
||||
import { repeat, reverse, sort } from 'ramda';
|
||||
import { escape } from 'sqlstring';
|
||||
@@ -298,22 +308,9 @@ export async function getChartData(payload: IGetChartDataInput) {
|
||||
}
|
||||
|
||||
export function getDatesFromRange(range: IChartRange) {
|
||||
if (range === 'today') {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
startDate.setUTCHours(0, 0, 0, 0);
|
||||
endDate.setUTCHours(23, 59, 59, 999);
|
||||
|
||||
return {
|
||||
startDate: startDate.toUTCString(),
|
||||
endDate: endDate.toUTCString(),
|
||||
};
|
||||
}
|
||||
|
||||
if (range === '30min' || range === '1h') {
|
||||
const startDate = new Date(
|
||||
Date.now() - 1000 * 60 * (range === '30min' ? 30 : 60)
|
||||
).toUTCString();
|
||||
if (range === '30min' || range === 'lastHour') {
|
||||
const minutes = range === '30min' ? 30 : 60;
|
||||
const startDate = subMinutes(new Date(), minutes).toUTCString();
|
||||
const endDate = new Date().toUTCString();
|
||||
|
||||
return {
|
||||
@@ -322,36 +319,92 @@ export function getDatesFromRange(range: IChartRange) {
|
||||
};
|
||||
}
|
||||
|
||||
let days = 1;
|
||||
if (range === 'today') {
|
||||
const startDate = startOfDay(new Date());
|
||||
const endDate = endOfDay(new Date());
|
||||
|
||||
if (range === '24h') {
|
||||
const startDate = subDays(new Date(), days);
|
||||
const endDate = new Date();
|
||||
return {
|
||||
startDate: startDate.toUTCString(),
|
||||
endDate: endDate.toUTCString(),
|
||||
};
|
||||
} else if (range === '7d') {
|
||||
days = 7;
|
||||
} else if (range === '14d') {
|
||||
days = 14;
|
||||
} else if (range === '1m') {
|
||||
days = 30;
|
||||
} else if (range === '3m') {
|
||||
days = 90;
|
||||
} else if (range === '6m') {
|
||||
days = 180;
|
||||
} else if (range === '1y') {
|
||||
days = 365;
|
||||
}
|
||||
|
||||
const startDate = subDays(new Date(), days);
|
||||
startDate.setUTCHours(0, 0, 0, 0);
|
||||
const endDate = new Date();
|
||||
endDate.setUTCHours(23, 59, 59, 999);
|
||||
if (range === '7d') {
|
||||
const startDate = subDays(new Date(), 7).toUTCString();
|
||||
const endDate = new Date().toUTCString();
|
||||
|
||||
return {
|
||||
startDate,
|
||||
endDate,
|
||||
};
|
||||
}
|
||||
|
||||
if (range === '30d') {
|
||||
const startDate = subDays(new Date(), 30).toUTCString();
|
||||
const endDate = new Date().toUTCString();
|
||||
|
||||
return {
|
||||
startDate,
|
||||
endDate,
|
||||
};
|
||||
}
|
||||
|
||||
if (range === 'monthToDate') {
|
||||
const startDate = startOfMonth(new Date()).toUTCString();
|
||||
const endDate = new Date().toUTCString();
|
||||
|
||||
return {
|
||||
startDate,
|
||||
endDate,
|
||||
};
|
||||
}
|
||||
|
||||
if (range === 'lastMonth') {
|
||||
const month = subMonths(new Date(), 1);
|
||||
const startDate = startOfMonth(month).toUTCString();
|
||||
const endDate = endOfDay(month).toUTCString();
|
||||
|
||||
return {
|
||||
startDate,
|
||||
endDate,
|
||||
};
|
||||
}
|
||||
|
||||
if (range === 'yearToDate') {
|
||||
const startDate = startOfYear(new Date()).toUTCString();
|
||||
const endDate = new Date().toUTCString();
|
||||
|
||||
return {
|
||||
startDate,
|
||||
endDate,
|
||||
};
|
||||
}
|
||||
|
||||
if (range === 'lastYear') {
|
||||
const year = subYears(new Date(), 1);
|
||||
const startDate = startOfYear(year).toUTCString();
|
||||
const endDate = endOfYear(year).toUTCString();
|
||||
|
||||
return {
|
||||
startDate,
|
||||
endDate,
|
||||
};
|
||||
}
|
||||
|
||||
console.log('-------------------------------');
|
||||
console.log('-------------------------------');
|
||||
console.log('-------------------------------');
|
||||
console.log('-------------------------------');
|
||||
console.log('-------------------------------');
|
||||
console.log('-------------------------------');
|
||||
console.log('-------------------------------');
|
||||
console.log('-------------------------------');
|
||||
console.log('-------------------------------');
|
||||
console.log('-------------------------------');
|
||||
console.log('-------------------------------');
|
||||
return {
|
||||
startDate: startDate.toUTCString(),
|
||||
endDate: endDate.toUTCString(),
|
||||
startDate: subDays(new Date(), 30).toISOString(),
|
||||
endDate: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -374,44 +427,7 @@ export function getChartPrevStartEndDate({
|
||||
endDate: string;
|
||||
range: IChartRange;
|
||||
}) {
|
||||
let diff = 0;
|
||||
|
||||
switch (range) {
|
||||
case '30min': {
|
||||
diff = 1000 * 60 * 30;
|
||||
break;
|
||||
}
|
||||
case '1h': {
|
||||
diff = 1000 * 60 * 60;
|
||||
break;
|
||||
}
|
||||
case '24h':
|
||||
case 'today': {
|
||||
diff = 1000 * 60 * 60 * 24;
|
||||
break;
|
||||
}
|
||||
case '7d': {
|
||||
diff = 1000 * 60 * 60 * 24 * 7;
|
||||
break;
|
||||
}
|
||||
case '14d': {
|
||||
diff = 1000 * 60 * 60 * 24 * 14;
|
||||
break;
|
||||
}
|
||||
case '1m': {
|
||||
diff = 1000 * 60 * 60 * 24 * 30;
|
||||
break;
|
||||
}
|
||||
case '3m': {
|
||||
diff = 1000 * 60 * 60 * 24 * 90;
|
||||
break;
|
||||
}
|
||||
case '6m': {
|
||||
diff = 1000 * 60 * 60 * 24 * 180;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const diff = new Date(endDate).getTime() - new Date(startDate).getTime();
|
||||
return {
|
||||
startDate: new Date(new Date(startDate).getTime() - diff).toISOString(),
|
||||
endDate: new Date(new Date(endDate).getTime() - diff).toISOString(),
|
||||
|
||||
@@ -29,7 +29,7 @@ export const reportRouter = createTRPCRouter({
|
||||
breakdowns: report.breakdowns,
|
||||
chartType: report.chartType,
|
||||
lineType: report.lineType,
|
||||
range: report.range,
|
||||
range: report.range === 'custom' ? '30d' : report.range,
|
||||
formula: report.formula,
|
||||
},
|
||||
});
|
||||
@@ -53,7 +53,7 @@ export const reportRouter = createTRPCRouter({
|
||||
breakdowns: report.breakdowns,
|
||||
chartType: report.chartType,
|
||||
lineType: report.lineType,
|
||||
range: report.range,
|
||||
range: report.range === 'custom' ? '30d' : report.range,
|
||||
formula: report.formula,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
lineTypes,
|
||||
metrics,
|
||||
operators,
|
||||
timeRanges,
|
||||
timeWindows,
|
||||
} from '@openpanel/constants';
|
||||
|
||||
export function objectToZodEnums<K extends string>(
|
||||
@@ -57,7 +57,7 @@ export const zTimeInterval = z.enum(objectToZodEnums(intervals));
|
||||
|
||||
export const zMetric = z.enum(objectToZodEnums(metrics));
|
||||
|
||||
export const zRange = z.enum(objectToZodEnums(timeRanges));
|
||||
export const zRange = z.enum(objectToZodEnums(timeWindows));
|
||||
|
||||
export const zChartInput = z.object({
|
||||
name: z.string().default(''),
|
||||
@@ -66,7 +66,7 @@ export const zChartInput = z.object({
|
||||
interval: zTimeInterval.default('day'),
|
||||
events: zChartEvents,
|
||||
breakdowns: zChartBreakdowns.default([]),
|
||||
range: zRange.default('1m'),
|
||||
range: zRange.default('30d'),
|
||||
previous: z.boolean().default(false),
|
||||
formula: z.string().optional(),
|
||||
metric: zMetric.default('sum'),
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { z } from 'zod';
|
||||
|
||||
import type { timeRanges } from '@openpanel/constants';
|
||||
|
||||
import type {
|
||||
zChartBreakdown,
|
||||
zChartEvent,
|
||||
@@ -9,6 +7,7 @@ import type {
|
||||
zChartType,
|
||||
zLineType,
|
||||
zMetric,
|
||||
zRange,
|
||||
zTimeInterval,
|
||||
} from './index';
|
||||
|
||||
@@ -24,7 +23,7 @@ export type IInterval = z.infer<typeof zTimeInterval>;
|
||||
export type IChartType = z.infer<typeof zChartType>;
|
||||
export type IChartMetric = z.infer<typeof zMetric>;
|
||||
export type IChartLineType = z.infer<typeof zLineType>;
|
||||
export type IChartRange = keyof typeof timeRanges;
|
||||
export type IChartRange = z.infer<typeof zRange>;
|
||||
export type IGetChartDataInput = {
|
||||
event: IChartEvent;
|
||||
projectId: string;
|
||||
|
||||
Reference in New Issue
Block a user