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