Skip to content

Commit 69ef595

Browse files
authored
Merge pull request #2070 from jaimergp/latest-packages
2 parents 565c088 + 8d5de23 commit 69ef595

File tree

2 files changed

+196
-88
lines changed

2 files changed

+196
-88
lines changed

src/components/Packages/index.jsx

Lines changed: 195 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useState, useEffect } from "react";
2+
import Admonition from "@docusaurus/theme-classic/lib/theme/Admonition";
23

34
// Function to calculate the Levenshtein distance between two strings
45
function levenshteinDistance(a, b) {
@@ -49,137 +50,243 @@ function highlightSubstring(str, substr) {
4950
}
5051

5152
const Packages = () => {
52-
const [packages, setPackages] = useState([]);
53+
const [allPackages, setAllPackages] = useState({});
54+
const [latestPackages, setLatestPackages] = useState([]);
5355
const [searchTerm, setSearchTerm] = useState("");
5456

5557
useEffect(() => {
56-
const fetchData = async () => {
58+
const fetchAllData = async () => {
5759
try {
58-
const response = await fetch("https://raw.githubusercontent.com/conda-forge/feedstock-outputs/single-file/feedstock-outputs.json");
60+
const response = await fetch(
61+
"https://raw.githubusercontent.com/conda-forge/feedstock-outputs/single-file/feedstock-outputs.json"
62+
);
5963
const data = await response.json();
6064

6165
if (typeof data === "object" && data !== null) {
62-
// Convert the object into an array of { pkg_name, repositories } objects
63-
const packagesArray = Object.entries(data).map(([name, repos]) => ({
64-
name,
65-
repos,
66-
}));
67-
68-
setPackages(packagesArray);
66+
setAllPackages(
67+
Object.fromEntries(
68+
Object.entries(data).map(([key, value]) => [
69+
key.toLowerCase(),
70+
value,
71+
])
72+
)
73+
);
6974
} else {
7075
console.error("Invalid data format. Expected an object.");
7176
}
7277
} catch (error) {
7378
console.error("Error fetching packages:", error);
7479
}
7580
};
76-
77-
fetchData();
81+
const fetchLatestData = async () => {
82+
try {
83+
const response = await fetch(
84+
"https://conda.anaconda.org/conda-forge/rss.xml"
85+
);
86+
// parse the RSS feed into an XML document
87+
const xml = await response.text();
88+
const parser = new DOMParser();
89+
const doc = parser.parseFromString(xml, "text/xml");
90+
const titles = doc.querySelectorAll("title");
91+
const dates = doc.querySelectorAll("pubDate");
92+
// Convert the object into an array of { name, date } objects
93+
var latestPackagesArray = [];
94+
// The first 'title' element is the feed title, so we skip it
95+
titles.forEach(
96+
(title, index) =>
97+
index &&
98+
latestPackagesArray.push({
99+
name: title.textContent.split(" ")[0],
100+
date: dates[index - 1].textContent,
101+
})
102+
);
103+
setLatestPackages(latestPackagesArray);
104+
} catch (error) {
105+
console.error("Error fetching latest packages:", error);
106+
}
107+
};
108+
fetchLatestData();
109+
fetchAllData();
78110
}, []);
79111

80112
const searchTermLower = searchTerm.toLowerCase();
81113
var filteredPackages = [];
82-
if (searchTerm.length >= 3) {
83-
// For queries with three or more characters, search the entire string for a match
84-
filteredPackages = packages.filter((pkg) =>
85-
pkg.name.toLowerCase().includes(searchTermLower)
86-
);
87-
} else if (searchTerm.length > 0) {
88-
// For queries with less than three characters,
89-
// only search if the package name starts with the query for performance reasons
90-
filteredPackages = packages.filter((pkg) =>
91-
pkg.name.toLowerCase().startsWith(searchTermLower)
92-
);
114+
var inclusionCriteria;
115+
if (searchTerm.length > 0) {
116+
if (searchTerm.length >= 3) {
117+
inclusionCriteria = (name) => name.includes(searchTermLower);
118+
} else {
119+
inclusionCriteria = (name) => name.startsWith(searchTermLower);
120+
}
121+
for (const name in allPackages) {
122+
if (inclusionCriteria(name)) {
123+
filteredPackages.push(name);
124+
}
125+
}
93126
}
127+
94128
// Sort the filtered packages in place by their Levenshtein distance
95129
filteredPackages.sort((a, b) => {
96-
const aDistance = levenshteinDistance(
97-
a.name.toLowerCase(),
98-
searchTermLower
99-
);
100-
const bDistance = levenshteinDistance(
101-
b.name.toLowerCase(),
102-
searchTermLower
103-
);
130+
const aDistance = levenshteinDistance(a, searchTermLower);
131+
const bDistance = levenshteinDistance(b, searchTermLower);
104132
return aDistance - bDistance;
105133
});
106134

107135
const handleSearchChange = (event) => {
108136
setSearchTerm(event.target.value);
109137
};
110138

139+
var renderResultsBlock;
140+
var resultsPill;
141+
if (searchTerm.length) {
142+
// This is the results table, displayed when the user enters a search term
143+
renderResultsBlock = (
144+
<table>
145+
<thead>
146+
<tr>
147+
<th>Package</th>
148+
<th>Feedstock(s)</th>
149+
</tr>
150+
</thead>
151+
<tbody>
152+
{(filteredPackages.length &&
153+
filteredPackages.map((pkg) => (
154+
<tr key={pkg}>
155+
<td>
156+
<a
157+
href={`https://anaconda.org/conda-forge/${pkg}`}
158+
target="_blank"
159+
title={`View ${pkg} on anaconda.org`}
160+
>
161+
{highlightSubstring(pkg, searchTermLower)}
162+
</a>
163+
</td>
164+
<td>
165+
{allPackages[pkg].map((repo) => (
166+
<span key={`${pkg}-${repo}`}>
167+
<a
168+
href={`https://github.com/conda-forge/${repo}-feedstock`}
169+
target="_blank"
170+
title={`View ${repo}-feedstock on GitHub`}
171+
>
172+
{repo}-feedstock
173+
</a>
174+
175+
<br />
176+
</span>
177+
))}
178+
</td>
179+
</tr>
180+
))) || (
181+
<tr>
182+
<td colSpan="2">No packages found</td>
183+
</tr>
184+
)}
185+
</tbody>
186+
</table>
187+
);
188+
resultsPill = (
189+
<span className="badge badge--secondary margin-left--sm">
190+
{filteredPackages.length} package(s) found
191+
</span>
192+
);
193+
} else {
194+
// Without a search term, display the most recently updated feedstocks
195+
renderResultsBlock = (
196+
<div>
197+
<Admonition type="tip" coll>
198+
<p>
199+
The following packages have recently received updates in{" "}
200+
<a
201+
href="https://anaconda.org/conda-forge"
202+
target="_blank"
203+
rel="noopener noreferrer"
204+
>
205+
Anaconda.org
206+
</a>
207+
. Check{" "}
208+
<a
209+
href="https://github.com/conda-forge/feedstocks/commits"
210+
target="_blank"
211+
rel="noopener noreferrer"
212+
>
213+
conda-forge/feedstocks
214+
</a>{" "}
215+
for an overview of the latest commits in our feedstocks.
216+
</p>
217+
</Admonition>
218+
<table>
219+
<thead>
220+
<tr>
221+
<th>#</th>
222+
<th>Package</th>
223+
<th>Feedstock(s)</th>
224+
<th>Last updated</th>
225+
</tr>
226+
</thead>
227+
<tbody>
228+
{latestPackages.map((item, index) => (
229+
<tr key={item.name}>
230+
<td>{index + 1}</td>
231+
<td>
232+
<a
233+
href={`https://anaconda.org/conda-forge/${item.name}`}
234+
target="_blank"
235+
rel="noopener noreferrer"
236+
>
237+
{item.name}
238+
</a>
239+
</td>
240+
<td>
241+
{(allPackages[item.name.toLowerCase()] || []).map((repo) => (
242+
<span key={`${item.name}-${index}-${repo}`}>
243+
<a
244+
href={`https://github.com/conda-forge/${repo}-feedstock`}
245+
target="_blank"
246+
rel="noopener noreferrer"
247+
title={`View ${repo}-feedstock on GitHub`}
248+
>
249+
{repo}-feedstock
250+
</a>
251+
<br />
252+
</span>
253+
))}
254+
</td>
255+
<td>{item.date}</td>
256+
</tr>
257+
))}
258+
</tbody>
259+
</table>
260+
</div>
261+
);
262+
resultsPill = (
263+
<span className="badge badge--secondary margin-left--sm">
264+
{Object.keys(allPackages).length} packages loaded
265+
</span>
266+
);
267+
}
268+
111269
return (
112-
<div
113-
className={["container", "margin-vert--lg"].join(" ")}
114-
>
270+
<div className={["container", "margin-vert--lg"].join(" ")}>
115271
<div className="row">
116272
<main className="col col--12">
117273
<h1>Packages in conda-forge</h1>
118-
<form className="margin-vert--md">
274+
<form id="filterPackages" className="margin-vert--md">
119275
<div className="navbar__search">
120-
<label htmlFor="search">
276+
<label htmlFor="filterPackagesInput">
121277
<input
278+
id="filterPackagesInput"
122279
type="text"
123280
placeholder="Filter items..."
124281
value={searchTerm}
125282
onChange={handleSearchChange}
126283
className="navbar__search-input"
127284
/>
128-
{(searchTerm.length && (
129-
<span class="badge badge--info margin-left--sm">
130-
{filteredPackages.length} package(s) found
131-
</span>
132-
)) || (
133-
<span class="badge badge--success margin-left--sm">
134-
{packages.length} packages loaded
135-
</span>
136-
)}
285+
{resultsPill}
137286
</label>
138287
</div>
139288
</form>
140-
<table>
141-
<thead>
142-
<tr>
143-
<th>Package</th>
144-
<th>Feedstock(s)</th>
145-
</tr>
146-
</thead>
147-
<tbody>
148-
{(filteredPackages.length &&
149-
filteredPackages.map((pkg) => (
150-
<tr key={pkg.name}>
151-
<td>
152-
<a
153-
href={`https://anaconda.org/conda-forge/${pkg.name}`}
154-
target="_blank"
155-
title={`View ${pkg.name} on anaconda.org`}
156-
>
157-
{highlightSubstring(pkg.name, searchTermLower)}
158-
</a>
159-
</td>
160-
<td>
161-
{pkg.repos.map((repo) => (
162-
<span>
163-
<a
164-
href={`https://github.com/conda-forge/${repo}-feedstock`}
165-
target="_blank"
166-
title={`View ${repo}-feedstock on GitHub`}
167-
>
168-
{repo}-feedstock
169-
</a>
170-
171-
<br />
172-
</span>
173-
))}
174-
</td>
175-
</tr>
176-
))) || (
177-
<tr>
178-
<td colSpan="2">Use the search bar to find packages</td>
179-
</tr>
180-
)}
181-
</tbody>
182-
</table>
289+
{renderResultsBlock}
183290
</main>
184291
</div>
185292
</div>

src/css/custom.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
--ifm-color-info: #64b5f6;
6464
--ifm-color-warning: #ffb74d;
6565
--ifm-color-danger: #ff8a65;
66+
--ifm-badge-color: var(--ifm-color-black);
6667
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
6768
--gradient: linear-gradient(
6869
60deg,

0 commit comments

Comments
 (0)