Skip to content

Commit 7934831

Browse files
authored
Merge branch 'main' into chore(dev-hub)-header-improvements
2 parents 90c8335 + 554e0f4 commit 7934831

File tree

22 files changed

+571
-120
lines changed

22 files changed

+571
-120
lines changed

apps/insights/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
"bs58": "catalog:",
3333
"clsx": "catalog:",
3434
"cryptocurrency-icons": "catalog:",
35+
"date-fns": "catalog:",
36+
"csv-stringify": "catalog:",
3537
"dnum": "catalog:",
3638
"ioredis": "^5.7.0",
3739
"lightweight-charts": "catalog:",
@@ -46,8 +48,8 @@
4648
"superjson": "catalog:",
4749
"swr": "catalog:",
4850
"zod": "catalog:",
49-
"zod-validation-error": "catalog:",
50-
"zod-search-params": "catalog:"
51+
"zod-search-params": "catalog:",
52+
"zod-validation-error": "catalog:"
5153
},
5254
"devDependencies": {
5355
"@cprussin/eslint-config": "catalog:",

apps/insights/src/app/api/pyth/get-feeds/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@ export const GET = async (request: NextRequest) => {
3737

3838
const queryParamsSchema = z.object({
3939
cluster: z.enum(CLUSTER_NAMES).transform((value) => toCluster(value)),
40-
excludePriceComponents: z.boolean(),
40+
excludePriceComponents: z.boolean().optional().default(false),
4141
});

apps/insights/src/cache.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,24 @@ const transformer = {
99
deserialize: parse,
1010
};
1111

12-
// export const DEFAULT_CACHE_TTL = 3600; // 1 hour
13-
// export const DEFAULT_CACHE_STALE = 86_400; // 24 hours
14-
15-
export const DEFAULT_CACHE_TTL = 60; // 1 minute
16-
export const DEFAULT_CACHE_STALE = 60; // 1 minute
12+
/**
13+
* - API routes will be cached for 1 hour
14+
* - Cached function will be cached for 10 minutes,
15+
* If the function is called within 1 hour, it will
16+
* still be served from the cache, but also fetch the latest data
17+
*/
18+
export const DEFAULT_NEXT_FETCH_TTL = 3600; // 1 hour
19+
export const DEFAULT_REDIS_CACHE_TTL = 60 * 10; // 10 minutes
20+
export const DEFAULT_REDIS_CACHE_STALE = 3600; // 1 hour
1721

1822
export const redisCache: ACDCache = createCache({
1923
transformer,
20-
stale: DEFAULT_CACHE_STALE,
21-
ttl: DEFAULT_CACHE_TTL,
24+
stale: DEFAULT_REDIS_CACHE_STALE,
25+
ttl: DEFAULT_REDIS_CACHE_TTL,
2226
storage: {
2327
type: "redis",
2428
options: {
2529
client: getRedis(),
2630
},
2731
},
2832
});
29-
30-
export const memoryOnlyCache: ACDCache = createCache({
31-
ttl: DEFAULT_CACHE_TTL,
32-
stale: DEFAULT_CACHE_STALE,
33-
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@use "@pythnetwork/component-library/theme";
2+
3+
.conformanceReport {
4+
display: flex;
5+
gap: theme.spacing(2);
6+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"use client";
2+
3+
import { Download } from "@phosphor-icons/react/dist/ssr/Download";
4+
import { Button } from "@pythnetwork/component-library/Button";
5+
import { Select } from "@pythnetwork/component-library/Select";
6+
import { useAlert } from "@pythnetwork/component-library/useAlert";
7+
import { useLogger } from "@pythnetwork/component-library/useLogger";
8+
import { useCallback, useState } from "react";
9+
10+
import styles from "./conformance-report.module.scss";
11+
import type { Interval } from "./types";
12+
import { INTERVALS } from "./types";
13+
14+
type ConformanceReportProps = {
15+
onClick: (timeframe: Interval) => Promise<void>;
16+
};
17+
18+
const ConformanceReport = (props: ConformanceReportProps) => {
19+
const [timeframe, setTimeframe] = useState<Interval>(INTERVALS[0]);
20+
const [isGeneratingReport, setIsGeneratingReport] = useState(false);
21+
const { open } = useAlert();
22+
const logger = useLogger();
23+
24+
/**
25+
* Download the conformance report for the given symbol or publisher
26+
*/
27+
const downloadReport = useCallback(async () => {
28+
await props.onClick(timeframe);
29+
}, [props, timeframe]);
30+
31+
const handleReport = () => {
32+
setIsGeneratingReport(true);
33+
downloadReport()
34+
.catch((error: unknown) => {
35+
open({
36+
title: "Error",
37+
contents: "Error generating conformance report",
38+
});
39+
logger.error(error);
40+
})
41+
.finally(() => {
42+
setIsGeneratingReport(false);
43+
});
44+
};
45+
46+
return (
47+
<div className={styles.conformanceReport}>
48+
<Select
49+
options={INTERVALS.map((interval) => ({ id: interval }))}
50+
placement="bottom end"
51+
selectedKey={timeframe}
52+
onSelectionChange={setTimeframe}
53+
size="sm"
54+
label="Timeframe"
55+
variant="outline"
56+
hideLabel
57+
/>
58+
<Button
59+
variant="outline"
60+
size="sm"
61+
onClick={handleReport}
62+
afterIcon={<Download key="download" />}
63+
isPending={isGeneratingReport}
64+
>
65+
Report
66+
</Button>
67+
</div>
68+
);
69+
};
70+
71+
export default ConformanceReport;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const WEB_API_BASE_URL = "https://web-api.pyth.network";
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const INTERVALS = ["24H", "48H", "72H", "1W", "1M"] as const;
2+
export type Interval = (typeof INTERVALS)[number];
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { useCallback } from "react";
2+
3+
import { WEB_API_BASE_URL } from "./constants";
4+
import type { Interval } from "./types";
5+
import { useDownloadBlob } from "../../hooks/use-download-blob";
6+
import { CLUSTER_NAMES } from "../../services/pyth";
7+
8+
const PYTHTEST_CONFORMANCE_REFERENCE_PUBLISHER =
9+
"HUZu4xMSHbxTWbkXR6jkGdjvDPJLjrpSNXSoUFBRgjWs";
10+
11+
export const useDownloadReportForFeed = () => {
12+
const download = useDownloadBlob();
13+
14+
return useCallback(
15+
async ({
16+
symbol,
17+
publisher,
18+
timeframe,
19+
cluster,
20+
}: {
21+
symbol: string;
22+
publisher: string;
23+
timeframe: Interval;
24+
cluster: (typeof CLUSTER_NAMES)[number];
25+
}) => {
26+
const url = new URL("/metrics/conformance", WEB_API_BASE_URL);
27+
url.searchParams.set("symbol", symbol);
28+
url.searchParams.set("range", timeframe);
29+
url.searchParams.set("cluster", cluster);
30+
url.searchParams.set("publisher", publisher);
31+
32+
if (cluster === "pythtest-conformance") {
33+
url.searchParams.set(
34+
"pythnet_aggregate_publisher",
35+
PYTHTEST_CONFORMANCE_REFERENCE_PUBLISHER,
36+
);
37+
}
38+
39+
const response = await fetch(url, {
40+
headers: new Headers({
41+
Accept: "application/octet-stream",
42+
}),
43+
});
44+
const blob = await response.blob();
45+
download(
46+
blob,
47+
`${publisher}-${symbol
48+
.split("/")
49+
.join("")}-${timeframe}-${cluster}-conformance-report.tsv`,
50+
);
51+
},
52+
[download],
53+
);
54+
};

0 commit comments

Comments
 (0)