Skip to content

Commit

Permalink
Merge pull request #2070 from jaimergp/latest-packages
Browse files Browse the repository at this point in the history
  • Loading branch information
beckermr authored Feb 7, 2024
2 parents 565c088 + 8d5de23 commit 69ef595
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 88 deletions.
283 changes: 195 additions & 88 deletions src/components/Packages/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from "react";
import Admonition from "@docusaurus/theme-classic/lib/theme/Admonition";

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

const Packages = () => {
const [packages, setPackages] = useState([]);
const [allPackages, setAllPackages] = useState({});
const [latestPackages, setLatestPackages] = useState([]);
const [searchTerm, setSearchTerm] = useState("");

useEffect(() => {
const fetchData = async () => {
const fetchAllData = async () => {
try {
const response = await fetch("https://raw.githubusercontent.com/conda-forge/feedstock-outputs/single-file/feedstock-outputs.json");
const response = await fetch(
"https://raw.githubusercontent.com/conda-forge/feedstock-outputs/single-file/feedstock-outputs.json"
);
const data = await response.json();

if (typeof data === "object" && data !== null) {
// Convert the object into an array of { pkg_name, repositories } objects
const packagesArray = Object.entries(data).map(([name, repos]) => ({
name,
repos,
}));

setPackages(packagesArray);
setAllPackages(
Object.fromEntries(
Object.entries(data).map(([key, value]) => [
key.toLowerCase(),
value,
])
)
);
} else {
console.error("Invalid data format. Expected an object.");
}
} catch (error) {
console.error("Error fetching packages:", error);
}
};

fetchData();
const fetchLatestData = async () => {
try {
const response = await fetch(
"https://conda.anaconda.org/conda-forge/rss.xml"
);
// parse the RSS feed into an XML document
const xml = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(xml, "text/xml");
const titles = doc.querySelectorAll("title");
const dates = doc.querySelectorAll("pubDate");
// Convert the object into an array of { name, date } objects
var latestPackagesArray = [];
// The first 'title' element is the feed title, so we skip it
titles.forEach(
(title, index) =>
index &&
latestPackagesArray.push({
name: title.textContent.split(" ")[0],
date: dates[index - 1].textContent,
})
);
setLatestPackages(latestPackagesArray);
} catch (error) {
console.error("Error fetching latest packages:", error);
}
};
fetchLatestData();
fetchAllData();
}, []);

const searchTermLower = searchTerm.toLowerCase();
var filteredPackages = [];
if (searchTerm.length >= 3) {
// For queries with three or more characters, search the entire string for a match
filteredPackages = packages.filter((pkg) =>
pkg.name.toLowerCase().includes(searchTermLower)
);
} else if (searchTerm.length > 0) {
// For queries with less than three characters,
// only search if the package name starts with the query for performance reasons
filteredPackages = packages.filter((pkg) =>
pkg.name.toLowerCase().startsWith(searchTermLower)
);
var inclusionCriteria;
if (searchTerm.length > 0) {
if (searchTerm.length >= 3) {
inclusionCriteria = (name) => name.includes(searchTermLower);
} else {
inclusionCriteria = (name) => name.startsWith(searchTermLower);
}
for (const name in allPackages) {
if (inclusionCriteria(name)) {
filteredPackages.push(name);
}
}
}

// Sort the filtered packages in place by their Levenshtein distance
filteredPackages.sort((a, b) => {
const aDistance = levenshteinDistance(
a.name.toLowerCase(),
searchTermLower
);
const bDistance = levenshteinDistance(
b.name.toLowerCase(),
searchTermLower
);
const aDistance = levenshteinDistance(a, searchTermLower);
const bDistance = levenshteinDistance(b, searchTermLower);
return aDistance - bDistance;
});

const handleSearchChange = (event) => {
setSearchTerm(event.target.value);
};

var renderResultsBlock;
var resultsPill;
if (searchTerm.length) {
// This is the results table, displayed when the user enters a search term
renderResultsBlock = (
<table>
<thead>
<tr>
<th>Package</th>
<th>Feedstock(s)</th>
</tr>
</thead>
<tbody>
{(filteredPackages.length &&
filteredPackages.map((pkg) => (
<tr key={pkg}>
<td>
<a
href={`https://anaconda.org/conda-forge/${pkg}`}
target="_blank"
title={`View ${pkg} on anaconda.org`}
>
{highlightSubstring(pkg, searchTermLower)}
</a>
</td>
<td>
{allPackages[pkg].map((repo) => (
<span key={`${pkg}-${repo}`}>
<a
href={`https://github.com/conda-forge/${repo}-feedstock`}
target="_blank"
title={`View ${repo}-feedstock on GitHub`}
>
{repo}-feedstock
</a>

<br />
</span>
))}
</td>
</tr>
))) || (
<tr>
<td colSpan="2">No packages found</td>
</tr>
)}
</tbody>
</table>
);
resultsPill = (
<span className="badge badge--secondary margin-left--sm">
{filteredPackages.length} package(s) found
</span>
);
} else {
// Without a search term, display the most recently updated feedstocks
renderResultsBlock = (
<div>
<Admonition type="tip" coll>
<p>
The following packages have recently received updates in{" "}
<a
href="https://anaconda.org/conda-forge"
target="_blank"
rel="noopener noreferrer"
>
Anaconda.org
</a>
. Check{" "}
<a
href="https://github.com/conda-forge/feedstocks/commits"
target="_blank"
rel="noopener noreferrer"
>
conda-forge/feedstocks
</a>{" "}
for an overview of the latest commits in our feedstocks.
</p>
</Admonition>
<table>
<thead>
<tr>
<th>#</th>
<th>Package</th>
<th>Feedstock(s)</th>
<th>Last updated</th>
</tr>
</thead>
<tbody>
{latestPackages.map((item, index) => (
<tr key={item.name}>
<td>{index + 1}</td>
<td>
<a
href={`https://anaconda.org/conda-forge/${item.name}`}
target="_blank"
rel="noopener noreferrer"
>
{item.name}
</a>
</td>
<td>
{(allPackages[item.name.toLowerCase()] || []).map((repo) => (
<span key={`${item.name}-${index}-${repo}`}>
<a
href={`https://github.com/conda-forge/${repo}-feedstock`}
target="_blank"
rel="noopener noreferrer"
title={`View ${repo}-feedstock on GitHub`}
>
{repo}-feedstock
</a>
<br />
</span>
))}
</td>
<td>{item.date}</td>
</tr>
))}
</tbody>
</table>
</div>
);
resultsPill = (
<span className="badge badge--secondary margin-left--sm">
{Object.keys(allPackages).length} packages loaded
</span>
);
}

return (
<div
className={["container", "margin-vert--lg"].join(" ")}
>
<div className={["container", "margin-vert--lg"].join(" ")}>
<div className="row">
<main className="col col--12">
<h1>Packages in conda-forge</h1>
<form className="margin-vert--md">
<form id="filterPackages" className="margin-vert--md">
<div className="navbar__search">
<label htmlFor="search">
<label htmlFor="filterPackagesInput">
<input
id="filterPackagesInput"
type="text"
placeholder="Filter items..."
value={searchTerm}
onChange={handleSearchChange}
className="navbar__search-input"
/>
{(searchTerm.length && (
<span class="badge badge--info margin-left--sm">
{filteredPackages.length} package(s) found
</span>
)) || (
<span class="badge badge--success margin-left--sm">
{packages.length} packages loaded
</span>
)}
{resultsPill}
</label>
</div>
</form>
<table>
<thead>
<tr>
<th>Package</th>
<th>Feedstock(s)</th>
</tr>
</thead>
<tbody>
{(filteredPackages.length &&
filteredPackages.map((pkg) => (
<tr key={pkg.name}>
<td>
<a
href={`https://anaconda.org/conda-forge/${pkg.name}`}
target="_blank"
title={`View ${pkg.name} on anaconda.org`}
>
{highlightSubstring(pkg.name, searchTermLower)}
</a>
</td>
<td>
{pkg.repos.map((repo) => (
<span>
<a
href={`https://github.com/conda-forge/${repo}-feedstock`}
target="_blank"
title={`View ${repo}-feedstock on GitHub`}
>
{repo}-feedstock
</a>

<br />
</span>
))}
</td>
</tr>
))) || (
<tr>
<td colSpan="2">Use the search bar to find packages</td>
</tr>
)}
</tbody>
</table>
{renderResultsBlock}
</main>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
--ifm-color-info: #64b5f6;
--ifm-color-warning: #ffb74d;
--ifm-color-danger: #ff8a65;
--ifm-badge-color: var(--ifm-color-black);
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
--gradient: linear-gradient(
60deg,
Expand Down

0 comments on commit 69ef595

Please sign in to comment.