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

tn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-atr-experiments.git


The following commit(s) were added to refs/heads/main by this push:
     new 81ee8dd  add page to display executed background tasks to the admin 
area
81ee8dd is described below

commit 81ee8dd140bd6a63d96fb17d9b5e756c6e8f203e
Author: Thomas Neidhart <[email protected]>
AuthorDate: Wed Mar 5 10:37:46 2025 +0100

    add page to display executed background tasks to the admin area
---
 .pre-commit-config.yaml                   |  2 +-
 atr/blueprints/admin/admin.py             |  5 +++
 atr/blueprints/admin/templates/tasks.html | 70 +++++++++++++++++++++++++++++++
 atr/blueprints/api/api.py                 | 20 ++++++++-
 atr/db/service.py                         | 13 +++++-
 atr/static/README.txt                     |  1 +
 atr/static/css/mermaid.min.css            |  5 +++
 atr/static/js/gridjs.production.min.js    |  2 +
 atr/templates/includes/sidebar.html       |  5 +++
 9 files changed, 120 insertions(+), 3 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 32974f2..0c5bbb3 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -43,7 +43,7 @@ repos:
     - id: stylelint
       additional_dependencies: ['[email protected]', 
'[email protected]']
       files: "atr/static/css/.*\\.css$"
-      exclude: 
"atr/static/css/bootstrap\\.min\\.css$|atr/static/css/fontawesome\\.all\\.min\\.css$|atr/static/css/normalize\\.css$"
+      exclude: 
"atr/static/css/bootstrap\\.min\\.css$|atr/static/css/fontawesome\\.all\\.min\\.css$|atr/static/css/normalize\\.css$|atr/static/css/mermaid\\.min\\.css$"
       types_or: ['css']
       args: ['--fix']
 - repo: local
diff --git a/atr/blueprints/admin/admin.py b/atr/blueprints/admin/admin.py
index ca603e2..87709af 100644
--- a/atr/blueprints/admin/admin.py
+++ b/atr/blueprints/admin/admin.py
@@ -278,6 +278,11 @@ async def _update_pmcs() -> int:
     return updated_count
 
 
[email protected]("/tasks")
+async def admin_tasks() -> str:
+    return await render_template("tasks.html")
+
+
 @blueprint.route("/debug/database")
 async def admin_debug_database() -> str:
     """Debug information about the database."""
diff --git a/atr/blueprints/admin/templates/tasks.html 
b/atr/blueprints/admin/templates/tasks.html
new file mode 100644
index 0000000..8563686
--- /dev/null
+++ b/atr/blueprints/admin/templates/tasks.html
@@ -0,0 +1,70 @@
+{% extends "layouts/base-admin.html" %}
+
+{% block title %}
+  Executed Tasks ~ ATR
+{% endblock %}
+
+{% block stylesheets %}
+  {{ super() }}
+  <link rel="stylesheet"
+        href="{{ url_for('static', filename='css/mermaid.min.css') }}" />
+
+  <style>
+    .gridjs-pages button {
+        color: unset;
+    }
+  </style>
+{% endblock stylesheets %}
+
+{% block content %}
+  <h1>Executed Background Tasks</h1>
+
+  <div id="task-table"></div>
+{% endblock content %}
+
+{% block javascripts %}
+  {{ super() }}
+  <script src="{{ url_for('static', filename='js/gridjs.production.min.js') 
}}"></script>
+
+  <script>
+    new gridjs.Grid({
+      columns: [
+        'ID',
+        'Task Type',
+        'Task Status',
+        {
+          name: 'Added',
+          formatter: (cell) => `${new Date(cell).toISOString()}`
+        },
+        {
+          name: 'Started',
+          formatter: (cell) => `${new Date(cell).toISOString()}`
+        },
+        {
+          name: 'Completed',
+          formatter: (cell) => `${new Date(cell).toISOString()}`
+        }
+      ],
+      style: {
+        table: {
+          'white-space': 'nowrap'
+        }
+      },
+      search: true,
+      pagination: {
+        limit: 15,
+        server: {
+          url: (prev, page, limit) => `${prev}?limit=${limit}&offset=${page * 
limit}`
+        }
+      },
+      // sort: true,
+      server: {
+        url: '/api/tasks',
+        then: data => data.data.map(task => [
+          task.id, task.task_type, task.status, task.added, task.started, 
task.completed
+        ]),
+        total: data => data.count
+      }
+    }).render(document.getElementById("task-table"));
+  </script>
+{% endblock javascripts %}
diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index 82d9d3d..79fd3ec 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -16,9 +16,13 @@
 # under the License.
 
 from collections.abc import Mapping
+from dataclasses import dataclass
 from typing import Any
 
-from atr.db.service import get_pmc_by_name
+from quart import Response, jsonify
+from quart_schema import validate_querystring
+
+from atr.db.service import get_pmc_by_name, get_tasks_paged
 
 from . import blueprint
 
@@ -30,3 +34,17 @@ async def api_pmc(project_name: str) -> tuple[Mapping[str, 
Any], int]:
         return pmc.model_dump(), 200
     else:
         return {}, 404
+
+
+@dataclass
+class Pagination:
+    offset: int = 0
+    limit: int = 20
+
+
[email protected]("/tasks")
+@validate_querystring(Pagination)
+async def api_tasks(query_args: Pagination) -> Response:
+    paged_tasks, count = await get_tasks_paged(limit=query_args.limit, 
offset=query_args.offset)
+    result = {"data": [x.model_dump(exclude={"result"}) for x in paged_tasks], 
"count": count}
+    return jsonify(result)
diff --git a/atr/db/service.py b/atr/db/service.py
index 5a1b06a..6c8c2a1 100644
--- a/atr/db/service.py
+++ b/atr/db/service.py
@@ -17,9 +17,10 @@
 
 from collections.abc import Sequence
 
+from sqlalchemy import func
 from sqlmodel import select
 
-from atr.db.models import PMC
+from atr.db.models import PMC, Task
 
 from . import get_session
 
@@ -37,3 +38,13 @@ async def get_pmcs() -> Sequence[PMC]:
         statement = select(PMC)
         pmcs = (await db_session.execute(statement)).scalars().all()
         return pmcs
+
+
+async def get_tasks_paged(limit: int, offset: int) -> tuple[Sequence[Task], 
int]:
+    """Returns a list of Tasks based on limit and offset values together with 
the total count."""
+
+    async with get_session() as db_session:
+        statement = 
select(Task).limit(limit).offset(offset).order_by(Task.id.desc())  # type: 
ignore
+        tasks = (await db_session.execute(statement)).scalars().all()
+        count = (await 
db_session.execute(select(func.count(Task.id)))).scalar_one()  # type: ignore
+        return tasks, count
diff --git a/atr/static/README.txt b/atr/static/README.txt
index 24e9335..f6d7751 100644
--- a/atr/static/README.txt
+++ b/atr/static/README.txt
@@ -3,3 +3,4 @@ The following static resources are copied:
 bootstrap 5.3.3
 fontawesome-free 6.7.2
 normalize.css 8.0.1
+gridjs 6.2.0
diff --git a/atr/static/css/mermaid.min.css b/atr/static/css/mermaid.min.css
new file mode 100644
index 0000000..718601d
--- /dev/null
+++ b/atr/static/css/mermaid.min.css
@@ -0,0 +1,5 @@
+.gridjs-footer button,.gridjs-head 
button{background-color:transparent;background-image:none;border:none;cursor:pointer;margin:0;outline:none;padding:0}.gridjs-temp{position:relative}.gridjs-head{margin-bottom:5px;padding:5px
 
1px;width:100%}.gridjs-head::after{clear:both;content:"";display:block}.gridjs-head:empty{border:none;padding:0}.gridjs-container{color:#000000;display:inline-block;overflow:hidden;padding:2px;position:relative;z-index:0}.gridjs-footer{background-color:#ffffff;borde
 [...]
+
+@supports (-moz-appearance:none){th.gridjs-th-fixed{box-shadow:0 0 0 1px 
#e5e7eb}}th.gridjs-th:first-child{border-left:none}th.gridjs-th:last-child{border-right:none}.gridjs-tr{border:none}.gridjs-tr-selected
 td{background-color:#ebf5ff}.gridjs-tr:last-child td{border-bottom:0}.gridjs 
*,.gridjs ::after,.gridjs 
::before{box-sizing:border-box}.gridjs-wrapper{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;border-color:#e5e7eb;border-radius:8px
 8px 0 0;border-top-width: [...]
+
+@keyframes shimmer{100%{transform:translateX(100%)}}.gridjs-td 
.gridjs-checkbox{cursor:pointer;display:block;margin:auto}.gridjs-resizable{bottom:0;position:absolute;right:0;top:0;width:5px}.gridjs-resizable:hover{background-color:#9bc2f7;cursor:ew-resize}
diff --git a/atr/static/js/gridjs.production.min.js 
b/atr/static/js/gridjs.production.min.js
new file mode 100644
index 0000000..31761d7
--- /dev/null
+++ b/atr/static/js/gridjs.production.min.js
@@ -0,0 +1,2 @@
+!function(t,n){"object"==typeof exports&&"undefined"!=typeof 
module?n(exports):"function"==typeof 
define&&define.amd?define(["exports"],n):n((t||self).gridjs={})}(this,function(t){function
 n(t,n){for(var e=0;e<n.length;e++){var 
r=n[e];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in 
r&&(r.writable=!0),Object.defineProperty(t,"symbol"==typeof(o=function(t,n){if("object"!=typeof
 t||null===t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var 
r=e.call(t,"string");if("object"!=t [...]
+//# sourceMappingURL=gridjs.umd.js.map
diff --git a/atr/templates/includes/sidebar.html 
b/atr/templates/includes/sidebar.html
index bcf9bdf..c29b70c 100644
--- a/atr/templates/includes/sidebar.html
+++ b/atr/templates/includes/sidebar.html
@@ -84,6 +84,11 @@
             <a href="{{ url_for('admin.admin_projects_update') }}"
                {% if request.endpoint == 'admin.admin_projects_update' 
%}class="active"{% endif %}>Update projects</a>
           </li>
+          <li>
+            <i class="fa-solid fa-list-check"></i>
+            <a href="{{ url_for('admin.admin_tasks') }}"
+               {% if request.endpoint == 'admin.admin_tasks' 
%}class="active"{% endif %}>Background Tasks</a>
+          </li>
           <li>
             <i class="fa-solid fa-weight-scale"></i>
             <a href="{{ url_for('admin.admin_performance') }}"


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to