asf-tooling commented on issue #1249:
URL:
https://github.com/apache/tooling-trusted-releases/issues/1249#issuecomment-4495884989
<!-- gofannon-issue-triage-bot v2 -->
**Automated triage** — analyzed at `main@ab610b23`
**Type:** `new_feature` • **Classification:** `actionable` •
**Confidence:** `medium`
**Application domain(s):** `web_interface_and_api`,
`project_and_committee_management`
### Summary
Issue requests three improvements to the /projects directory page: (1)
expose the project label/key (needed for tooling-actions and maven-plugin
configuration), (2) add a URL parameter to toggle 'Show all' vs 'Show my
projects' for linkability, and (3) switch from card/tile layout to a structured
list view. @dave2wave noted the team will discuss internally, so these diffs
are illustrative starting points. The relevant code is in the template
(projects.html), the JS filter (projects-directory.js), and the route handler
(get/projects.py).
### Where this lives in the code today
#### `atr/get/projects.py` — `projects` (lines 52-59)
_currently does this_
Route handler for /projects that fetches all projects and renders the
template; does not read any query parameters for filtering state.
```python
@get.typed
async def projects(session: web.Public, _projects: Literal["projects"]) ->
str:
"""
URL: /projects
Main project directory page.
"""
async with db.session() as data:
projects = await data.project(_committee=True,
_releases=True).order_by(sql.Project.name).all()
```
### Proposed approach
Since @dave2wave noted the team will discuss internally, these are
illustrative changes. The minimal and highest-value change is exposing the
project label (key) in the project list — this is what external tooling
(tooling-actions, atr-maven-plugin) needs users to copy. This can be done by
adding a small `<code>` element below or beside the display name showing
`project.key`.
For URL parameter support, the JS can read `window.location.search` on page
load to check for a `?show=mine` parameter, and update the URL via
`history.replaceState` when the toggle is clicked. This requires no server-side
changes since the filtering is client-side.
For the list view conversion, the card grid would be replaced with a table
or compact list showing columns for label, display name, committee, categories,
and languages. This is a larger UI change that the team should discuss first.
### Suggested patches
#### `atr/templates/projects.html`
Expose the project label (key) in each project entry and convert to a more
compact list/table layout that shows label prominently
````diff
--- a/atr/templates/projects.html
+++ b/atr/templates/projects.html
@@ -20,41 +20,55 @@
<div class="mb-3">
<p>
Total count: <span id="project-count">{{ projects|length }}</span>
</p>
</div>
- <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-4">
+ <table class="table table-hover align-middle">
+ <thead>
+ <tr>
+ <th>Project</th>
+ <th>Label</th>
+ <th>Categories</th>
+ <th>Languages</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
{% for project in projects %}
{% set is_part = false %}
{% if current_user and project.committee %}
{% if current_user.uid in project.committee.committee_members or
current_user.uid in project.committee.committers or
current_user.uid in project.committee.release_managers %}
{% set is_part = true %}
{% endif %}
{% endif %}
- <div class="col">
- <div class="card h-100 shadow-sm position-relative page-project-card
{{ '' if project.status.value.lower() == 'active' else 'bg-body-secondary' }}"
- data-project-url="{{ as_url(get.projects.view,
project_key=project.key) }}"
- data-is-participant="{{ 'true' if is_part else 'false' }}">
- <div class="card-body">
- <div class="row g-1">
- <div class="col-sm">
- <h3 class="card-title fs-4 mb-3"><a href="{{
as_url(get.projects.view, project_key=project.key) }}"
class="text-decoration-none text-body stretched-link">{{ project.display_name
}}</a></h3>
- </div>
- {% if project.status.value.lower() != 'active' %}
- <div class="col-sm-2">
- <span class="badge text-bg-secondary">{{
project.status.value.lower() }}</span>
- </div>
- {% endif %}
- </div>
- {% if project.category %}
- <div class="row g-1">
- {% set categories = project.category.split(', ') %}
- {% for category in categories if category != "retired" %}
- <div class="col-sm-auto">
- <span class="badge text-bg-primary">{{ category }}</span>
- </div>
- {% endfor %}
- </div>
- {% endif %}
- {% if project.programming_languages %}
- <div class="row g-1">
- {% set langs = project.programming_languages.split(', ') %}
- {% for lang in langs %}
- <div class="col-sm-auto">
- <span class="badge text-bg-success">{{ lang }}</span>
- </div>
- {% endfor %}
- </div>
- {% endif %}
-
- {% if project.key in action_forms %}<div class="mt-3
position-relative z-1">{{ action_forms[project.key] }}</div>{% endif %}
-
- </div>
- </div>
- </div>
+ <tr class="page-project-card {{ '' if project.status.value.lower() ==
'active' else 'table-secondary' }}"
+ data-project-url="{{ as_url(get.projects.view,
project_key=project.key) }}"
+ data-is-participant="{{ 'true' if is_part else 'false' }}">
+ <td>
+ <a href="{{ as_url(get.projects.view, project_key=project.key)
}}" class="text-decoration-none fw-semibold">{{ project.display_name }}</a>
+ {% if project.status.value.lower() != 'active' %}
+ <span class="badge text-bg-secondary ms-1">{{
project.status.value.lower() }}</span>
+ {% endif %}
+ </td>
+ <td><code>{{ project.key }}</code></td>
+ <td>
+ {% if project.category %}
+ {% set categories = project.category.split(', ') %}
+ {% for category in categories if category != "retired" %}
+ <span class="badge text-bg-primary me-1">{{ category }}</span>
+ {% endfor %}
+ {% endif %}
+ </td>
+ <td>
+ {% if project.programming_languages %}
+ {% set langs = project.programming_languages.split(', ') %}
+ {% for lang in langs %}
+ <span class="badge text-bg-success me-1">{{ lang }}</span>
+ {% endfor %}
+ {% endif %}
+ </td>
+ <td>
+ {% if project.key in action_forms %}{{ action_forms[project.key]
}}{% endif %}
+ </td>
+ </tr>
{% endfor %}
-</div>
+ </tbody>
+ </table>
{% endblock content %}
````
#### `atr/static/js/src/projects-directory.js`
Add URL parameter support so ?show=mine deep-links to the
participant-filtered view, and update URL on toggle
````diff
--- a/atr/static/js/src/projects-directory.js
+++ b/atr/static/js/src/projects-directory.js
@@ -20,6 +20,16 @@
*/
+// Read initial state from URL parameters
+function getInitialShowState() {
+ const params = new URLSearchParams(window.location.search);
+ return params.get("show") === "mine" ? "participant" : "all";
+}
+
+function updateUrlState(showing) {
+ const url = new URL(window.location);
+ if (showing === "participant") {
+ url.searchParams.set("show", "mine");
+ } else {
+ url.searchParams.delete("show");
+ }
+ window.history.replaceState({}, "", url);
+}
+
function filter() {
const projectFilter = document
.getElementById("project-filter")
@@ -53,6 +63,7 @@
// Participant filter logic
const participantButton =
document.getElementById("filter-participant-button");
+
participantButton.addEventListener("click", function () {
const showing = this.dataset.showing;
const cards = document.querySelectorAll(".page-project-card");
@@ -68,6 +79,7 @@
this.textContent = "Show all projects";
this.dataset.showing = "participant";
+ updateUrlState("participant");
} else {
// Switch to showing all projects
cards.forEach((card) => {
@@ -76,8 +88,26 @@
});
this.textContent = "Show my projects";
this.dataset.showing = "all";
+ updateUrlState("all");
}
// Reset text filter when toggling participant view
document.getElementById("project-filter").value = "";
// Update count
document.getElementById("project-count").textContent = visibleCount;
});
+
+// On page load, apply initial state from URL
+(function () {
+ const initialState = getInitialShowState();
+ if (initialState === "participant") {
+ const cards = document.querySelectorAll(".page-project-card");
+ let visibleCount = 0;
+ cards.forEach((card) => {
+ const isParticipant = card.dataset.isParticipant ===
"true";
+ card.parentElement.hidden = !isParticipant;
+ if (!card.parentElement.hidden) visibleCount++;
+ });
+ participantButton.textContent = "Show all projects";
+ participantButton.dataset.showing = "participant";
+ document.getElementById("project-count").textContent =
visibleCount;
+ }
+})();
````
### Open questions
- The team will discuss internally (per @dave2wave) — the list-vs-tile
layout change is a significant UI decision that should be confirmed before
implementing.
- The JS filter currently uses `.page-project-card` class and
`.parentElement.hidden` — converting from cards-in-cols to table rows changes
the DOM structure, so the JS selectors need updating to work with <tr> elements
instead of card divs (the diff for projects-directory.js assumes the class is
on the <tr> and uses the row directly rather than parentElement).
- Should the table also show the committee name as a column for additional
context?
- Should the text filter also search by label/key in addition to display
name?
### Files examined
- `atr/get/projects.py`
- `atr/templates/projects.html`
- `atr/shared/projects.py`
- `atr/post/projects.py`
- `atr/storage/writers/project.py`
- `atr/templates/index-committer.html`
- `atr/static/js/src/projects-directory.js`
- `atr/get/committees.py`
### Related issues
This issue appears related to: #1253.
_Both concern the project label/identifier field - one requests renaming it
for clarity, the other requests exposing it in the project list_
---
*Draft from a triage agent. A human reviewer should validate before merging
any change. The agent did not run tests or verify diffs apply.*
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]