Skip to content

chore(price-feed): added update parameters as a column in each table for EVM #739

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 14, 2025
195 changes: 195 additions & 0 deletions components/SponsoredFeedsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { useState } from "react";
import CopyIcon from "./icons/CopyIcon";

interface SponsoredFeed {
name: string;
priceFeedId: string;
updateParameters: string;
}

interface SponsoredFeedsTableProps {
feeds: SponsoredFeed[];
networkName: string;
}

export const SponsoredFeedsTable = ({
feeds,
networkName,
}: SponsoredFeedsTableProps) => {
const [copiedId, setCopiedId] = useState<string | null>(null);

const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text).then(() => {
setCopiedId(text);
setTimeout(() => setCopiedId(null), 2000);
});
};

// Calculate parameter statistics
const paramCounts = feeds.reduce((acc, feed) => {
acc[feed.updateParameters] = (acc[feed.updateParameters] || 0) + 1;
return acc;
}, {} as Record<string, number>);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super minor nit but I tend to find this kind of thing is more easy to understand when implemented as:

const paramCounts = Object.fromEntries(
  Object
    .entries(Object.groupBy(feeds, feed => feed.updateParameters))
    .map(([updateParameters, feeds]) => [updateParameters, feeds.length])
)

I will also typically create a mapValues helper (which really should be in the ES standard IMO and probably will be eventually) which I use all the time and makes the code more concise:

const paramCounts = mapValues(
  Object.groupBy(feeds, feed => feed.updateParameters)),
  feeds => feeds.length
)

For some reason, reduce seems to be a hard operation for a lot of folks to easily grok so I've started to move away from using it except in very specific scenarios. Here is a decent writeup for an eslint rule that I use nowadays that has some links to some threads on the topic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, I have created a utility file for object called "ObjectHelpers" in the utils folder so can be used across.
I will see to add a linter rule as well, good idea and thanks for sharing the reference that really helps.
for ensuring Object.groupBy can be used, had to increase the typescript version as it was not present in older versions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what it's worth, I maintain canned eslint and typescript configs which we use across most node packages here, and I try to keep things in the main monorepo up to date, which applies to a pretty large bulk of our Node work. However, this docs repo is just an old repo and we've been planning to replace the docs app for a long time, so it's in a bit of a weird state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect! Thanks a lot for sharing this. Yeah, I will import these config in my local and gonna make sure if we have these same config applicable to this documentation repo.


const defaultParams = Object.entries(paramCounts).sort(
([, a], [, b]) => b - a
)[0][0];

// Calculate table height based on number of items
// Each row is approximately 40px (py-2 = 8px top + 8px bottom + content height)
// Header is approximately 48px (py-2 = 8px top + 8px bottom + font height)
// Show 7 rows by default, then scroll - but maintain consistent minimum height
const maxVisibleRows = 7;
const shouldScroll = feeds.length > maxVisibleRows;
const rowHeight = 56; // Increased row height to account for actual content height
const headerHeight = 48; // Header height in pixels
const exactTableHeight = `${headerHeight + maxVisibleRows * rowHeight}px`; // Exact height for 7 rows
const tableHeight = shouldScroll ? exactTableHeight : "auto"; // Use exact height for scrollable tables

return (
<div className="my-6">
<p className="mb-3">
The price feeds listed in the table below are currently sponsored in{" "}
<strong>{networkName}</strong>.
</p>

<div className="border border-gray-200 dark:border-gray-700 rounded-lg">
{/* Summary bar */}
<div className="bg-blue-50 dark:bg-blue-900/20 px-3 py-2 border-b border-gray-200 dark:border-gray-600">
<div className="flex flex-wrap items-center gap-4 text-sm">
<div className="flex items-center gap-1.5">
<div className="w-1.5 h-1.5 bg-green-500 rounded-full flex-shrink-0"></div>
<span className="font-medium">Default:</span>
<span
dangerouslySetInnerHTML={{
__html: defaultParams.replace("<br/>", " / "),
}}
/>
<span className="text-gray-500">
({paramCounts[defaultParams]})
</span>
</div>
{Object.entries(paramCounts)
.filter(([params]) => params !== defaultParams)
.map(([params, count]) => (
<div key={params} className="flex items-center gap-1.5">
<div className="w-1.5 h-1.5 bg-orange-500 rounded-full flex-shrink-0"></div>
<span className="font-medium">Exception:</span>
<span
dangerouslySetInnerHTML={{
__html: params.replace("<br/>", " / "),
}}
/>
<span className="text-gray-500">({count})</span>
</div>
))}
</div>
</div>

{/* Table */}
<div className="overflow-x-auto">
<div
className={`${shouldScroll ? "overflow-y-auto" : ""}`}
style={{ height: tableHeight }}
>
<table className="w-full text-sm min-w-full">
<thead className="sticky top-0 bg-gray-50 dark:bg-gray-800 z-30">
<tr>
<th className="text-left px-3 py-2 font-semibold text-gray-900 dark:text-gray-100 border-b border-gray-200 dark:border-gray-600 min-w-[100px]">
Name
</th>
<th className="text-left px-3 py-2 font-semibold text-gray-900 dark:text-gray-100 border-b border-gray-200 dark:border-gray-600 min-w-[400px]">
Price Feed Id
</th>
<th className="text-left px-3 py-2 font-semibold text-gray-900 dark:text-gray-100 border-b border-gray-200 dark:border-gray-600 min-w-[200px]">
Update Parameters
</th>
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-900">
{feeds.map((feed, index) => {
const isDefault = feed.updateParameters === defaultParams;
const prevFeed = feeds[index - 1];
const isFirstInGroup =
!prevFeed ||
prevFeed.updateParameters !== feed.updateParameters;

return (
<tr
key={feed.priceFeedId}
className={`border-b border-gray-100 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800/30 ${
isFirstInGroup
? "sticky top-12 bg-white dark:bg-gray-900 z-20 shadow-sm"
: ""
}`}
>
<td className="px-3 py-2 align-top">
<span className="font-medium text-gray-900 dark:text-gray-100">
{feed.name}
</span>
</td>
<td className="px-3 py-2 align-top">
<div className="flex items-start gap-2">
<code className="text-xs font-mono text-gray-600 dark:text-gray-400 flex-1 break-all leading-relaxed">
{feed.priceFeedId}
</code>
<button
onClick={() => copyToClipboard(feed.priceFeedId)}
className="p-1 hover:bg-gray-200 dark:hover:bg-gray-600 rounded flex-shrink-0 mt-0.5"
title="Copy Price Feed ID"
>
{copiedId === feed.priceFeedId ? (
<span className="text-green-500 text-xs font-bold">
</span>
) : (
<CopyIcon className="w-3 h-3 text-gray-400" />
)}
</button>
</div>
</td>
<td className="px-3 py-2 align-top">
{isFirstInGroup ? (
<div className="flex items-start gap-1.5">
<div
className={`w-1.5 h-1.5 rounded-full mt-1 flex-shrink-0 ${
isDefault ? "bg-green-500" : "bg-orange-500"
}`}
></div>
<span
className={`text-xs leading-relaxed font-medium ${
isDefault
? "text-gray-700 dark:text-gray-300"
: "text-orange-600 dark:text-orange-400"
}`}
dangerouslySetInnerHTML={{
__html: feed.updateParameters,
}}
/>
</div>
) : (
<div className="flex items-start gap-1.5 text-gray-400 text-xs">
<div className="w-1.5 h-1.5 bg-green-500 rounded-full mt-1 flex-shrink-0"></div>
<span>Same as above</span>
</div>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>

{/* Show count indicator when scrolling is needed */}
{shouldScroll && (
<div className="px-3 py-1 bg-gray-50 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-600 text-xs text-gray-500 text-center">
Showing {maxVisibleRows} of {feeds.length} feeds • Scroll to see
more
</div>
)}
</div>
</div>
);
};
Loading
Loading