Skip to content

Commit 026e246

Browse files
committed
use date range filter
1 parent 9467d23 commit 026e246

File tree

5 files changed

+103
-69
lines changed

5 files changed

+103
-69
lines changed

packages/web/app/src/components/ui/date-range-picker.tsx

Lines changed: 60 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useEffect, useRef, useState } from 'react';
2-
import { endOfDay, endOfToday, subMonths } from 'date-fns';
2+
import { endOfDay, endOfToday, formatDate, subMonths } from 'date-fns';
33
import { CalendarDays } from 'lucide-react';
44
import { DateRange, Matcher } from 'react-day-picker';
55
import { DurationUnit, formatDateToString, parse, units } from '@/lib/date-math';
@@ -32,13 +32,6 @@ export interface DateRangePickerProps {
3232
validUnits?: DurationUnit[];
3333
}
3434

35-
const formatDate = (date: Date, locale = 'en-us'): string => {
36-
return date.toLocaleDateString(locale, {
37-
month: 'short',
38-
day: 'numeric',
39-
});
40-
};
41-
4235
interface ResolvedDateRange {
4336
from: Date;
4437
to: Date;
@@ -51,7 +44,16 @@ export type Preset = {
5144
};
5245

5346
export function buildDateRangeString(range: ResolvedDateRange, locale = 'en-us'): string {
54-
return `${formatDate(range.from, locale)} - ${formatDate(range.to, locale)}`;
47+
const fromDate = formatDate(range.from, 'MMM d');
48+
const fromTime = formatDate(range.from, 'HH:mm');
49+
const toDate = formatDate(range.to, 'MMM d');
50+
const toTime = formatDate(range.to, 'HH:mm');
51+
52+
if (fromDate === toDate) {
53+
return `${fromDate}, ${fromTime} - ${toTime}`;
54+
}
55+
56+
return `${fromDate}, ${fromTime} - ${toDate}, ${toTime}`;
5557
}
5658

5759
function resolveRange(rawFrom: string, rawTo: string): ResolvedDateRange | null {
@@ -88,8 +90,12 @@ export const presetLast1Day: Preset = {
8890

8991
// Define presets
9092
export const availablePresets: Preset[] = [
93+
{ name: 'last15m', label: 'Last 15 minutes', range: { from: 'now-15m', to: 'now' } },
9194
{ name: 'last30m', label: 'Last 30 minutes', range: { from: 'now-30m', to: 'now' } },
9295
{ name: 'last1h', label: 'Last 1 hour', range: { from: 'now-1h', to: 'now' } },
96+
{ name: 'last3h', label: 'Last 3 hours', range: { from: 'now-3h', to: 'now' } },
97+
{ name: 'last6h', label: 'Last 6 hours', range: { from: 'now-6h', to: 'now' } },
98+
{ name: 'last12h', label: 'Last 12 hours', range: { from: 'now-12h', to: 'now' } },
9399
presetLast1Day,
94100
presetLast7Days,
95101
{ name: 'last14d', label: 'Last 14 days', range: { from: 'now-14d', to: 'now' } },
@@ -314,25 +320,34 @@ export function DateRangePicker(props: DateRangePickerProps): JSX.Element {
314320
</Button>
315321
</PopoverTrigger>
316322
<PopoverContent align={props.align} className="mt-1 flex h-[380px] w-auto p-0">
317-
<div className="flex flex-col py-4">
323+
<div className="flex flex-col py-2">
318324
<div className="flex flex-col items-center justify-end gap-2 lg:flex-row lg:items-start">
319325
<div className="flex flex-col gap-1 pl-3">
320-
<div className="mb-2 font-bold">Absolute date range</div>
326+
<div className="mb-2 text-sm">Absolute date range</div>
321327
<div className="space-y-2">
322328
<div className="grid w-full max-w-sm items-center gap-1.5">
323-
<Label htmlFor="from">From</Label>
329+
<Label htmlFor="from" className="text-xs text-gray-400">
330+
From
331+
</Label>
324332
<div className="flex w-full max-w-sm items-center space-x-2">
325-
<Input
326-
type="text"
327-
id="from"
328-
value={fromValue}
329-
onChange={ev => {
330-
setFromValue(ev.target.value);
331-
}}
332-
/>
333-
<Button size="icon" variant="outline" onClick={() => setShowCalendar(true)}>
334-
<CalendarDays className="size-4" />
335-
</Button>
333+
<div className="relative flex w-full">
334+
<Input
335+
type="text"
336+
id="from"
337+
value={fromValue}
338+
onChange={ev => {
339+
setFromValue(ev.target.value);
340+
}}
341+
className="font-mono"
342+
/>
343+
<Button
344+
variant="ghost"
345+
className="color-white absolute right-2 top-1/2 size-6 -translate-y-1/2 px-0"
346+
onClick={() => setShowCalendar(true)}
347+
>
348+
<CalendarDays className="size-3.5" />
349+
</Button>
350+
</div>
336351
</div>
337352
<div className="text-red-500">
338353
{hasInvalidUnitRegex?.test(fromValue) ? (
@@ -343,19 +358,28 @@ export function DateRangePicker(props: DateRangePickerProps): JSX.Element {
343358
</div>
344359
</div>
345360
<div className="grid w-full max-w-sm items-center gap-1.5">
346-
<Label htmlFor="to">To</Label>
361+
<Label htmlFor="to" className="text-xs text-gray-400">
362+
To
363+
</Label>
347364
<div className="flex w-full max-w-sm items-center space-x-2">
348-
<Input
349-
type="text"
350-
id="to"
351-
value={toValue}
352-
onChange={ev => {
353-
setToValue(ev.target.value);
354-
}}
355-
/>
356-
<Button size="icon" variant="outline" onClick={() => setShowCalendar(true)}>
357-
<CalendarDays className="size-4" />
358-
</Button>
365+
<div className="relative flex w-full">
366+
<Input
367+
type="text"
368+
id="to"
369+
value={toValue}
370+
onChange={ev => {
371+
setToValue(ev.target.value);
372+
}}
373+
className="font-mono"
374+
/>
375+
<Button
376+
variant="ghost"
377+
className="color-white absolute right-2 top-1/2 size-6 -translate-y-1/2 px-0"
378+
onClick={() => setShowCalendar(true)}
379+
>
380+
<CalendarDays className="size-3.5" />
381+
</Button>
382+
</div>
359383
</div>
360384
<div className="text-red-500">
361385
{hasInvalidUnitRegex?.test(toValue) ? (
@@ -432,7 +456,7 @@ export function DateRangePicker(props: DateRangePickerProps): JSX.Element {
432456
</div>
433457
</div>
434458
{showCalendar && (
435-
<div className="absolute left-0 top-0 -translate-x-full">
459+
<div className="absolute left-0 top-[4px] -translate-x-full">
436460
<div className="bg-popover mr-1 rounded-md border p-4">
437461
<Button
438462
variant="ghost"

packages/web/app/src/components/ui/page-content-layout.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,11 @@ type SubPageLayoutHeaderProps = {
5353
children?: ReactNode;
5454
subPageTitle?: ReactNode;
5555
description?: string | ReactNode;
56+
sideContent?: ReactNode;
5657
} & HTMLAttributes<HTMLDivElement>;
5758

58-
const SubPageLayoutHeader = forwardRef<HTMLDivElement, SubPageLayoutHeaderProps>((props, ref) => (
59-
<div className="flex flex-row items-center justify-between" ref={ref}>
59+
const SubPageLayoutHeader = forwardRef<HTMLDivElement, SubPageLayoutHeaderProps>((props, ref) => {
60+
const header = (
6061
<div className="space-y-1.5">
6162
<CardTitle>{props.subPageTitle}</CardTitle>
6263
{typeof props.description === 'string' ? (
@@ -65,9 +66,21 @@ const SubPageLayoutHeader = forwardRef<HTMLDivElement, SubPageLayoutHeaderProps>
6566
props.description
6667
)}
6768
</div>
68-
<div>{props.children}</div>
69-
</div>
70-
));
69+
);
70+
return (
71+
<div className="flex flex-row items-center justify-between" ref={ref}>
72+
{props.sideContent ? (
73+
<div className="flex w-full">
74+
{header}
75+
{props.sideContent}
76+
</div>
77+
) : (
78+
header
79+
)}
80+
<div>{props.children}</div>
81+
</div>
82+
);
83+
});
7184
SubPageLayoutHeader.displayName = 'SubPageLayoutHeader';
7285

7386
export { PageLayout, NavLayout, PageLayoutContent, SubPageLayout, SubPageLayoutHeader };

packages/web/app/src/lib/date-math.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function unitToDurationKey(unit: DurationUnit): keyof Duration {
3232
}
3333
}
3434

35-
const dateStringFormat = 'yyyy-MM-dd';
35+
const dateStringFormat = 'yyyy-MM-dd HH:mm';
3636

3737
function parseDateString(input: string) {
3838
try {

packages/web/app/src/pages/target-traces.tsx

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
ChartTooltip,
2424
ChartTooltipContent,
2525
} from '@/components/ui/chart';
26+
import { DateRangePicker, presetLast7Days } from '@/components/ui/date-range-picker';
2627
import { Meta } from '@/components/ui/meta';
2728
import { SubPageLayoutHeader } from '@/components/ui/page-content-layout';
2829
import { QueryError } from '@/components/ui/query-error';
@@ -52,6 +53,7 @@ import {
5253
} from '@/components/ui/table';
5354
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
5455
import { FragmentType, graphql, useFragment } from '@/gql';
56+
import { useDateRangeController } from '@/lib/hooks/use-date-range-controller';
5557
import { cn } from '@/lib/utils';
5658
import { Link, useNavigate, useRouter } from '@tanstack/react-router';
5759
import {
@@ -63,18 +65,12 @@ import {
6365
useReactTable,
6466
} from '@tanstack/react-table';
6567
import * as GraphQLSchema from '../gql/graphql';
66-
import * as dateMath from '../lib/date-math';
6768
import {
6869
CopyIconButton,
6970
formatNanoseconds,
7071
TraceSheet as ImportedTraceSheet,
7172
} from './target-trace';
72-
import {
73-
DurationFilter,
74-
MultiInputFilter,
75-
MultiSelectFilter,
76-
TimelineFilter,
77-
} from './traces/target-traces-filter';
73+
import { DurationFilter, MultiInputFilter, MultiSelectFilter } from './traces/target-traces-filter';
7874

7975
const chartConfig = {
8076
ok: {
@@ -154,16 +150,13 @@ const TrafficBucketDiagram = memo(function Traffic(props: TrafficProps) {
154150
// Ensure left is always before right
155151
let left = refAreaLeft;
156152
let right = refAreaRight;
157-
const now = new Date();
158153

159-
if (
160-
parseDate(left, 'yyyy-MM-dd', now).getTime() > parseDate(right, 'yyyy-MM-dd', now).getTime()
161-
) {
154+
if (new Date(left).getTime() > new Date(right).getTime()) {
162155
[left, right] = [right, left];
163156
}
164157

165158
void navigate({
166-
search: (prev: any) => ({ ...prev, filter: { ...prev.filter, period: [left, right] } }),
159+
search: (prev: any) => ({ ...prev, from: left, to: right }),
167160
});
168161

169162
setRefAreaLeft(null);
@@ -226,6 +219,7 @@ const TrafficBucketDiagram = memo(function Traffic(props: TrafficProps) {
226219
/>
227220
<Bar stackId="all" dataKey="ok" fill="var(--color-ok)" name="Ok" />
228221
<Bar stackId="all" dataKey="error" fill="var(--color-error)" name="Error" />
222+
// TODO: hide this if there is no filter declared
229223
<Bar stackId="all" dataKey="remaining" fill="rgba(170,175,180,0.1)" name="Filtered out" />
230224
{refAreaLeft && refAreaRight && (
231225
<ReferenceArea x1={refAreaLeft} x2={refAreaRight} fill="white" fillOpacity={0.2} />
@@ -708,7 +702,6 @@ function LabelWithColor(props: { className: string; children: ReactNode }) {
708702
}
709703

710704
export const TargetTracesFilterState = z.object({
711-
period: z.union([z.tuple([z.string(), z.string()]), z.tuple([])]).default([]),
712705
duration: z.union([z.tuple([z.number(), z.number()]), z.tuple([])]).default([]),
713706
'trace.id': z.array(z.string()).default([]),
714707
'graphql.status': z.array(z.string()).default([]),
@@ -818,7 +811,6 @@ function Filters(
818811
</Button>
819812
) : null}
820813
</SidebarGroupLabel>
821-
<TimelineFilter value={filterSelector('period')} onChange={updateFilter('period')} />
822814
<DurationFilter value={filterSelector('duration')} onChange={updateFilter('duration')} />
823815
<MultiInputFilter
824816
key="trace.id"
@@ -1144,15 +1136,14 @@ const TargetTracesFetchMoreTracesQuery = graphql(`
11441136
function TargetTracesPageContent(props: SortProps & PaginationProps & FilterProps) {
11451137
const targetRef = useTargetReference();
11461138

1147-
const period = useMemo(() => {
1148-
if (!props.filter.period?.length) {
1149-
return null;
1150-
}
1151-
return dateMath.resolveRange({ from: props.filter.period[0], to: props.filter.period[1] });
1152-
}, [props.filter.period]);
1139+
const dateRangeController = useDateRangeController({
1140+
// TODO: ressolve retention from account
1141+
dataRetentionInDays: 365,
1142+
defaultPreset: presetLast7Days,
1143+
});
11531144

11541145
const filter: GraphQLSchema.TracesFilterInput = {
1155-
period,
1146+
period: dateRangeController.resolvedRange,
11561147
duration: {
11571148
min: props.filter.duration?.[0] ?? null,
11581149
max: props.filter.duration?.[1] ?? null,
@@ -1302,10 +1293,17 @@ function TargetTracesPageContent(props: SortProps & PaginationProps & FilterProp
13021293
<div className="py-6">
13031294
<SubPageLayoutHeader
13041295
subPageTitle="Traces"
1305-
description={
1306-
<>
1307-
<CardDescription>Insights into the requests made to your GraphQL API.</CardDescription>
1308-
</>
1296+
description="Insights into the requests made to your GraphQL API."
1297+
sideContent={
1298+
<div className="ml-auto mr-0">
1299+
<DateRangePicker
1300+
validUnits={['y', 'M', 'w', 'd', 'h', 'm']}
1301+
selectedRange={dateRangeController.selectedPreset.range}
1302+
startDate={dateRangeController.startDate}
1303+
align="end"
1304+
onUpdate={args => dateRangeController.setSelectedPreset(args.preset)}
1305+
/>
1306+
</div>
13091307
}
13101308
/>
13111309
<SidebarProvider className="mt-4">
@@ -1315,7 +1313,7 @@ function TargetTracesPageContent(props: SortProps & PaginationProps & FilterProp
13151313
</SidebarContent>
13161314
</Sidebar>
13171315
<SidebarInset className="bg-transparent">
1318-
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
1316+
<div className="flex flex-1 flex-col gap-4 pl-4 pt-0">
13191317
<div>
13201318
<TrafficBucketDiagram buckets={query.data?.target?.tracesStatusBreakdown ?? []} />
13211319
</div>

packages/web/app/src/router.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,6 @@ const targetTracesRoute = createRoute({
685685
'http.url': [],
686686
'trace.id': [],
687687
duration: [],
688-
period: [],
689688
} satisfies FilterState,
690689
sort = {
691690
id: 'timestamp',

0 commit comments

Comments
 (0)