Skip to content

Commit

Permalink
fix: ratelimit date input and time display logs (#2717)
Browse files Browse the repository at this point in the history
* fix: time display

* feat: improve ratelimit logs

Improve ratelimits filter date pickers and how we display times on
ratelimit logs

* refactor: add hover state to timestamp

* fix: prevent until date set before from
  • Loading branch information
ogzhanolguncu authored Dec 7, 2024
1 parent c1af9a3 commit 2a07b44
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 47 deletions.
126 changes: 80 additions & 46 deletions apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@
import { ArrayInput } from "@/components/array-input";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { CalendarRange, CheckCheck, ChevronDown, RefreshCw, User, X } from "lucide-react";
import {
CalendarIcon,
CalendarRange,
CheckCheck,
ChevronDown,
RefreshCw,
User,
X,
} from "lucide-react";
import {
parseAsArrayOf,
parseAsBoolean,
parseAsIsoDateTime,
parseAsString,
parseAsTimestamp,
useQueryState,
} from "nuqs";
import type React from "react";
import { useEffect, useState, useTransition } from "react";
import { useState, useTransition } from "react";

import { DateTimePicker } from "@/components/date-time-picker";
import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -27,6 +36,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { format } from "date-fns";
import { useRouter } from "next/navigation";

export const Filters: React.FC = () => {
Expand All @@ -49,36 +59,34 @@ export const Filters: React.FC = () => {
clearOnDefault: true,
}),
);

const [after, setAfter] = useQueryState(
"after",
parseAsIsoDateTime.withOptions({
parseAsTimestamp.withOptions({
history: "push",
shallow: false, // otherwise server components won't notice the change
shallow: false,
clearOnDefault: true,
}),
);

const [before, setBefore] = useQueryState(
"before",
parseAsIsoDateTime.withOptions({
parseAsTimestamp.withOptions({
history: "push",
shallow: false, // otherwise server components won't notice the change
shallow: false,
clearOnDefault: true,
}),
);

const [localTime, setLocalTime] = useState("");
useEffect(() => {
setLocalTime(after?.toLocaleString() ?? "");
}, [after]);

const [identifierVisible, setIdentifierVisible] = useState(false);
const [successVisible, setSuccessVisible] = useState(false);
const [timeRangeVisible, setTimeRangeVisible] = useState(false);

return (
<div className="flex flex-col w-full gap-2">
<div className="flex items-center justify-end w-full gap-2">
<DropdownMenu>
<DropdownMenuTrigger>
<DropdownMenuTrigger asChild>
<Button variant="secondary" className="text-xs">
Add Filter <ChevronDown className="w-4 h-4" />
</Button>
Expand Down Expand Up @@ -151,8 +159,9 @@ export const Filters: React.FC = () => {
<Select
value={success ? "true" : "false"}
onValueChange={(v) => {
setSuccess(v === "true");
startTransition(() => {});
startTransition(() => {
setSuccess(v === "true");
});
}}
>
<SelectTrigger>
Expand All @@ -167,9 +176,10 @@ export const Filters: React.FC = () => {
size="icon"
variant="secondary"
onClick={() => {
setSuccessVisible(false);
setSuccess(null);
startTransition(() => {});
startTransition(() => {
setSuccessVisible(false);
setSuccess(null);
});
}}
>
<X className="w-4 h-4" />
Expand All @@ -178,36 +188,60 @@ export const Filters: React.FC = () => {
) : null}
{timeRangeVisible || after !== null || before !== null ? (
<div className="flex items-center w-full gap-2">
<div className="flex items-center w-full h-8 p-1 text-sm border rounded-md group focus-within:border-primary">
<div className="flex flex-wrap items-center w-full gap-1 px-2">
<DateTimePicker
date={after ?? new Date()}
onDateChange={(date) =>
startTransition(() => {
setAfter(date);
})
}
timeInputLabel="Select Time"
calendarProps={{
disabled: { before: new Date() },
showOutsideDays: true,
}}
timeInputProps={{
className: "w-[100px]",
}}
>
<Button variant="outline" className="text-xs font-medium w-full justify-start gap-0">
<span className="mr-1 text-xs font-medium">From:</span>
--{after?.toLocaleString()}-- --{localTime}--
<input
type="datetime-local"
value={after?.toLocaleString()}
onChange={(v) => {
setAfter(new Date(v.currentTarget.value));
startTransition(() => {});
}}
className="flex-1 w-full bg-transparent outline-none placeholder:text-content-subtle"
/>
</div>
</div>
<div className="flex items-center w-full h-8 p-1 text-sm border rounded-md group focus-within:border-primary">
<div className="flex flex-wrap items-center w-full gap-1 px-2">
<span className="mr-1 text-xs font-medium">Until:</span>
<input
id="before"
type="datetime-local"
value={before?.toLocaleString()}
onChange={(v) => {
setBefore(new Date(v.currentTarget.value));
startTransition(() => {});
}}
className="flex-1 w-full bg-transparent outline-none placeholder:text-content-subtle"
/>
</div>

{after ? format(after, "PPp") : format(new Date(), "PPp")}

<CalendarIcon className="mr-2 h-4 w-4 ml-auto" />
</Button>
</DateTimePicker>
<div className="flex items-center w-full gap-2">
<DateTimePicker
date={before ?? new Date()}
onDateChange={(date) =>
startTransition(() => {
setBefore(date);
})
}
timeInputLabel="Select Time"
calendarProps={{
disabled: { before: after ?? new Date() },
showOutsideDays: true,
}}
timeInputProps={{
className: "w-[130px]",
}}
>
<Button
variant="outline"
className="text-xs font-medium w-full justify-start gap-0"
>
<span className="mr-1 text-xs font-medium">Until:</span>

{before ? format(before, "PPp") : format(new Date(), "PPp")}

<CalendarIcon className="mr-2 h-4 w-4 ml-auto" />
</Button>
</DateTimePicker>
</div>

<Button
className="flex-shrink-0"
size="icon"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { notFound } from "next/navigation";

import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
import { Loading } from "@/components/dashboard/loading";
import { TimestampInfo } from "@/components/timestamp-info";
import { Badge } from "@/components/ui/badge";
import {
Table,
Expand Down Expand Up @@ -159,7 +160,9 @@ const AuditLogTable: React.FC<{
{logs.map((l) => (
<TableRow key={l.request_id}>
<TableCell>
<span className="text-sm text-content">{new Date(l.time).toISOString()}</span>
<div className="px-[2px] flex items-center hover:underline hover:decoration-dotted">
<TimestampInfo value={l.time} className="text-sm" />
</div>
</TableCell>

<TableCell>
Expand Down
81 changes: 81 additions & 0 deletions apps/dashboard/components/date-time-picker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Calendar } from "@/components/ui/calendar";
import { Input } from "@/components/ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Separator } from "@/components/ui/separator";
import { format, setHours, setMinutes } from "date-fns";
import type React from "react";
import { type PropsWithChildren, useState } from "react";

type DateTimePickerProps = {
date: Date;
onDateChange: (date: Date) => void;
calendarProps?: React.ComponentProps<typeof Calendar>;
timeInputProps?: React.ComponentProps<typeof Input>;
popoverContentProps?: React.ComponentProps<typeof PopoverContent>;
timeInputLabel?: string;
disabled?: boolean;
};

export function DateTimePicker({
date,
onDateChange: setDate,
children,
calendarProps,
timeInputProps,
popoverContentProps,
timeInputLabel = "Time",
disabled,
}: PropsWithChildren<DateTimePickerProps>) {
const [selectedDateTime, setSelectedDateTime] = useState<Date>(date);

const handleSelect = (selected: Date) => {
if (!selected) {
return;
}
const hours = selectedDateTime.getHours();
const minutes = selectedDateTime.getMinutes();
const newDate = setMinutes(setHours(selected, hours), minutes);
setSelectedDateTime(newDate);
setDate(newDate);
};

const handleTimeChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
const { value } = e.target;
const [hours, minutes] = value.split(":").map(Number);
const newDate = setMinutes(setHours(selectedDateTime, hours || 0), minutes || 0);
setSelectedDateTime(newDate);
setDate(newDate);
};

const timeValue = format(selectedDateTime, "HH:mm");

return (
<Popover>
<PopoverTrigger asChild disabled={disabled}>
{children}
</PopoverTrigger>
<PopoverContent className="w-auto p-0" {...popoverContentProps}>
<Calendar
mode="single"
selected={selectedDateTime}
//@ts-expect-error Calendar can't infer the mode correctly
onSelect={(date) => handleSelect(date ?? new Date())}
initialFocus
{...calendarProps}
/>
<Separator />
<div className="p-3 flex flex-col gap-2">
<label className="text-sm font-medium leading-none ml-0.5">{timeInputLabel}</label>
<Input
type="time"
value={timeValue}
className="w-[130px] mt-2"
onChange={handleTimeChange}
disabled={disabled}
{...timeInputProps}
/>
</div>
</PopoverContent>
</Popover>
);
}

0 comments on commit 2a07b44

Please sign in to comment.