Skip to content

Commit d982960

Browse files
committed
refactor: move trends suggestion composition
1 parent 5cf0d15 commit d982960

3 files changed

Lines changed: 92 additions & 4 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { PackageSuggestion } from '../client/api'
2+
import { rankSuggestionsByQuery } from '../pages/trends/trendsAutocomplete'
3+
4+
const suggestion = (name: string, searchScore: number): PackageSuggestion => ({
5+
package: { name, description: '' },
6+
searchScore,
7+
score: { detail: { popularity: searchScore } },
8+
})
9+
10+
it('ranks matching suggestions by query before search score', () => {
11+
expect(
12+
rankSuggestionsByQuery(
13+
[
14+
suggestion('preact', 100),
15+
suggestion('react-dom', 75),
16+
suggestion('React', 100),
17+
suggestion('react', 20),
18+
suggestion('not-a-match', 500),
19+
],
20+
'react'
21+
).map(item => item.package.name)
22+
).toEqual(['React', 'react-dom', 'preact'])
23+
})

pages/trends/index.page.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import GithubIcon from '../../client/assets/github-logo.svg'
2121
import NPMIcon from '../../client/assets/npm-logo.svg'
2222
import { getTrendsRecommendations } from '../../utils/trendsRecommendations'
2323
import TrendsChart, { TRENDS_SERIES_COLORS } from './TrendsChart'
24+
import { loadRelatedPackageSuggestions } from './trendsAutocomplete'
2425

2526
const DEFAULT_PACKAGES = ['react', 'vue']
2627
const MAX_PACKAGES = 5
@@ -134,7 +135,7 @@ export default function TrendsPage() {
134135
const [error, setError] = useState<string | null>(null)
135136
const [trendsData, setTrendsData] = useState<TrendsResponse | null>(null)
136137
const [copied, setCopied] = useState(false)
137-
const [suggestionQueries, setSuggestionQueries] = useState<string[]>([])
138+
const [relatedPackageNames, setRelatedPackageNames] = useState<string[]>([])
138139
const [suggestedPackages, setSuggestedPackages] = useState<string[]>([])
139140
const trendsCache = useRef(new Map<string, TrendsResponse>())
140141
const packageTrendsCache = useRef(
@@ -201,7 +202,7 @@ export default function TrendsPage() {
201202
similarResults: results,
202203
}
203204
)
204-
setSuggestionQueries(autocompleteQueries)
205+
setRelatedPackageNames(autocompleteQueries)
205206
setSuggestedPackages(recommendations)
206207
})
207208

@@ -210,6 +211,12 @@ export default function TrendsPage() {
210211
}
211212
}, [packages])
212213

214+
const loadSuggestions = useCallback(
215+
(query: string) =>
216+
loadRelatedPackageSuggestions(query, relatedPackageNames),
217+
[relatedPackageNames]
218+
)
219+
213220
// Update URL parameters without full page reload
214221
const updateUrl = useCallback(
215222
(
@@ -456,8 +463,7 @@ export default function TrendsPage() {
456463
key={inputKey}
457464
containerClass="trends-autocomplete"
458465
compact
459-
suggestionQueries={suggestionQueries}
460-
rankSuggestions
466+
loadSuggestions={loadSuggestions}
461467
onSearchSubmit={handleAddPackage}
462468
/>
463469
</div>

pages/trends/trendsAutocomplete.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import API, {
2+
sortSuggestionsBySearchScore,
3+
type PackageSuggestion,
4+
} from '../../client/api'
5+
6+
const MAX_RELATED_PACKAGES = 5
7+
8+
export function rankSuggestionsByQuery(
9+
suggestions: PackageSuggestion[],
10+
query: string
11+
) {
12+
const normalizedQuery = query.toLowerCase()
13+
const uniqueSuggestions = new Map<string, PackageSuggestion>()
14+
15+
suggestions.forEach(suggestion => {
16+
const packageName = suggestion.package.name.toLowerCase()
17+
if (!uniqueSuggestions.has(packageName)) {
18+
uniqueSuggestions.set(packageName, suggestion)
19+
}
20+
})
21+
22+
return Array.from(uniqueSuggestions.values())
23+
.filter(suggestion =>
24+
suggestion.package.name.toLowerCase().includes(normalizedQuery)
25+
)
26+
.sort((a, b) => {
27+
const aStartsWithQuery = a.package.name
28+
.toLowerCase()
29+
.startsWith(normalizedQuery)
30+
const bStartsWithQuery = b.package.name
31+
.toLowerCase()
32+
.startsWith(normalizedQuery)
33+
34+
if (aStartsWithQuery !== bStartsWithQuery) {
35+
return aStartsWithQuery ? -1 : 1
36+
}
37+
38+
return sortSuggestionsBySearchScore(a, b)
39+
})
40+
}
41+
42+
export async function loadRelatedPackageSuggestions(
43+
query: string,
44+
relatedPackageNames: string[]
45+
) {
46+
const queries = [query, ...relatedPackageNames]
47+
.filter(Boolean)
48+
.slice(0, MAX_RELATED_PACKAGES + 1)
49+
const results = await Promise.allSettled(
50+
queries.map(packageName => API.getSuggestions(packageName))
51+
)
52+
53+
return rankSuggestionsByQuery(
54+
results.flatMap(result =>
55+
result.status === 'fulfilled' ? result.value : []
56+
),
57+
query
58+
)
59+
}

0 commit comments

Comments
 (0)