Skip to content

Commit 81362db

Browse files
authored
Merge pull request #3011 from pyth-network/fix/search-ih
feat(IH): improve search
2 parents b7ed7fb + 01fb78d commit 81362db

File tree

6 files changed

+148
-312
lines changed

6 files changed

+148
-312
lines changed

apps/insights/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"dnum": "catalog:",
3838
"ioredis": "^5.7.0",
3939
"lightweight-charts": "catalog:",
40+
"match-sorter": "catalog:",
4041
"motion": "catalog:",
4142
"next": "catalog:",
4243
"next-themes": "catalog:",

apps/insights/src/components/PriceFeed/price-feed-select.tsx

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import { SearchField } from "@pythnetwork/component-library/unstyled/SearchField
1616
import { Select } from "@pythnetwork/component-library/unstyled/Select";
1717
import { Input } from "@pythnetwork/component-library/unstyled/TextField";
1818
import clsx from "clsx";
19+
import { matchSorter } from "match-sorter";
1920
import type { ReactNode } from "react";
2021
import { useMemo, useState } from "react";
21-
import { useCollator, useFilter } from "react-aria";
2222

2323
import styles from "./price-feed-select.module.scss";
2424
import { AssetClassBadge } from "../AssetClassBadge";
@@ -56,7 +56,7 @@ type ResolvedPriceFeedSelect = {
5656
symbol: string;
5757
displaySymbol: string;
5858
assetClass: string;
59-
key: string;
59+
key: string; // price_account
6060
description: string;
6161
icon: ReactNode;
6262
}[];
@@ -66,29 +66,14 @@ const ResolvedPriceFeedSelect = ({
6666
feeds,
6767
...props
6868
}: ResolvedPriceFeedSelect) => {
69-
const collator = useCollator();
70-
const filter = useFilter({ sensitivity: "base", usage: "search" });
7169
const [search, setSearch] = useState("");
72-
const filteredFeeds = useMemo(
70+
const filteredAndSortedFeeds = useMemo(
7371
() =>
74-
search === ""
75-
? feeds
76-
: feeds.filter(
77-
({ displaySymbol, assetClass, key }) =>
78-
filter.contains(displaySymbol, search) ||
79-
filter.contains(assetClass, search) ||
80-
filter.contains(key, search),
81-
),
82-
[feeds, search, filter],
72+
matchSorter(feeds, search, {
73+
keys: ["displaySymbol", "symbol", "description", "key"],
74+
}),
75+
[feeds, search],
8376
);
84-
const sortedFeeds = useMemo(
85-
() =>
86-
filteredFeeds.toSorted((a, b) =>
87-
collator.compare(a.displaySymbol, b.displaySymbol),
88-
),
89-
[filteredFeeds, collator],
90-
);
91-
9277
return (
9378
<PriceFeedSelectImpl
9479
menu={
@@ -109,7 +94,7 @@ const ResolvedPriceFeedSelect = ({
10994
</SearchField>
11095
<Virtualizer layout={new ListLayout()}>
11196
<ListBox
112-
items={sortedFeeds}
97+
items={filteredAndSortedFeeds}
11398
className={styles.listbox ?? ""}
11499
// eslint-disable-next-line jsx-a11y/no-autofocus
115100
autoFocus={false}
@@ -120,7 +105,9 @@ const ResolvedPriceFeedSelect = ({
120105
className={styles.priceFeed ?? ""}
121106
href={`/price-feeds/${encodeURIComponent(symbol)}`}
122107
data-is-first={
123-
symbol === sortedFeeds[0]?.symbol ? "" : undefined
108+
symbol === filteredAndSortedFeeds[0]?.symbol
109+
? ""
110+
: undefined
124111
}
125112
prefetch={false}
126113
>

apps/insights/src/components/Root/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ const getPublishersForSearchDialog = async (cluster: Cluster) => {
7171

7272
const getFeedsForSearchDialog = async (cluster: Cluster) => {
7373
const feeds = await getFeeds(cluster);
74-
7574
return feeds.map((feed) => ({
7675
symbol: feed.symbol,
7776
displaySymbol: feed.product.display_symbol,
7877
assetClass: feed.product.asset_type,
7978
description: feed.product.description,
79+
priceAccount: feed.product.price_account,
8080
icon: (
8181
<PriceFeedIcon
8282
assetClass={feed.product.asset_type}

apps/insights/src/components/Root/search-button.tsx

Lines changed: 32 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import { SearchInput } from "@pythnetwork/component-library/SearchInput";
1010
import { SingleToggleGroup } from "@pythnetwork/component-library/SingleToggleGroup";
1111
import { Skeleton } from "@pythnetwork/component-library/Skeleton";
1212
import {
13-
Virtualizer,
1413
ListLayout,
14+
Virtualizer,
1515
} from "@pythnetwork/component-library/Virtualizer";
1616
import type { Button as UnstyledButton } from "@pythnetwork/component-library/unstyled/Button";
1717
import {
@@ -20,16 +20,17 @@ import {
2020
} from "@pythnetwork/component-library/unstyled/ListBox";
2121
import { useDrawer } from "@pythnetwork/component-library/useDrawer";
2222
import { useLogger } from "@pythnetwork/component-library/useLogger";
23+
import { matchSorter } from "match-sorter";
2324
import type { ReactNode } from "react";
24-
import { useMemo, useCallback, useEffect, useState } from "react";
25-
import { useIsSSR, useCollator, useFilter } from "react-aria";
25+
import { useCallback, useEffect, useMemo, useState } from "react";
26+
import { useIsSSR } from "react-aria";
2627

27-
import styles from "./search-button.module.scss";
2828
import { Cluster, ClusterToName } from "../../services/pyth";
2929
import { AssetClassBadge } from "../AssetClassBadge";
3030
import { PriceFeedTag } from "../PriceFeedTag";
3131
import { PublisherTag } from "../PublisherTag";
3232
import { Score } from "../Score";
33+
import styles from "./search-button.module.scss";
3334

3435
const INPUTS = new Set(["input", "select", "button", "textarea"]);
3536

@@ -50,6 +51,7 @@ type ResolvedSearchButtonProps = {
5051
displaySymbol: string;
5152
assetClass: string;
5253
description: string;
54+
priceAccount: string;
5355
icon: ReactNode;
5456
}[];
5557
publishers: ({
@@ -170,58 +172,38 @@ const SearchDialogContents = ({
170172
const logger = useLogger();
171173
const [search, setSearch] = useState("");
172174
const [type, setType] = useState<ResultType | "">("");
173-
const collator = useCollator();
174-
const filter = useFilter({ sensitivity: "base", usage: "search" });
175175
const closeDrawer = useCallback(() => {
176176
drawer.close().catch((error: unknown) => {
177177
logger.error(error);
178178
});
179179
}, [drawer, logger]);
180-
const results = useMemo(
181-
() =>
182-
[
183-
...(type === ResultType.Publisher
184-
? []
185-
: // This is inefficient but Safari doesn't support `Iterator.filter`,
186-
// see https://bugs.webkit.org/show_bug.cgi?id=248650
187-
[...feeds.entries()]
188-
.filter(([, { displaySymbol }]) =>
189-
filter.contains(displaySymbol, search),
190-
)
191-
.map(([symbol, feed]) => ({
192-
type: ResultType.PriceFeed as const,
193-
id: symbol,
194-
...feed,
195-
}))),
196-
...(type === ResultType.PriceFeed
197-
? []
198-
: publishers
199-
.filter(
200-
(publisher) =>
201-
filter.contains(publisher.publisherKey, search) ||
202-
(publisher.name && filter.contains(publisher.name, search)),
203-
)
204-
.map((publisher) => ({
205-
type: ResultType.Publisher as const,
206-
id: [
207-
ClusterToName[publisher.cluster],
208-
publisher.publisherKey,
209-
].join(":"),
210-
...publisher,
211-
}))),
212-
].sort((a, b) =>
213-
collator.compare(
214-
a.type === ResultType.PriceFeed
215-
? a.displaySymbol
216-
: (a.name ?? a.publisherKey),
217-
b.type === ResultType.PriceFeed
218-
? b.displaySymbol
219-
: (b.name ?? b.publisherKey),
220-
),
221-
),
222-
[feeds, publishers, collator, filter, search, type],
223-
);
224180

181+
const results = useMemo(() => {
182+
const filteredFeeds = matchSorter(feeds, search, {
183+
keys: ["displaySymbol", "symbol", "description", "priceAccount"],
184+
}).map(({ symbol, ...feed }) => ({
185+
type: ResultType.PriceFeed as const,
186+
id: symbol,
187+
symbol,
188+
...feed,
189+
}));
190+
191+
const filteredPublishers = matchSorter(publishers, search, {
192+
keys: ["publisherKey", "name"],
193+
}).map((publisher) => ({
194+
type: ResultType.Publisher as const,
195+
id: [ClusterToName[publisher.cluster], publisher.publisherKey].join(":"),
196+
...publisher,
197+
}));
198+
199+
if (type === ResultType.PriceFeed) {
200+
return filteredFeeds;
201+
}
202+
if (type === ResultType.Publisher) {
203+
return filteredPublishers;
204+
}
205+
return [...filteredFeeds, ...filteredPublishers];
206+
}, [feeds, publishers, search, type]);
225207
return (
226208
<div className={styles.searchDialogContents}>
227209
<div className={styles.searchBar}>

0 commit comments

Comments
 (0)