asf-tooling commented on issue #276:
URL: 
https://github.com/apache/tooling-trusted-releases/issues/276#issuecomment-4410395003

   <!-- gofannon-issue-triage-bot v2 -->
   
   **Automated triage** — analyzed at `main@2da7807a`
   
   **Type:** `new_feature`  •  **Classification:** `actionable`  •  
**Confidence:** `medium`
   **Application domain(s):** `admin_operations`
   
   ### Summary
   The data browser admin page renders all records at once, making large tables 
(e.g., PublicSigningKey) unusable. The issue requests paging and optionally 
search. @dave2wave noted reusable code exists in the agenda tool. @sbp 
confirmed the team decided in a meeting to address this during beta. The code 
responsible is the `data_model` route in `atr/admin/__init__.py` (delegating to 
`_data_browse`) and the template `atr/admin/templates/data-browser.html`, both 
of which currently have no pagination logic.
   
   ### Where this lives in the code today
   
   #### `atr/admin/__init__.py` — `data` (lines 145-150)
   _needs modification_
   Entry point for the data browser, delegates to _data_browse without any 
paging parameters.
   
   ```python
   @admin.typed
   async def data(session: web.Committer, _data: Literal["data"]) -> str:
       """
       URL: GET /data
       """
       return await _data_browse(session, "Committee")
   ```
   
   #### `atr/admin/__init__.py` — `data_model` (lines 145-153)
   _needs modification_
   Route for browsing a specific model's records, currently has no paging or 
search query parameters.
   
   ```python
   @admin.typed
   async def data_model(
       session: web.Committer, _data: Literal["data"], model: unsafe.UnsafeStr 
= unsafe.UnsafeStr("Committee")
   ) -> str:
       """
       URL: GET /data/<model>
       """
   
       return await _data_browse(session, str(model))
   ```
   
   #### `atr/blueprints/admin.py` — `typed` (lines 56-67)
   _extension point_
   The typed decorator supports dataclass parameters parsed from the query 
string, which is how paging/search query params would be accepted.
   
   ```python
   def typed(func: Callable[..., Any]) -> Callable[..., Any]:
       """Decorator that derives the URL path from the function's type 
annotations.
   
       - Literal["..."] parameters become literal path segments
       - safe.SafeType subclass parameters are validated from the URL path
       - pydantic.BaseModel subclass parameters are parsed from the JSON 
request body
       - form.Form subclass parameters are validated from the request body
       - dataclass parameters are parsed from the query string
       - str | None parameters create optional URL segments (two routes 
registered)
       - int, float use Quart's built-in type converters
       - HTTP method is POST if a body or form param is present, GET otherwise
       """
   ```
   
   ### Where new code would go
   - `atr/admin/__init__.py` — before data function (around line 196)
     Add a dataclass for query parameters (page number, page size, search term) 
used by the data browser routes.
   - `atr/admin/templates/data-browser.html` — after the records loop in 
content block
     Add pagination controls (previous/next links, page indicator) and a search 
input form.
   
   ### Proposed approach
   Add a dataclass `DataBrowserQuery` with `page` (int, default 1), `page_size` 
(int, default 50), and `search` (str, default "") fields. Pass this as a query 
parameter to `data_model`. Modify `_data_browse` to apply LIMIT/OFFSET to the 
database query and optionally filter records by a search term (using LIKE on 
text columns). Pass pagination metadata (current page, total pages, total 
records) to the template.
   
   Update the template to show a search input at the top, display the record 
count, and render pagination links (Previous / Page X of Y / Next) at the 
bottom. The search could filter on all text columns of the selected model using 
a simple LIKE query. Since @dave2wave mentioned reusable code in the agenda 
tool, the team may want to adopt that widget pattern, but the core change is 
straightforward enough to implement directly.
   
   ### Suggested patches
   
   #### `atr/admin/__init__.py`
   Add a dataclass for query parameters and modify data_model to accept 
paging/search params.
   
   ````diff
   --- a/atr/admin/__init__.py
   +++ b/atr/admin/__init__.py
   @@ -26,6 +26,7 @@
    import sys
    import time
    from collections.abc import Callable, Mapping
   +from dataclasses import dataclass, field
    from typing import Any, Final, Literal, NamedTuple
    
    import aiofiles.os
   @@ -60,6 +61,13 @@
    ROUTES_MODULE: Final[Literal[True]] = True
    
    _MAXIMUM_PERMITTED_AGE_DAYS: Final = datetime.timedelta(days=90)
   +_DEFAULT_PAGE_SIZE: Final = 50
   +
   +
   +@dataclass
   +class DataBrowserQuery:
   +    page: int = 1
   +    page_size: int = _DEFAULT_PAGE_SIZE
   +    search: str = ""
    
    
    class BrowseAsUserForm(form.Form):
   @@ -196,18 +204,18 @@
    @admin.typed
    async def data(session: web.Committer, _data: Literal["data"]) -> str:
        """
        URL: GET /data
        """
   -    return await _data_browse(session, "Committee")
   +    return await _data_browse(session, "Committee", DataBrowserQuery())
    
    
    @admin.typed
    async def data_model(
   -    session: web.Committer, _data: Literal["data"], model: unsafe.UnsafeStr 
= unsafe.UnsafeStr("Committee")
   +    session: web.Committer, _data: Literal["data"], model: unsafe.UnsafeStr 
= unsafe.UnsafeStr("Committee"), query: DataBrowserQuery = DataBrowserQuery()
    ) -> str:
        """
        URL: GET /data/<model>
        """
    
   -    return await _data_browse(session, str(model))
   +    return await _data_browse(session, str(model), query)
   ````
   
   #### `atr/admin/templates/data-browser.html`
   Add search input, record count display, and pagination controls to the data 
browser template.
   
   ````diff
   --- a/atr/admin/templates/data-browser.html
   +++ b/atr/admin/templates/data-browser.html
   @@ -37,6 +37,30 @@
          border-radius: 4px;
        }
    
   +    .page-search-form {
   +      margin: 1rem 0;
   +      display: flex;
   +      gap: 0.5rem;
   +      align-items: center;
   +    }
   +
   +    .page-search-form input[type="text"] {
   +      padding: 0.25rem 0.5rem;
   +      border: 1px solid #ddd;
   +      border-radius: 2px;
   +      flex: 1;
   +      max-width: 300px;
   +    }
   +
   +    .page-pagination {
   +      margin: 1rem 0;
   +      display: flex;
   +      gap: 1rem;
   +      align-items: center;
   +    }
   +
   +    .page-pagination a.disabled {
   +      color: #ccc;
   +      pointer-events: none;
   +    }
   +
        .page-record pre {
          background: #f5f5f5;
          padding: 0.5rem;
   @@ -65,8 +89,25 @@
      <h1>Data browser</h1>
      <p>Browse all records in the database.</p>
    
      <div class="page-model-nav">
        {% for model_name in models %}
          <a href="{{ as_url(admin.data_model, model=model_name) }}"
             {% if model == model_name %}class="active"{% endif %}>{{ 
model_name }}</a>
        {% endfor %}
      </div>
    
   +  {% if model %}
   +  <form class="page-search-form" method="get" action="{{ 
as_url(admin.data_model, model=model) }}">
   +    <input type="text" name="search" value="{{ search }}" 
placeholder="Search records...">
   +    <button type="submit">Search</button>
   +    {% if search %}
   +      <a href="{{ as_url(admin.data_model, model=model) }}">Clear</a>
   +    {% endif %}
   +  </form>
   +  {% endif %}
   +
   +  {% if total_records is defined %}
   +    <p>Showing {{ records | length }} of {{ total_records }} records{% if 
search %} matching "{{ search }}"{% endif %} (page {{ page }} of {{ total_pages 
}}).</p>
   +  {% endif %}
   +
      {% if records %}
        <div>
          {% for record in records %}
   @@ -93,6 +134,20 @@
          {% endfor %}
        </div>
   +
   +    {% if total_pages > 1 %}
   +    <div class="page-pagination">
   +      {% if page > 1 %}
   +        <a href="{{ as_url(admin.data_model, model=model) }}?page={{ page - 
1 }}{% if search %}&search={{ search }}{% endif %}">← Previous</a>
   +      {% else %}
   +        <a class="disabled">← Previous</a>
   +      {% endif %}
   +      <span>Page {{ page }} of {{ total_pages }}</span>
   +      {% if page < total_pages %}
   +        <a href="{{ as_url(admin.data_model, model=model) }}?page={{ page + 
1 }}{% if search %}&search={{ search }}{% endif %}">Next →</a>
   +      {% else %}
   +        <a class="disabled">Next →</a>
   +      {% endif %}
   +    </div>
   +    {% endif %}
      {% else %}
        <p class="page-no-records">No records found for {{ model }}.</p>
      {% endif %}
    {% endblock content %}
   ````
   
   ### Open questions
   - The `_data_browse` function is in the truncated portion of 
`atr/admin/__init__.py` — it needs to be modified to accept the query params 
and apply LIMIT/OFFSET/search filtering, but its exact current implementation 
is not visible.
   - How should search work across different model types? A simple approach 
would be to cast all fields to text and use LIKE, but performance on large 
tables may require indexing.
   - Should the page_size be user-configurable via a dropdown, or fixed at 50?
   - @dave2wave mentioned reusable code in the 'agenda tool' — should that 
widget be adopted instead of a custom implementation?
   - The `DataBrowserQuery` dataclass approach for query parameters needs 
verification that `@admin.typed` correctly parses dataclass query params with 
default values when some params come from the URL path (like `model`).
   
   ### Files examined
   - `atr/admin/templates/data-browser.html`
   - `atr/admin/__init__.py`
   - `atr/blueprints/admin.py`
   
   ---
   *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