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]