From af79ca874d88e6b5c36e36073fa53fd8cb4de423 Mon Sep 17 00:00:00 2001 From: ytkimirti Date: Sat, 3 May 2025 15:01:17 +0200 Subject: [PATCH 1/5] chore: bump redis --- bun.lockb | Bin 181954 -> 181954 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index 484185f651a7cc268e1d3fd231be74fec570b747..b4c7e432c9db76491229d43085566a0fdde05d55 100755 GIT binary patch delta 172 zcmV;d08{_MjSIq!3y>}#mXaaZ$|rvtdb9|Uaf}=KL`^{rDx}!LYTeR!;g~Tnu}-=k z0SJ?E94C`7ISI2lA8d?3cw%5Ur#{(AxB=9R(o=EyYsD569?BAT*&&U&DdH^nIF#1< zo0xV14*RITlYwZAWbX$F4>o4UKSotSdhnJ`gXOEY<*Na>D+d8yF)lGKGPk7H0q$W8 aI4*Q&dSh~VaCC1im*Is19k;tV0tbzU_DY!m delta 170 zcmV;b09F6OjSIq!3y>}#%2TxxI);}Iz|ETfkt={88{XYCte0h<|H%7MKx!x}u}-=k zlLRa%lQ206vpFAZj6hXAxlbd58!!BpIeQoY=O%+DwqkV`$9&;M-x`V0L|-3Sf^+Zf ziRf(B`L2XaYh>W;u0=b06%?-&ZbrVh(shI7tGDH=0k|s%0ssI20002Dq}KuNVGJ`a YbZ2^Fa(QrcZ!MSMg#sP7yEy^}jcCP6Gynhq diff --git a/package.json b/package.json index d70d36a..ca53754 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@tabler/icons-react": "^3.19.0", "@tanstack/react-query": "^5.32.0", "@types/bytes": "^3.1.4", - "@upstash/redis": "^1.34.3", + "@upstash/redis": "^1.34.8", "bytes": "^3.1.2", "react-hook-form": "^7.53.0", "react-resizable-panels": "^2.1.4", From 48c7be2f7a8a8d5309cd3edbd3228da55e9fae9a Mon Sep 17 00:00:00 2001 From: ytkimirti Date: Sat, 3 May 2025 15:06:52 +0200 Subject: [PATCH 2/5] feat: refactor ttl badge and add it for hash fields --- .../components/display/display-header.tsx | 4 +- .../components/display/display-list-edit.tsx | 57 +++++++++++------- .../components/display/display-list.tsx | 51 +++++++++------- .../display/hash/hash-field-ttl-badge.tsx | 20 +++++++ .../display/hash/hash-field-ttl-info.tsx | 34 +++++++++++ .../components/display/header-badges.tsx | 52 ++++------------ .../components/display/ttl-badge.tsx | 56 +++++++++++++++++ .../components/display/ttl-popover.tsx | 29 +++++---- .../databrowser/hooks/use-fetch-hash-ttl.ts | 60 +++++++++++++++++++ .../databrowser/hooks/use-fetch-ttl.ts | 12 ++-- .../databrowser/hooks/use-set-hash-ttl.ts | 32 ++++++++++ .../databrowser/hooks/use-set-ttl.ts | 7 ++- 12 files changed, 308 insertions(+), 106 deletions(-) create mode 100644 src/components/databrowser/components/display/hash/hash-field-ttl-badge.tsx create mode 100644 src/components/databrowser/components/display/hash/hash-field-ttl-info.tsx create mode 100644 src/components/databrowser/components/display/ttl-badge.tsx create mode 100644 src/components/databrowser/hooks/use-fetch-hash-ttl.ts create mode 100644 src/components/databrowser/hooks/use-set-hash-ttl.ts diff --git a/src/components/databrowser/components/display/display-header.tsx b/src/components/databrowser/components/display/display-header.tsx index 3248cde..fc7f20b 100644 --- a/src/components/databrowser/components/display/display-header.tsx +++ b/src/components/databrowser/components/display/display-header.tsx @@ -5,7 +5,7 @@ import { IconPlus } from "@tabler/icons-react" import { Button } from "@/components/ui/button" import { TypeTag } from "../type-tag" -import { LengthBadge, SizeBadge, TTLBadge } from "./header-badges" +import { HeaderTTLBadge, LengthBadge, SizeBadge } from "./header-badges" import { KeyActions } from "./key-actions" export const DisplayHeader = ({ @@ -49,7 +49,7 @@ export const DisplayHeader = ({ - + ) diff --git a/src/components/databrowser/components/display/display-list-edit.tsx b/src/components/databrowser/components/display/display-list-edit.tsx index 8fd9016..d9fc046 100644 --- a/src/components/databrowser/components/display/display-list-edit.tsx +++ b/src/components/databrowser/components/display/display-list-edit.tsx @@ -3,6 +3,7 @@ import { useDatabrowserStore } from "@/store" import type { ListDataType } from "@/types" import { Controller, FormProvider, useForm, useFormContext } from "react-hook-form" +import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { Spinner } from "@/components/ui/spinner" import { SimpleTooltip } from "@/components/ui/tooltip" @@ -10,6 +11,7 @@ import { SimpleTooltip } from "@/components/ui/tooltip" import { useFetchListItems } from "../../hooks" import { useEditListItem } from "../../hooks/use-edit-list-item" import { headerLabels } from "./display-list" +import { HashFieldTTLBadge } from "./hash/hash-field-ttl-badge" import { useField } from "./input/use-field" export const ListEditDisplay = ({ @@ -105,30 +107,43 @@ const ListEditForm = ({ )} -
- - +
+ {type === "hash" && itemKey !== "" && ( + + )} + +
- + + + +
diff --git a/src/components/databrowser/components/display/display-list.tsx b/src/components/databrowser/components/display/display-list.tsx index 2f9109e..39071fc 100644 --- a/src/components/databrowser/components/display/display-list.tsx +++ b/src/components/databrowser/components/display/display-list.tsx @@ -14,6 +14,7 @@ import { InfiniteScroll } from "../sidebar/infinite-scroll" import { DeleteAlertDialog } from "./delete-alert-dialog" import { DisplayHeader } from "./display-header" import { ListEditDisplay } from "./display-list-edit" +import { HashFieldTTLInfo } from "./hash/hash-field-ttl-info" export const headerLabels = { list: ["Index", "Content"], @@ -72,6 +73,7 @@ export const ListItems = ({ }) => { const { setSelectedListItem } = useDatabrowserStore() const keys = useMemo(() => query.data?.pages.flatMap((page) => page.keys) ?? [], [query.data]) + const fields = useMemo(() => keys.map((key) => key.key), [keys]) const { mutate: editItem } = useEditListItem() return ( @@ -84,7 +86,7 @@ export const ListItems = ({ onClick={() => { setSelectedListItem({ key }) }} - className="h-10 border-b border-b-zinc-100 hover:bg-zinc-50" + className={cn("h-10 border-b border-b-zinc-100 hover:bg-zinc-100 ")} > { e.stopPropagation() }} > - { - e.stopPropagation() - editItem({ - type, - dataKey, - itemKey: key, - // For deletion - newKey: undefined, - }) - }} - > - - +
+ {type === "hash" && ( + + )} + { + e.stopPropagation() + editItem({ + type, + dataKey, + itemKey: key, + // For deletion + newKey: undefined, + }) + }} + > + + +
)} diff --git a/src/components/databrowser/components/display/hash/hash-field-ttl-badge.tsx b/src/components/databrowser/components/display/hash/hash-field-ttl-badge.tsx new file mode 100644 index 0000000..99861fb --- /dev/null +++ b/src/components/databrowser/components/display/hash/hash-field-ttl-badge.tsx @@ -0,0 +1,20 @@ +import { useFetchHashFieldExpires } from "@/components/databrowser/hooks/use-fetch-hash-ttl" +import { useSetHashTTL } from "@/components/databrowser/hooks/use-set-hash-ttl" + +import { TTLBadge } from "../ttl-badge" + +export const HashFieldTTLBadge = ({ dataKey, field }: { dataKey: string; field: string }) => { + const { data } = useFetchHashFieldExpires({ dataKey, fields: [field] }) + const { mutate: setTTL, isPending } = useSetHashTTL() + + const expireAt = data?.[field] + + return ( + setTTL({ dataKey, field, ttl })} + isPending={isPending} + /> + ) +} diff --git a/src/components/databrowser/components/display/hash/hash-field-ttl-info.tsx b/src/components/databrowser/components/display/hash/hash-field-ttl-info.tsx new file mode 100644 index 0000000..e82cba4 --- /dev/null +++ b/src/components/databrowser/components/display/hash/hash-field-ttl-info.tsx @@ -0,0 +1,34 @@ +import { useEffect, useState } from "react" + +import { formatTime } from "@/lib/utils" +import { useFetchHashFieldExpires } from "@/components/databrowser/hooks/use-fetch-hash-ttl" + +import { calculateTTL, TTL_INFINITE, TTL_NOT_FOUND } from "../ttl-badge" + +export const HashFieldTTLInfo = ({ + dataKey, + field, + fields, +}: { + dataKey: string + field: string + fields: string[] +}) => { + const { data } = useFetchHashFieldExpires({ dataKey, fields }) + const expireAt = data?.[field] + + const [ttl, setTTL] = useState(() => calculateTTL(expireAt)) + + useEffect(() => { + setTTL(calculateTTL(expireAt)) + const interval = setInterval(() => { + setTTL(calculateTTL(expireAt)) + }, 1000) + + return () => clearInterval(interval) + }, [expireAt]) + + if (!expireAt || expireAt === TTL_NOT_FOUND || expireAt === TTL_INFINITE) return + + return {formatTime(ttl ?? 0)} +} diff --git a/src/components/databrowser/components/display/header-badges.tsx b/src/components/databrowser/components/display/header-badges.tsx index f1de5ce..6d304e6 100644 --- a/src/components/databrowser/components/display/header-badges.tsx +++ b/src/components/databrowser/components/display/header-badges.tsx @@ -1,17 +1,12 @@ -import { useEffect } from "react" import { type DataType } from "@/types" -import { IconChevronDown } from "@tabler/icons-react" import bytes from "bytes" -import { queryClient } from "@/lib/clients" -import { formatTime } from "@/lib/utils" import { Skeleton } from "@/components/ui/skeleton" -import { FETCH_TTL_QUERY_KEY, useFetchTTL } from "../../hooks" -import { useDeleteKeyCache } from "../../hooks/use-delete-key-cache" +import { useFetchKeyExpire, useSetTTL } from "../../hooks" import { useFetchKeyLength } from "../../hooks/use-fetch-key-length" import { useFetchKeySize } from "../../hooks/use-fetch-key-size" -import { TTLPopover } from "./ttl-popover" +import { TTLBadge } from "./ttl-badge" export const LengthBadge = ({ dataKey, @@ -50,43 +45,16 @@ export const SizeBadge = ({ dataKey }: { dataKey: string }) => { ) } -const TTL_INFINITE = -1 -const TTL_NOT_FOUND = -2 - -export const TTLBadge = ({ dataKey }: { dataKey: string }) => { - const { data: ttl } = useFetchTTL(dataKey) - const { deleteKeyCache } = useDeleteKeyCache() - - // Tick the ttl query every second - useEffect(() => { - const interval = setInterval(() => { - queryClient.setQueryData([FETCH_TTL_QUERY_KEY, dataKey], (ttl?: number) => { - if (ttl === undefined || ttl === TTL_INFINITE) return ttl - - if (ttl <= 1) { - deleteKeyCache(dataKey) - return TTL_NOT_FOUND - } - return ttl - 1 - }) - }, 1000) - - return () => clearInterval(interval) - }, []) +export const HeaderTTLBadge = ({ dataKey }: { dataKey: string }) => { + const { data: expireAt } = useFetchKeyExpire(dataKey) + const { mutate: setTTL, isPending } = useSetTTL() return ( - - {ttl === undefined ? ( - - ) : ( - -
- {ttl === TTL_INFINITE ? "Forever" : formatTime(ttl)} - -
-
- )} -
+ setTTL({ dataKey, ttl })} + isPending={isPending} + /> ) } diff --git a/src/components/databrowser/components/display/ttl-badge.tsx b/src/components/databrowser/components/display/ttl-badge.tsx new file mode 100644 index 0000000..d840ced --- /dev/null +++ b/src/components/databrowser/components/display/ttl-badge.tsx @@ -0,0 +1,56 @@ +import { useEffect, useState } from "react" +import { IconChevronDown } from "@tabler/icons-react" + +import { formatTime } from "@/lib/utils" +import { Skeleton } from "@/components/ui/skeleton" + +import { Badge } from "./header-badges" +import { TTLPopover } from "./ttl-popover" + +export const TTL_INFINITE = -1 +export const TTL_NOT_FOUND = -2 + +export const calculateTTL = (expireAt?: number) => { + if (!expireAt) return + if (expireAt === TTL_INFINITE) return TTL_INFINITE + return Math.max(0, Math.floor((expireAt - Date.now()) / 1000)) +} + +export const TTLBadge = ({ + label = "TTL:", + expireAt, + setTTL, + isPending, +}: { + label?: string + expireAt?: number + setTTL: (ttl: number) => void + isPending: boolean +}) => { + const [ttl, setTTLLabel] = useState(() => calculateTTL(expireAt)) + + // Update ttl every second + useEffect(() => { + setTTLLabel(calculateTTL(expireAt)) + const interval = setInterval(() => { + setTTLLabel(calculateTTL(expireAt)) + }, 1000) + + return () => clearInterval(interval) + }, [expireAt]) + + return ( + + {ttl === undefined ? ( + + ) : ( + +
+ {ttl === TTL_INFINITE ? "Forever" : formatTime(ttl)} + +
+
+ )} +
+ ) +} diff --git a/src/components/databrowser/components/display/ttl-popover.tsx b/src/components/databrowser/components/display/ttl-popover.tsx index 47d23e6..c17bf2d 100644 --- a/src/components/databrowser/components/display/ttl-popover.tsx +++ b/src/components/databrowser/components/display/ttl-popover.tsx @@ -12,9 +12,8 @@ import { SelectValue, } from "@/components/ui/select" import { Spinner } from "@/components/ui/spinner" -import { useSetTTL } from "@/components/databrowser/hooks/use-set-ttl" -const PERSISTED_KEY = -1 +import { TTL_INFINITE } from "./ttl-badge" const timeUnits = [ { label: "Seconds", value: 1 }, @@ -26,10 +25,10 @@ const timeUnits = [ export function TTLPopover({ children, ttl, - dataKey, -}: PropsWithChildren<{ ttl: number; dataKey: string }>) { + setTTL, + isPending, +}: PropsWithChildren<{ ttl: number; setTTL: (ttl: number) => void; isPending: boolean }>) { const [open, setOpen] = useState(false) - const { mutateAsync: setTTL, isPending } = useSetTTL() const defaultValues = useMemo(() => { return { type: "Seconds", value: ttl } as const @@ -49,18 +48,12 @@ export function TTLPopover({ }, [defaultValues]) const onSubmit = handleSubmit(async ({ value, type }) => { - await setTTL({ - dataKey: dataKey, - ttl: value * timeUnits.find((unit) => unit.label === type)!.value, - }) + await setTTL(value * timeUnits.find((unit) => unit.label === type)!.value) setOpen(false) }) const handlePersist = async () => { - await setTTL({ - dataKey: dataKey, - ttl: undefined, - }) + await setTTL(TTL_INFINITE) setOpen(false) } @@ -77,7 +70,13 @@ export function TTLPopover({ -
+ { + onSubmit(e) + e.stopPropagation() + }} + >

Expiration

@@ -128,7 +127,7 @@ export function TTLPopover({