Skip to content

Commit 255bd78

Browse files
committed
PR feedback
1 parent 1b23216 commit 255bd78

File tree

5 files changed

+90
-113
lines changed

5 files changed

+90
-113
lines changed

apps/insights/src/app/historical-prices/route.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ const queryParamsSchema = z.object({
99
.string()
1010
.nullable()
1111
.transform((value) => value ?? undefined),
12-
cluster: z.enum(["pythnet", "pythtest"]),
1312
from: z.string().transform(Number),
1413
to: z.string().transform(Number),
1514
resolution: z.enum(["1s", "1m", "5m", "1H", "1D"]).transform((value) => {
@@ -48,11 +47,9 @@ export async function GET(req: NextRequest) {
4847
});
4948
}
5049

51-
const { symbol, publisher, cluster, from, to, resolution } = parsed.data;
50+
const { symbol, publisher, from, to, resolution } = parsed.data;
5251

53-
try {
54-
checkMaxDataPointsInvariant(from, to, resolution);
55-
} catch {
52+
if (getNumDataPoints(to, from, resolution) > MAX_DATA_POINTS) {
5653
return new Response("Unsupported resolution for date range", {
5754
status: 400,
5855
});
@@ -63,17 +60,16 @@ export async function GET(req: NextRequest) {
6360
from,
6461
to,
6562
publisher,
66-
cluster,
6763
resolution,
6864
});
6965

7066
return Response.json(res);
7167
}
7268

7369
const MAX_DATA_POINTS = 3000;
74-
function checkMaxDataPointsInvariant(
75-
from: number,
70+
function getNumDataPoints(
7671
to: number,
72+
from: number,
7773
resolution: "1 SECOND" | "1 MINUTE" | "5 MINUTE" | "1 HOUR" | "1 DAY",
7874
) {
7975
let diff = to - from;
@@ -96,7 +92,5 @@ function checkMaxDataPointsInvariant(
9692
}
9793
}
9894

99-
if (diff > MAX_DATA_POINTS) {
100-
throw new Error("Unsupported resolution for date range");
101-
}
95+
return diff;
10296
}

apps/insights/src/components/PriceFeed/Chart/chart-toolbar.tsx

Lines changed: 30 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,52 @@ import { useLogger } from "@pythnetwork/component-library/useLogger";
55
import { useCallback } from "react";
66
import type { Key } from "react-aria";
77

8-
import type { Lookback, Resolution } from "./use-chart-toolbar";
8+
import type { QuickSelectWindow, Resolution } from "./use-chart-toolbar";
99
import {
10-
LOOKBACK_TO_RESOLUTION,
11-
LOOKBACKS,
12-
RESOLUTION_TO_LOOKBACK,
10+
QUICK_SELECT_WINDOW_TO_RESOLUTION,
11+
QUICK_SELECT_WINDOWS,
12+
RESOLUTION_TO_QUICK_SELECT_WINDOW,
1313
RESOLUTIONS,
14-
useChartLookback,
14+
useChartQuickSelectWindow,
1515
useChartResolution,
1616
} from "./use-chart-toolbar";
1717

1818
const ENABLE_RESOLUTION_SELECTOR = false;
1919

2020
export const ChartToolbar = () => {
2121
const logger = useLogger();
22-
const [lookback, setLookback] = useChartLookback();
22+
const [quickSelectWindow, setQuickSelectWindow] = useChartQuickSelectWindow();
2323
const [resolution, setResolution] = useChartResolution();
2424

25-
const handleLookbackChange = useCallback(
26-
(newValue: Key) => {
27-
if (!isLookback(newValue)) {
28-
throw new TypeError("Invalid lookback");
29-
}
30-
const lookback: Lookback = newValue;
31-
setLookback(lookback).catch((error: unknown) => {
32-
logger.error("Failed to update lookback", error);
25+
const handleResolutionChanged = useCallback(
26+
(resolution: Resolution) => {
27+
setResolution(resolution).catch((error: unknown) => {
28+
logger.error("Failed to update resolution", error);
3329
});
34-
setResolution(LOOKBACK_TO_RESOLUTION[lookback]).catch(
30+
setQuickSelectWindow(RESOLUTION_TO_QUICK_SELECT_WINDOW[resolution]).catch(
3531
(error: unknown) => {
36-
logger.error("Failed to update resolution", error);
32+
logger.error("Failed to update quick select window", error);
3733
},
3834
);
3935
},
40-
[logger, setLookback, setResolution],
36+
[logger, setResolution, setQuickSelectWindow],
4137
);
4238

43-
const handleResolutionChanged = useCallback(
44-
(newValue: Key) => {
45-
if (!isResolution(newValue)) {
46-
throw new TypeError("Invalid resolution");
39+
const handleQuickSelectWindowChange = useCallback(
40+
(quickSelectWindow: Key) => {
41+
if (!isQuickSelectWindow(quickSelectWindow)) {
42+
throw new TypeError("Invalid quick select window");
4743
}
48-
const resolution: Resolution = newValue;
49-
setResolution(resolution).catch((error: unknown) => {
50-
logger.error("Failed to update resolution", error);
44+
setQuickSelectWindow(quickSelectWindow).catch((error: unknown) => {
45+
logger.error("Failed to update quick select window", error);
5146
});
52-
setLookback(RESOLUTION_TO_LOOKBACK[resolution]).catch(
47+
setResolution(QUICK_SELECT_WINDOW_TO_RESOLUTION[quickSelectWindow]).catch(
5348
(error: unknown) => {
54-
logger.error("Failed to update lookback", error);
49+
logger.error("Failed to update resolution", error);
5550
},
5651
);
5752
},
58-
[logger, setResolution, setLookback],
53+
[logger, setQuickSelectWindow, setResolution],
5954
);
6055

6156
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -78,25 +73,18 @@ export const ChartToolbar = () => {
7873
variant="outline"
7974
/>
8075
<SingleToggleGroup
81-
selectedKey={lookback}
82-
onSelectionChange={handleLookbackChange}
76+
selectedKey={quickSelectWindow}
77+
onSelectionChange={handleQuickSelectWindowChange}
8378
rounded
84-
items={[
85-
{ id: "1m", children: "1m" },
86-
{ id: "1H", children: "1H" },
87-
{ id: "1D", children: "1D" },
88-
{ id: "1W", children: "1W" },
89-
{ id: "1M", children: "1M" },
90-
]}
79+
items={QUICK_SELECT_WINDOWS.map((quickSelectWindow) => ({
80+
id: quickSelectWindow,
81+
children: quickSelectWindow,
82+
}))}
9183
/>
9284
</>
9385
);
9486
};
9587

96-
function isLookback(value: Key): value is Lookback {
97-
return LOOKBACKS.includes(value as Lookback);
98-
}
99-
100-
function isResolution(value: Key): value is Resolution {
101-
return RESOLUTIONS.includes(value as Resolution);
88+
function isQuickSelectWindow(value: Key): value is QuickSelectWindow {
89+
return QUICK_SELECT_WINDOWS.includes(value as QuickSelectWindow);
10290
}

apps/insights/src/components/PriceFeed/Chart/chart.tsx

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import { z } from "zod";
2828

2929
import styles from "./chart.module.scss";
3030
import {
31-
lookbackToMilliseconds,
32-
useChartLookback,
31+
quickSelectWindowToMilliseconds,
32+
useChartQuickSelectWindow,
3333
useChartResolution,
3434
} from "./use-chart-toolbar";
3535
import { useLivePriceData } from "../../../hooks/use-live-price-data";
@@ -62,19 +62,14 @@ const useChart = (symbol: string, feedId: string) => {
6262

6363
const useChartElem = (symbol: string, feedId: string) => {
6464
const logger = useLogger();
65-
const [lookback] = useChartLookback();
65+
const [quickSelectWindow] = useChartQuickSelectWindow();
6666
const [resolution] = useChartResolution();
6767
const chartContainerRef = useRef<HTMLDivElement | null>(null);
6868
const chartRef = useRef<ChartRefContents | undefined>(undefined);
6969
const isBackfilling = useRef(false);
7070
const priceFormatter = usePriceFormatter();
71-
const resolutionRef = useRef(resolution);
7271
const abortControllerRef = useRef<AbortController | undefined>(undefined);
7372

74-
useEffect(() => {
75-
resolutionRef.current = resolution;
76-
}, [resolution]);
77-
7873
const { current: livePriceData } = useLivePriceData(Cluster.Pythnet, feedId);
7974

8075
const didResetVisibleRange = useRef(false);
@@ -154,31 +149,33 @@ const useChartElem = (symbol: string, feedId: string) => {
154149
});
155150

156151
fetch(url, { signal: abortControllerRef.current.signal })
157-
.then(async (data) => historicalDataSchema.parse(await data.json()))
158-
.then((data) => {
152+
.then((rawData) => rawData.json())
153+
.then((jsonData) => {
159154
if (!chartRef.current) {
160155
return;
161156
}
162157

158+
const data = historicalDataSchema.parse(jsonData);
159+
163160
// Get the current historical price data
164-
const currentHistoricalPriceData = chartRef.current.price
165-
.data()
166-
.filter((d) => isLineData(d));
161+
// Note that .data() returns (WhitespaceData | LineData)[], hence the type cast
162+
const currentHistoricalPriceData =
163+
chartRef.current.price.data() as LineData[];
167164
const currentHistoricalConfidenceHighData =
168-
chartRef.current.confidenceHigh.data().filter((d) => isLineData(d));
165+
chartRef.current.confidenceHigh.data() as LineData[];
169166
const currentHistoricalConfidenceLowData =
170-
chartRef.current.confidenceLow.data().filter((d) => isLineData(d));
167+
chartRef.current.confidenceLow.data() as LineData[];
171168

172169
const newHistoricalPriceData = data.map((d) => ({
173-
time: Number(d.timestamp) as UTCTimestamp,
170+
time: d.time,
174171
value: d.price,
175172
}));
176173
const newHistoricalConfidenceHighData = data.map((d) => ({
177-
time: Number(d.timestamp) as UTCTimestamp,
174+
time: d.time,
178175
value: d.price + d.confidence,
179176
}));
180177
const newHistoricalConfidenceLowData = data.map((d) => ({
181-
time: Number(d.timestamp) as UTCTimestamp,
178+
time: d.time,
182179
value: d.price - d.confidence,
183180
}));
184181

@@ -273,15 +270,15 @@ const useChartElem = (symbol: string, feedId: string) => {
273270
const newToMs = firstMs;
274271
const newFromMs = startOfResolution(
275272
new Date(newToMs - visibleRangeMs),
276-
resolutionRef.current,
273+
resolution,
277274
);
278275

279276
// When we're getting close to the earliest data, we need to backfill more
280277
if (remainingDataMs <= threshold) {
281278
fetchHistoricalData({
282279
from: newFromMs / 1000,
283280
to: newToMs / 1000,
284-
resolution: resolutionRef.current,
281+
resolution,
285282
});
286283
}
287284
});
@@ -306,7 +303,9 @@ const useChartElem = (symbol: string, feedId: string) => {
306303
const now = new Date();
307304
const to = startOfResolution(now, resolution);
308305
const from = startOfResolution(
309-
new Date(now.getTime() - lookbackToMilliseconds(lookback)),
306+
new Date(
307+
now.getTime() - quickSelectWindowToMilliseconds(quickSelectWindow),
308+
),
310309
resolution,
311310
);
312311

@@ -326,7 +325,7 @@ const useChartElem = (symbol: string, feedId: string) => {
326325
to: to / 1000,
327326
resolution,
328327
});
329-
}, [lookback, resolution, fetchHistoricalData]);
328+
}, [quickSelectWindow, resolution, fetchHistoricalData]);
330329

331330
return { chartRef, chartContainerRef };
332331
};
@@ -340,11 +339,17 @@ type ChartRefContents = {
340339
};
341340

342341
const historicalDataSchema = z.array(
343-
z.strictObject({
344-
timestamp: z.number().transform(BigInt),
345-
price: z.number(),
346-
confidence: z.number(),
347-
}),
342+
z
343+
.strictObject({
344+
timestamp: z.number(),
345+
price: z.number(),
346+
confidence: z.number(),
347+
})
348+
.transform((d) => ({
349+
time: Number(d.timestamp) as UTCTimestamp,
350+
price: d.price,
351+
confidence: d.confidence,
352+
})),
348353
);
349354
const priceFormat = {
350355
type: "price",
@@ -443,17 +448,12 @@ const getColors = (container: HTMLDivElement, resolvedTheme: string) => {
443448
};
444449
};
445450

446-
function isLineData(data: LineData | WhitespaceData): data is LineData {
447-
return "time" in data && "value" in data;
448-
}
449-
450451
/**
451452
* Merge (and sort) two arrays of line data, deduplicating by time
452453
*/
453454
export function mergeData(as: LineData[], bs: LineData[]) {
454455
const unique = new Map<number, LineData>();
455456

456-
// TODO fhqvst Can optimize with while's
457457
for (const a of as) {
458458
unique.set(a.time as number, a);
459459
}

apps/insights/src/components/PriceFeed/Chart/use-chart-toolbar.tsx

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { parseAsStringLiteral, useQueryState } from "nuqs";
33
export const RESOLUTIONS = ["1s", "1m", "5m", "1H", "1D"] as const;
44
export type Resolution = (typeof RESOLUTIONS)[number];
55

6-
export const LOOKBACKS = ["1m", "1H", "1D", "1W", "1M"] as const;
7-
export type Lookback = (typeof LOOKBACKS)[number];
6+
export const QUICK_SELECT_WINDOWS = ["1m", "1H", "1D", "1W", "1M"] as const;
7+
export type QuickSelectWindow = (typeof QUICK_SELECT_WINDOWS)[number];
88

9-
export function useChartLookback() {
9+
export function useChartQuickSelectWindow() {
1010
return useQueryState(
11-
"lookback",
12-
parseAsStringLiteral(LOOKBACKS).withDefault("1m"),
11+
"quickSelectWindow",
12+
parseAsStringLiteral(QUICK_SELECT_WINDOWS).withDefault("1m"),
1313
);
1414
}
1515

@@ -20,9 +20,14 @@ export function useChartResolution() {
2020
);
2121
}
2222

23-
// TODO fhqvst Clean this up - it's confusing
24-
export function lookbackToMilliseconds(lookback: Lookback): number {
25-
switch (lookback) {
23+
/**
24+
* Converts a quick select window string (e.g., "1m", "1H", "1D") to its equivalent duration in milliseconds.
25+
* Used to determine the time range for chart data based on user selection.
26+
*/
27+
export function quickSelectWindowToMilliseconds(
28+
quickSelectWindow: QuickSelectWindow,
29+
): number {
30+
switch (quickSelectWindow) {
2631
case "1m": {
2732
return 60_000;
2833
}
@@ -41,15 +46,21 @@ export function lookbackToMilliseconds(lookback: Lookback): number {
4146
}
4247
}
4348

44-
export const RESOLUTION_TO_LOOKBACK: Record<Resolution, Lookback> = {
49+
export const RESOLUTION_TO_QUICK_SELECT_WINDOW: Record<
50+
Resolution,
51+
QuickSelectWindow
52+
> = {
4553
"1s": "1m",
4654
"1m": "1H",
4755
"5m": "1D",
4856
"1H": "1W",
4957
"1D": "1M",
5058
};
5159

52-
export const LOOKBACK_TO_RESOLUTION: Record<Lookback, Resolution> = {
60+
export const QUICK_SELECT_WINDOW_TO_RESOLUTION: Record<
61+
QuickSelectWindow,
62+
Resolution
63+
> = {
5364
"1m": "1s",
5465
"1H": "1m",
5566
"1D": "5m",

0 commit comments

Comments
 (0)