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]

Reply via email to