You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by ar...@apache.org on 2026/10/07 09:41:50 UTC

(tooling-trusted-releases) 01/02: #1717 - show matching projects on search

This is an automated email from the ASF dual-hosted git repository.

alitheg pushed a commit to branch arm
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git

commit d1c9699c580cd954e56564e9f2546be3a3f0995d
Author: Alastair McFarlane <ar...@apache.org>
AuthorDate: Wed Oct 7 09:57:43 2026 +0100

    #1717 - show matching projects on search
---
 atr/catalog_site.py                   | 38 ++++++++++++++++++++++++++++-------
 atr/static/js/src/card-grid.js        | 24 ++++++++++++++++++++--
 atr/templates/catalog_site/index.html | 17 ++++++++++++----
 3 files changed, 66 insertions(+), 13 deletions(-)

diff --git a/atr/catalog_site.py b/atr/catalog_site.py
index 474bd91d..a3a3b793 100644
--- a/atr/catalog_site.py
+++ b/atr/catalog_site.py
@@ -174,11 +174,19 @@ _PROJECT_RELEASES_FILE: Final = "project_releases.json"
 _PODLING_RELEASES_FILE: Final = "podling_releases.json"
 
 
+# Ordered on the name first, so a card's projects sort the way they read
+@dataclasses.dataclass(order=True)
+class _ProjectLink:
+    name: str
+    # Relative to the site root
+    target: str
+
+
 @dataclasses.dataclass
 class _CommitteeSummary:
     release_count: int
     latest_date: datetime.datetime | None
-    project_names: list[str]
+    projects: list[_ProjectLink]
 
 
 @dataclasses.dataclass
@@ -436,18 +444,27 @@ async def _committee_summaries(data: db.Session, committees: Sequence[sql.Commit
         if (released is not None) and ((key not in latest) or (released > latest[key])):
             latest[key] = released
     # The search matches project names as well as committee names, so each card lists its
-    # projects. The Incubator lists its podlings, which are committees rather than projects.
-    names: dict[str, list[str]] = {
-        committee.key: sorted(project.display_name for project in committee.projects) for committee in committees
+    # projects, ready to link straight to whichever ones a search hits. The Incubator lists
+    # its podlings, which are committees rather than projects.
+    links: dict[str, list[_ProjectLink]] = {
+        committee.key: sorted(
+            _ProjectLink(name=project.display_name, target=f"{project.key}/index.html")
+            for project in committee.projects
+        )
+        for committee in committees
     }
-    names[_INCUBATOR_COMMITTEE_KEY] = sorted(committee.display_name for committee in committees if committee.is_podling)
+    links[_INCUBATOR_COMMITTEE_KEY] = sorted(
+        _ProjectLink(name=committee.display_name, target=_podling_target(committee))
+        for committee in committees
+        if committee.is_podling
+    )
     return {
         key: _CommitteeSummary(
             release_count=counts.get(key, 0),
             latest_date=latest.get(key),
-            project_names=names.get(key, []),
+            projects=links.get(key, []),
         )
-        for key in (set(counts) | set(names))
+        for key in (set(counts) | set(links))
     }
 
 
@@ -538,6 +555,13 @@ def _minimal_combo(classifiers: dict[str, str], others: list[dict[str, str]]) ->
     return None
 
 
+def _podling_target(podling: sql.Committee) -> str:
+    # Mirrors the Incubator page: a podling with a single project opens that project
+    if len(podling.projects) == 1:
+        return f"{podling.projects[0].key}/index.html"
+    return f"{podling.key}/index.html"
+
+
 def _project_htaccess(project_key: str, versions: Sequence[api.CatalogVersion]) -> str | None:
     """Render a project's `.htaccess`, resolving `<project>@latest`, or None if there's no release.
 
diff --git a/atr/static/js/src/card-grid.js b/atr/static/js/src/card-grid.js
index ccd62f35..789c588f 100644
--- a/atr/static/js/src/card-grid.js
+++ b/atr/static/js/src/card-grid.js
@@ -73,9 +73,13 @@ function setupProjectToggleButtons() {
 	}
 }
 
-function cardMatchesText(card, query) {
+function titleMatchesText(card, query) {
 	const title = card.querySelector(".card-title");
-	if (title?.textContent.trim().toLowerCase().includes(query)) {
+	return title?.textContent.trim().toLowerCase().includes(query) ?? false;
+}
+
+function cardMatchesText(card, query) {
+	if (titleMatchesText(card, query)) {
 		return true;
 	}
 	// Catalogue cards bundle projects, so match their names too.
@@ -87,6 +91,18 @@ function cardMatchesText(card, query) {
 	return false;
 }
 
+function showProjectMatches(card, query) {
+	// A search that only hits a bundled project shows that project's own link,
+	// so it can be opened directly. One that hits the card's own title shows
+	// none, or searching "commons" would list every Commons project.
+	const wanted = query && !titleMatchesText(card, query);
+	for (const match of card.querySelectorAll(".page-project-match")) {
+		const hit =
+			wanted && match.dataset.projectName.toLowerCase().includes(query);
+		match.classList.toggle("d-none", !hit);
+	}
+}
+
 function updateUrl(mineOnly) {
 	// Write the view explicitly, so a reload can tell "chose all" apart from
 	// "fresh visit, use default".
@@ -153,6 +169,9 @@ function setParticipantMode(ctx, mineOnly, keepText) {
 	}
 	if (!keepText && ctx.filterInput) {
 		ctx.filterInput.value = "";
+		for (const card of ctx.cards) {
+			showProjectMatches(card, "");
+		}
 	}
 	setCount(ctx, visible);
 }
@@ -168,6 +187,7 @@ function filterByText(ctx) {
 		const show =
 			cardEligible(ctx, card) && (!query || cardMatchesText(card, query));
 		card.parentElement.hidden = !show;
+		showProjectMatches(card, query);
 		if (show) {
 			visible++;
 		}
diff --git a/atr/templates/catalog_site/index.html b/atr/templates/catalog_site/index.html
index 4b254aa7..43b16b5b 100644
--- a/atr/templates/catalog_site/index.html
+++ b/atr/templates/catalog_site/index.html
@@ -45,10 +45,19 @@
                       {{ summary.release_count }} {{ "release" if summary.release_count == 1 else "releases" }}
                       {%- if summary.latest_date %} &middot; latest {{ summary.latest_date.strftime("%Y-%m-%d") }}{% endif %}
                     </p>
-                    {# The projects ride along hidden so a search matches on their names too #}
-                    {% for name in summary.project_names %}
-                      <span class="d-none" data-project-name="{{ name }}"></span>
-                    {% endfor %}
+                    {# The projects ride along hidden so a search matches on their names too. A
+                       project the search hits shows its own link, sitting above the card's stretched
+                       link, so it opens the project rather than its whole committee #}
+                    {% if summary.projects %}
+                      <ul class="list-unstyled small mb-0">
+                        {% for project in summary.projects %}
+                          <li class="mt-1 d-none page-project-match"
+                              data-project-name="{{ project.name }}">
+                            <a href="{{ root }}{{ project.target }}" class="position-relative z-2">{{ project.name }} &rarr;</a>
+                          </li>
+                        {% endfor %}
+                      </ul>
+                    {% endif %}
                   {% endif %}
                 </div>
                 <div class="col-5 col-lg-4 text-center">


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org