You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2025/07/31 17:48:53 UTC

(tooling-trusted-release) branch main updated: Add a form to add a check result ignore

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

sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git


The following commit(s) were added to refs/heads/main by this push:
     new e15b64e  Add a form to add a check result ignore
e15b64e is described below

commit e15b64e346b3ac592d56769ff1aedc7e8f2a3073
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Thu Jul 31 18:48:33 2025 +0100

    Add a form to add a check result ignore
---
 atr/forms.py           | 46 ++++++++++++++++++++++++--
 atr/models/sql.py      | 14 ++++++++
 atr/routes/__init__.py |  6 ++++
 atr/routes/ignores.py  | 90 +++++++++++++++++++++++++++-----------------------
 4 files changed, 113 insertions(+), 43 deletions(-)

diff --git a/atr/forms.py b/atr/forms.py
index 1da7f01..06163bd 100644
--- a/atr/forms.py
+++ b/atr/forms.py
@@ -19,6 +19,8 @@ from __future__ import annotations
 
 from typing import Any, Final, TypeVar
 
+import htpy
+import markupsafe
 import quart_wtf
 import quart_wtf.typing
 import wtforms
@@ -197,9 +199,49 @@ def radio(label: str, optional: bool = False, validators: list[Any] | None = Non
     return wtforms.RadioField(label, validators=validators, **kwargs)
 
 
-def select(label: str, validators: list[Any] | None = None, **kwargs: Any) -> wtforms.SelectField:
+def render(form: Typed, action: str) -> htpy.Element:
+    hidden_elems: list[markupsafe.Markup] = []
+    field_rows: list[htpy.Element] = []
+    submit_row: htpy.Element | None = None
+
+    for field in form:
+        if isinstance(field, wtforms.HiddenField):
+            hidden_elems.append(markupsafe.Markup(str(field)))
+            continue
+        if isinstance(field, wtforms.StringField):
+            widget = markupsafe.Markup(str(field(class_="form-control")))
+            label_html = markupsafe.Markup(str(field.label(class_="col-sm-3 col-form-label text-sm-end")))
+            row = htpy.div(".mb-3 row")[label_html, htpy.div(".col-sm-8")[widget]]
+            field_rows.append(row)
+            continue
+        if isinstance(field, wtforms.SelectField):
+            widget = markupsafe.Markup(str(field(class_="form-select")))
+            label_html = markupsafe.Markup(str(field.label(class_="col-sm-3 col-form-label text-sm-end")))
+            row = htpy.div(".mb-3 row")[label_html, htpy.div(".col-sm-8")[widget]]
+            field_rows.append(row)
+            continue
+        if isinstance(field, wtforms.SubmitField):
+            button_html = markupsafe.Markup(str(field(class_="btn btn-primary mt-2")))
+            submit_row = htpy.div(".row")[htpy.div(".col-sm-9 offset-sm-3")[button_html]]
+            continue
+        raise TypeError(f"Unsupported field type: {type(field).__name__}")
+
+    form_children: list[htpy.Element | markupsafe.Markup] = hidden_elems + field_rows
+    if submit_row is not None:
+        form_children.append(submit_row)
+
+    return htpy.form(".atr-canary", action=action, method="post")[form_children]
+
+
+def select(
+    label: str, optional: bool = False, validators: list[Any] | None = None, **kwargs: Any
+) -> wtforms.SelectField:
     if validators is None:
-        validators = [REQUIRED]
+        validators = []
+    if optional is False:
+        validators.append(REQUIRED)
+    else:
+        validators.append(OPTIONAL)
     return wtforms.SelectField(label, validators=validators, **kwargs)
 
 
diff --git a/atr/models/sql.py b/atr/models/sql.py
index e77d213..ab93527 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -63,6 +63,20 @@ class CheckResultStatusIgnore(str, enum.Enum):
     FAILURE = "failure"
     WARNING = "warning"
 
+    @classmethod
+    def from_form_field(cls, status: str) -> Optional["CheckResultStatusIgnore"]:
+        match status:
+            case "None":
+                return None
+            case "CheckResultStatusIgnore.EXCEPTION":
+                return cls.EXCEPTION
+            case "CheckResultStatusIgnore.FAILURE":
+                return cls.FAILURE
+            case "CheckResultStatusIgnore.WARNING":
+                return cls.WARNING
+            case _:
+                raise ValueError(f"Invalid status: {status}")
+
 
 class ProjectStatus(str, enum.Enum):
     ACTIVE = "active"
diff --git a/atr/routes/__init__.py b/atr/routes/__init__.py
index cfd9e03..769a634 100644
--- a/atr/routes/__init__.py
+++ b/atr/routes/__init__.py
@@ -169,6 +169,12 @@ class CommitterSession:
         self._projects: list[sql.Project] | None = None
         self._session = web_session
 
+    @property
+    def asf_uid(self) -> str:
+        if self._session.uid is None:
+            raise base.ASFQuartException("Not authenticated", errorcode=401)
+        return self._session.uid
+
     def __getattr__(self, name: str) -> Any:
         # TODO: Not type safe, should subclass properly if possible
         # For example, we can access session.no_such_attr and the type checkers won't notice
diff --git a/atr/routes/ignores.py b/atr/routes/ignores.py
index cfee760..4b88bce 100644
--- a/atr/routes/ignores.py
+++ b/atr/routes/ignores.py
@@ -19,11 +19,11 @@ from typing import Final
 
 import htpy
 import markupsafe
+import quart
 import werkzeug.wrappers.response as response
 from htpy import (
     button,
     div,
-    form,
     h1,
     h2,
     h3,
@@ -62,24 +62,37 @@ document.querySelectorAll("table.page-details input.form-control").forEach(funct
 
 
 class AddIgnoreForm(forms.Typed):
-    release_glob = forms.string("Release glob")
-    revision_number = forms.string("Revision number")
-    checker_glob = forms.string("Checker glob")
-    primary_rel_path_glob = forms.string("Primary rel path glob")
-    member_rel_path_glob = forms.string("Member rel path glob")
-    status = forms.string("Status")
+    release_glob = forms.optional("Release pattern")
+    revision_number = forms.optional("Revision number (literal)")
+    checker_glob = forms.optional("Checker pattern")
+    primary_rel_path_glob = forms.optional("Primary rel path pattern")
+    member_rel_path_glob = forms.optional("Member rel path pattern")
+    status = forms.select(
+        "Status",
+        optional=True,
+        choices=[
+            (None, "-"),
+            (sql.CheckResultStatusIgnore.EXCEPTION, "Exception"),
+            (sql.CheckResultStatusIgnore.FAILURE, "Failure"),
+            (sql.CheckResultStatusIgnore.WARNING, "Warning"),
+        ],
+    )
+    message = forms.optional("Message pattern")
+    submit = forms.submit("Add ignore")
+
+    # TODO: Validate that at least one field is set
 
 
 @routes.committer("/ignores/<committee_name>", methods=["GET", "POST"])
 async def ignores(session: routes.CommitterSession, committee_name: str) -> str | response.Response:
-    async with storage.read() as read:
+    async with storage.read(session.asf_uid) as read:
         ragp = read.as_general_public()
         ignores = await ragp.checks.ignores(committee_name)
 
     content = div[
         h1["Ignored checks"],
         p[f"Manage ignored checks for committee {committee_name}."],
-        _add_ignore(),
+        _add_ignore(committee_name),
         _existing_ignores(ignores),
         _script(_UPDATE_IGNORE_FORM),
     ]
@@ -88,13 +101,31 @@ async def ignores(session: routes.CommitterSession, committee_name: str) -> str
 
 
 @routes.committer("/ignores/<committee_name>/add", methods=["POST"])
-async def ignores_committe_add(session: routes.CommitterSession, committee_name: str) -> str | response.Response:
-    form = forms.Value()
-    if not form.validate_on_submit():
+async def ignores_committee_add(session: routes.CommitterSession, committee_name: str) -> str | response.Response:
+    data = await quart.request.form
+    form = await AddIgnoreForm.create_form(data=data)
+    if not (await form.validate_on_submit()):
         return await session.redirect(ignores, error="Form validation errors")
 
-    # TODO: Add ignore
-    return await session.redirect(ignores, success="Ignore added")
+    status = sql.CheckResultStatusIgnore.from_form_field(form.status.data)
+
+    async with storage.write(session.asf_uid) as write:
+        wacm = write.as_committee_member(committee_name)
+        await wacm.checks.ignore_add(
+            release_glob=form.release_glob.data,
+            revision_number=form.revision_number.data,
+            checker_glob=form.checker_glob.data,
+            primary_rel_path_glob=form.primary_rel_path_glob.data,
+            member_rel_path_glob=form.member_rel_path_glob.data,
+            status=status,
+            message_glob=form.message.data,
+        )
+
+    return await session.redirect(
+        ignores,
+        committee_name=committee_name,
+        success="Ignore added",
+    )
 
 
 def _check_result_ignore_card(cri: sql.CheckResultIgnore) -> htpy.Element:
@@ -121,7 +152,7 @@ def _check_result_ignore_card(cri: sql.CheckResultIgnore) -> htpy.Element:
     add_row("Checker", cri.checker_glob)
     add_row("Primary path", cri.primary_rel_path_glob)
     add_row("Member path", cri.member_rel_path_glob)
-    add_row("Status (enum)", cri.status)
+    add_row("Status (enum)", cri.status.value.title() if cri.status else "")
     add_row("Message", cri.message_glob)
 
     table_striped = table(".table.table-striped.table-bordered.page-details")[table_rows]
@@ -134,35 +165,12 @@ def _check_result_ignore_card(cri: sql.CheckResultIgnore) -> htpy.Element:
     return card
 
 
-def _add_ignore() -> htpy.Element:
-    table_rows = []
-
-    def add_row(label: str) -> None:
-        nonlocal table_rows
-        table_rows.append(
-            tr[
-                th(".text-end.text-nowrap")[label],
-                td[input(".form-control.form-control-sm")],
-            ]
-        )
-
-    add_row("Release")
-    add_row("Revision number (literal)")
-    add_row("Checker")
-    add_row("Primary path")
-    add_row("Member path")
-    add_row("Status (enum)")
-    add_row("Message")
-
-    table_striped = table(".table.table-striped.table-bordered")[table_rows]
-
+def _add_ignore(committee_name: str) -> htpy.Element:
+    form_path = util.as_url(ignores_committee_add, committee_name=committee_name)
     return div[
         h2["Add ignore"],
         p["Add a new ignore for a check result."],
-        form(".atr-canary")[
-            table_striped,
-            p[button(".btn.btn-primary")["Add"]],
-        ],
+        forms.render(AddIgnoreForm(), form_path),
     ]
 
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org