useFillTimeseriesSorted.ts127 lines · main
1import { useMemo } from 'react'
2
3import { fillTimeseries } from '@/components/interfaces/Settings/Logs/Logs.utils'
4import type { Datum } from '@/components/ui/Charts/Charts.types'
5
6export type FillTimeseriesOptions<T extends Datum = Datum> = {
7 /** The timeseries data to fill gaps in */
8 data: T[]
9 /** The key in each data object that contains the timestamp */
10 timestampKey: string
11 /** The key(s) to fill with default values when gaps exist */
12 valueKey: string | string[]
13 /** Default value to use for gaps */
14 defaultValue: number
15 /** Start of the time range (ISO string) */
16 startDate?: string
17 /** End of the time range (ISO string) */
18 endDate?: string
19 /** Minimum number of points before filling is applied */
20 minPointsToFill?: number
21 /** Optional interval specification (e.g., '5m', '1h') */
22 interval?: string
23}
24
25export type FillTimeseriesResult<T extends Datum = Datum> = {
26 data: T[]
27 error: Error | null
28 isError: boolean
29}
30
31/**
32 * Sorts timeseries data by timestamp in ascending order
33 * Returns a new sorted array without mutating the input
34 */
35export function sortByTimestamp<T extends Datum>(data: T[], timestampKey: string): T[] {
36 return [...data].sort((a, b) => {
37 return (
38 new Date(a[timestampKey] as string).getTime() - new Date(b[timestampKey] as string).getTime()
39 )
40 })
41}
42
43/**
44 * Validates that the data has a valid timestamp key
45 */
46export function hasValidTimestamp<T extends Datum>(data: T[], timestampKey: string): boolean {
47 return Boolean(data[0]?.[timestampKey])
48}
49
50/**
51 * Convenience hook for memoized filling of timeseries data.
52 *
53 * Fills gaps in timeseries data and sorts results by timestamp.
54 *
55 * @example
56 * ```ts
57 * const { data, error, isError } = useFillTimeseriesSorted({
58 * data: chartData,
59 * timestampKey: 'timestamp',
60 * valueKey: 'count',
61 * defaultValue: 0,
62 * startDate: startIso,
63 * endDate: endIso
64 * })
65 * ```
66 */
67export const useFillTimeseriesSorted = <T extends Datum = Datum>(
68 options: FillTimeseriesOptions<T>
69): FillTimeseriesResult<T> => {
70 const {
71 data,
72 timestampKey,
73 valueKey,
74 defaultValue,
75 startDate,
76 endDate,
77 minPointsToFill = 20,
78 interval,
79 } = options
80
81 return useMemo(() => {
82 // Early return if no valid timestamp
83 if (!hasValidTimestamp(data, timestampKey)) {
84 return {
85 data,
86 error: null,
87 isError: false,
88 }
89 }
90
91 try {
92 const filled = fillTimeseries(
93 data,
94 timestampKey,
95 valueKey,
96 defaultValue,
97 startDate,
98 endDate,
99 minPointsToFill,
100 interval
101 ) as T[]
102
103 const sorted = sortByTimestamp(filled, timestampKey)
104
105 return {
106 data: sorted,
107 error: null,
108 isError: false,
109 }
110 } catch (error: unknown) {
111 return {
112 data: [],
113 error: error instanceof Error ? error : new Error(String(error)),
114 isError: true,
115 }
116 }
117 }, [
118 JSON.stringify(data),
119 timestampKey,
120 JSON.stringify(valueKey),
121 defaultValue,
122 startDate,
123 endDate,
124 minPointsToFill,
125 interval,
126 ])
127}