@@ -65,10 +68,10 @@ export const Publishers = async () => {
corner={
}
className={styles.statCard ?? ""}
stat={(
- pythnetPublishers.reduce(
- (sum, publisher) => sum + publisher.averageScore,
+ rankedPublishers.reduce(
+ (sum, publisher) => sum + (publisher.averageScore ?? 0),
0,
- ) / pythnetPublishers.length
+ ) / rankedPublishers.length
).toFixed(2)}
/>
>[number]) => {
+}: Awaited>[number]) => {
const knownPublisher = lookupPublisher(key);
return {
id: key,
diff --git a/apps/insights/src/components/Publishers/publishers-card.tsx b/apps/insights/src/components/Publishers/publishers-card.tsx
index 4f28ca83e4..42d24906e4 100644
--- a/apps/insights/src/components/Publishers/publishers-card.tsx
+++ b/apps/insights/src/components/Publishers/publishers-card.tsx
@@ -45,10 +45,10 @@ type Props = {
type Publisher = {
id: string;
- ranking: number;
+ ranking?: number | undefined;
permissionedFeeds: number;
- activeFeeds: number;
- averageScore: number;
+ activeFeeds?: number | undefined;
+ averageScore?: number | undefined;
} & (
| { name: string; icon: ReactNode }
| { name?: undefined; icon?: undefined }
@@ -100,27 +100,38 @@ const ResolvedPublishersCard = ({
filter.contains(publisher.id, search) ||
(publisher.name !== undefined && filter.contains(publisher.name, search)),
(a, b, { column, direction }) => {
+ const desc = direction === "descending" ? -1 : 1;
+
+ const sortByName =
+ desc * collator.compare(a.name ?? a.id, b.name ?? b.id);
+
+ const sortByRankingField = (
+ column: "ranking" | "activeFeeds" | "averageScore",
+ ) => {
+ if (a[column] === undefined) {
+ return b[column] === undefined ? sortByName : 1;
+ } else {
+ return b[column] === undefined ? -1 : desc * (a[column] - b[column]);
+ }
+ };
+
switch (column) {
+ case "permissionedFeeds": {
+ return desc * (a[column] - b[column]);
+ }
+
case "ranking":
- case "permissionedFeeds":
case "activeFeeds":
case "averageScore": {
- return (
- (direction === "descending" ? -1 : 1) * (a[column] - b[column])
- );
+ return sortByRankingField(column);
}
case "name": {
- return (
- (direction === "descending" ? -1 : 1) *
- collator.compare(a.name ?? a.id, b.name ?? b.id)
- );
+ return sortByName;
}
default: {
- return (
- (direction === "descending" ? -1 : 1) * (a.ranking - b.ranking)
- );
+ return sortByRankingField("ranking");
}
}
},
@@ -144,7 +155,7 @@ const ResolvedPublishersCard = ({
textValue: publisher.name ?? id,
prefetch: false,
data: {
- ranking: {ranking},
+ ranking: ranking !== undefined && {ranking},
name: (
),
- averageScore: (
+ averageScore: averageScore !== undefined && (
),
},
diff --git a/apps/insights/src/components/Root/index.tsx b/apps/insights/src/components/Root/index.tsx
index 679a7b6938..35954de1a0 100644
--- a/apps/insights/src/components/Root/index.tsx
+++ b/apps/insights/src/components/Root/index.tsx
@@ -3,18 +3,18 @@ import { lookup as lookupPublisher } from "@pythnetwork/known-publishers";
import { NuqsAdapter } from "nuqs/adapters/next/app";
import type { ReactNode } from "react";
+import { SearchButton as SearchButtonImpl } from "./search-button";
import {
AMPLITUDE_API_KEY,
ENABLE_ACCESSIBILITY_REPORTING,
GOOGLE_ANALYTICS_ID,
} from "../../config/server";
+import { getPublishersWithRankings } from "../../get-publishers-with-rankings";
import { LivePriceDataProvider } from "../../hooks/use-live-price-data";
-import { getPublishers } from "../../services/clickhouse";
import { Cluster } from "../../services/pyth";
import { getFeeds } from "../../services/pyth/get-feeds";
import { PriceFeedIcon } from "../PriceFeedIcon";
import { PublisherIcon } from "../PublisherIcon";
-import { SearchButton as SearchButtonImpl } from "./search-button";
export const TABS = [
{ segment: "", children: "Overview" },
@@ -53,7 +53,7 @@ const SearchButton = async () => {
};
const getPublishersForSearchDialog = async (cluster: Cluster) => {
- const publishers = await getPublishers(cluster);
+ const publishers = await getPublishersWithRankings(cluster);
return publishers.map((publisher) => {
const knownPublisher = lookupPublisher(publisher.key);
diff --git a/apps/insights/src/components/Root/search-button.tsx b/apps/insights/src/components/Root/search-button.tsx
index d80da0bc1c..db5ae16251 100644
--- a/apps/insights/src/components/Root/search-button.tsx
+++ b/apps/insights/src/components/Root/search-button.tsx
@@ -54,7 +54,7 @@ type ResolvedSearchButtonProps = {
}[];
publishers: ({
publisherKey: string;
- averageScore: number;
+ averageScore?: number | undefined;
cluster: Cluster;
} & (
| { name: string; icon: ReactNode }
@@ -291,7 +291,9 @@ const SearchDialogContents = ({
<>
Average Score
-
+ {result.averageScore !== undefined && (
+
+ )}
>
)}
@@ -335,7 +337,9 @@ const SearchDialogContents = ({
icon: result.icon,
})}
/>
-
+ {result.averageScore !== undefined && (
+
+ )}
>
)}
diff --git a/apps/insights/src/get-publishers-with-rankings.ts b/apps/insights/src/get-publishers-with-rankings.ts
new file mode 100644
index 0000000000..2d26cca04b
--- /dev/null
+++ b/apps/insights/src/get-publishers-with-rankings.ts
@@ -0,0 +1,23 @@
+import { getPublishers } from "./server/pyth";
+import { getPublisherRankings } from "./services/clickhouse";
+import type { Cluster } from "./services/pyth";
+
+export const getPublishersWithRankings = async (cluster: Cluster) => {
+ const [publishers, publisherRankings] = await Promise.all([
+ getPublishers(cluster),
+ getPublisherRankings(cluster),
+ ]);
+
+ return publishers
+ .map((publisher) => ({
+ ...publisher,
+ ...publisherRankings.find((ranking) => ranking.key === publisher.key),
+ }))
+ .toSorted((a, b) => {
+ if (a.rank === undefined) {
+ return b.rank === undefined ? a.key.localeCompare(b.key) : 1;
+ } else {
+ return b.rank === undefined ? -1 : a.rank - b.rank;
+ }
+ });
+};
diff --git a/apps/insights/src/server/pyth.ts b/apps/insights/src/server/pyth.ts
index 45f1cdf1fa..4455fbd346 100644
--- a/apps/insights/src/server/pyth.ts
+++ b/apps/insights/src/server/pyth.ts
@@ -11,54 +11,43 @@ export async function getPublishersForFeedRequest(
cluster: Cluster,
symbol: string,
) {
- const url = new URL(
- `/api/pyth/get-publishers/${encodeURIComponent(symbol)}`,
- await getHost(),
+ const data = await fetchPythData(
+ cluster,
+ `get-publishers/${encodeURIComponent(symbol)}`,
);
- url.searchParams.set("cluster", ClusterToName[cluster]);
-
- const data = await fetch(url, {
- next: {
- revalidate: DEFAULT_NEXT_FETCH_TTL,
- },
- headers: VERCEL_REQUEST_HEADERS,
- });
- const parsedData: unknown = await data.json();
- return z.array(z.string()).parse(parsedData);
+ return z.array(z.string()).parse(await data.json());
}
+export const getPublishers = async (cluster: Cluster) => {
+ const data = await fetchPythData(cluster, `get-publishers`);
+ return publishersSchema.parse(await data.json());
+};
+
+const publishersSchema = z.array(
+ z.strictObject({
+ key: z.string(),
+ permissionedFeeds: z.number(),
+ }),
+);
+
export async function getFeedsForPublisherRequest(
cluster: Cluster,
publisher: string,
) {
- const url = new URL(
- `/api/pyth/get-feeds-for-publisher/${encodeURIComponent(publisher)}`,
- await getHost(),
+ const data = await fetchPythData(
+ cluster,
+ `get-feeds-for-publisher/${encodeURIComponent(publisher)}`,
);
- url.searchParams.set("cluster", ClusterToName[cluster]);
-
- const data = await fetch(url, {
- next: {
- revalidate: DEFAULT_NEXT_FETCH_TTL,
- },
- headers: VERCEL_REQUEST_HEADERS,
- });
const rawData = await data.text();
const parsedData = parse(rawData);
return priceFeedsSchema.parse(parsedData);
}
export const getFeedsRequest = async (cluster: Cluster) => {
- const url = new URL(`/api/pyth/get-feeds`, await getHost());
- url.searchParams.set("cluster", ClusterToName[cluster]);
- url.searchParams.set("excludePriceComponents", "true");
-
- const data = await fetch(url, {
- next: {
- revalidate: DEFAULT_NEXT_FETCH_TTL,
- },
- headers: VERCEL_REQUEST_HEADERS,
+ const data = await fetchPythData(cluster, "get-feeds", {
+ excludePriceComponents: "true",
});
+
const rawData = await data.text();
const parsedData = parse(rawData);
@@ -75,18 +64,10 @@ export const getFeedForSymbolRequest = async ({
symbol: string;
cluster?: Cluster;
}): Promise