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-releases.git
The following commit(s) were added to refs/heads/main by this push:
new 47ef22c Move file, finish, and ignores routes to the new layout
47ef22c is described below
commit 47ef22caaa69d8def3e96251bc9fa5a36780a632
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Oct 28 14:53:30 2025 +0000
Move file, finish, and ignores routes to the new layout
---
atr/get/__init__.py | 6 +
atr/{routes => get}/file.py | 7 +-
atr/{post/__init__.py => get/finish.py} | 20 +-
atr/get/ignores.py | 132 +++++++++++++
atr/post/__init__.py | 12 +-
atr/{get/__init__.py => post/finish.py} | 37 ++--
atr/post/ignores.py | 121 ++++++++++++
atr/routes/__init__.py | 6 -
atr/routes/ignores.py | 272 ---------------------------
atr/routes/mapping.py | 3 +-
atr/routes/resolve.py | 16 +-
atr/shared/__init__.py | 5 +
atr/shared/distribution.py | 3 +-
atr/{routes => shared}/finish.py | 13 +-
atr/shared/ignores.py | 68 +++++++
atr/templates/announce-selected.html | 2 +-
atr/templates/check-selected-path-table.html | 2 +-
atr/templates/check-selected.html | 2 +-
atr/templates/phase-view.html | 4 +-
atr/templates/revisions-selected.html | 2 +-
20 files changed, 395 insertions(+), 338 deletions(-)
diff --git a/atr/get/__init__.py b/atr/get/__init__.py
index d3b96cc..5c2c1b4 100644
--- a/atr/get/__init__.py
+++ b/atr/get/__init__.py
@@ -25,6 +25,9 @@ import atr.get.distribution as distribution
import atr.get.docs as docs
import atr.get.download as download
import atr.get.draft as draft
+import atr.get.file as file
+import atr.get.finish as finish
+import atr.get.ignores as ignores
import atr.get.vote as vote
ROUTES_MODULE: Final[Literal[True]] = True
@@ -38,5 +41,8 @@ __all__ = [
"docs",
"download",
"draft",
+ "file",
+ "finish",
+ "ignores",
"vote",
]
diff --git a/atr/routes/file.py b/atr/get/file.py
similarity index 90%
rename from atr/routes/file.py
rename to atr/get/file.py
index 1879ab6..545ab9e 100644
--- a/atr/routes/file.py
+++ b/atr/get/file.py
@@ -17,14 +17,15 @@
import werkzeug.wrappers.response as response
-import atr.route as route
+import atr.blueprints.get as get
import atr.template as template
import atr.util as util
+import atr.web as web
[email protected]("/file/<project_name>/<version_name>/<path:file_path>")
[email protected]("/file/<project_name>/<version_name>/<path:file_path>")
async def selected_path(
- session: route.CommitterSession, project_name: str, version_name: str,
file_path: str
+ session: web.Committer, project_name: str, version_name: str, file_path:
str
) -> response.Response | str:
"""View the content of a specific file in the release candidate draft."""
# TODO: Make this independent of the release phase
diff --git a/atr/post/__init__.py b/atr/get/finish.py
similarity index 61%
copy from atr/post/__init__.py
copy to atr/get/finish.py
index 9a396cb..9530bad 100644
--- a/atr/post/__init__.py
+++ b/atr/get/finish.py
@@ -15,14 +15,18 @@
# specific language governing permissions and limitations
# under the License.
-from typing import Final, Literal
-import atr.post.announce as announce
-import atr.post.candidate as candidate
-import atr.post.distribution as distribution
-import atr.post.draft as draft
-import atr.post.vote as vote
+import quart.wrappers.response as quart_response
+import werkzeug.wrappers.response as response
-ROUTES_MODULE: Final[Literal[True]] = True
+import atr.blueprints.get as get
+import atr.shared as shared
+import atr.web as web
-__all__ = ["announce", "candidate", "distribution", "draft", "vote"]
+
[email protected]("/finish/<project_name>/<version_name>")
+async def selected(
+ session: web.Committer, project_name: str, version_name: str
+) -> tuple[quart_response.Response, int] | response.Response | str:
+ """Finish a release preview."""
+ return await shared.finish.selected(session, project_name, version_name)
diff --git a/atr/get/ignores.py b/atr/get/ignores.py
new file mode 100644
index 0000000..517509d
--- /dev/null
+++ b/atr/get/ignores.py
@@ -0,0 +1,132 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from typing import Final
+
+import markupsafe
+import werkzeug.wrappers.response as response
+import wtforms
+
+import atr.blueprints.get as get
+import atr.forms as forms
+import atr.htm as htm
+import atr.models.sql as sql
+import atr.post as post
+import atr.shared as shared
+import atr.storage as storage
+import atr.template as template
+import atr.util as util
+import atr.web as web
+
+# TODO: Port to TypeScript and move to static files
+_UPDATE_IGNORE_FORM: Final[str] = """
+document.querySelectorAll("table.page-details
input.form-control").forEach(function (input) {
+ var row = input.closest("tr");
+ var updateBtn = row.querySelector("button.btn-primary");
+ function check() {
+ if (input.value !== input.dataset.value) {
+ updateBtn.classList.remove("disabled");
+ } else {
+ updateBtn.classList.add("disabled");
+ }
+ }
+ input.addEventListener("input", check);
+ check();
+});
+"""
+
+
[email protected]("/ignores/<committee_name>")
+async def ignores(session: web.Committer, committee_name: str) -> str |
response.Response:
+ async with storage.read() as read:
+ ragp = read.as_general_public()
+ ignores = await ragp.checks.ignores(committee_name)
+
+ content = htm.div[
+ htm.h1["Ignored checks"],
+ htm.p[f"Manage ignored checks for committee {committee_name}."],
+ _add_ignore(committee_name),
+ _existing_ignores(ignores),
+ _script_dom_loaded(_UPDATE_IGNORE_FORM),
+ ]
+
+ return await template.blank("Ignored checks", content)
+
+
+def _check_result_ignore_card(cri: sql.CheckResultIgnore) -> htm.Element:
+ h3_id = cri.id or ""
+ h3_asf_uid = cri.asf_uid
+ h3_created = util.format_datetime(cri.created)
+ card_header_h3 = htm.h3(".mt-3.mb-0")[f"{h3_id} - {h3_asf_uid} -
{h3_created}"]
+
+ form_update = shared.ignores.UpdateIgnoreForm(id=cri.id)
+
+ def set_field(field: wtforms.StringField | wtforms.SelectField, value: str
| None) -> None:
+ if value is not None:
+ field.data = value
+
+ set_field(form_update.release_glob, cri.release_glob)
+ set_field(form_update.revision_number, cri.revision_number)
+ set_field(form_update.checker_glob, cri.checker_glob)
+ set_field(form_update.primary_rel_path_glob, cri.primary_rel_path_glob)
+ set_field(form_update.member_rel_path_glob, cri.member_rel_path_glob)
+ set_field(form_update.status, cri.status.to_form_field() if cri.status
else "None")
+ set_field(form_update.message_glob, cri.message_glob)
+
+ form_path_update = util.as_url(post.ignores.ignores_committee_update,
committee_name=cri.committee_name)
+ form_update_html = forms.render_table(form_update, form_path_update)
+
+ form_delete = shared.ignores.DeleteIgnoreForm(id=cri.id)
+ form_path_delete = util.as_url(post.ignores.ignores_committee_delete,
committee_name=cri.committee_name)
+ form_delete_html = forms.render_simple(
+ form_delete,
+ form_path_delete,
+ form_classes=".mt-2.mb-0",
+ submit_classes="btn-danger",
+ )
+
+ card = htm.div(".card.mb-5")[
+ htm.div(".card-header.d-flex.justify-content-between")[card_header_h3,
form_delete_html],
+ htm.div(".card-body")[form_update_html],
+ ]
+
+ return card
+
+
+def _add_ignore(committee_name: str) -> htm.Element:
+ form_path = util.as_url(post.ignores.ignores_committee_add,
committee_name=committee_name)
+ return htm.div[
+ htm.h2["Add ignore"],
+ htm.p["Add a new ignore for a check result."],
+ forms.render_columns(shared.ignores.AddIgnoreForm(), form_path),
+ ]
+
+
+def _existing_ignores(ignores: list[sql.CheckResultIgnore]) -> htm.Element:
+ return htm.div[
+ htm.h2["Existing ignores"],
+ [_check_result_ignore_card(cri) for cri in ignores] or htm.p["No
ignores found."],
+ ]
+
+
+def _script_dom_loaded(text: str) -> htm.Element:
+ script_text = markupsafe.Markup(f"""
+document.addEventListener("DOMContentLoaded", function () {{
+{text}
+}});
+""")
+ return htm.script[script_text]
diff --git a/atr/post/__init__.py b/atr/post/__init__.py
index 9a396cb..be0f6c2 100644
--- a/atr/post/__init__.py
+++ b/atr/post/__init__.py
@@ -21,8 +21,18 @@ import atr.post.announce as announce
import atr.post.candidate as candidate
import atr.post.distribution as distribution
import atr.post.draft as draft
+import atr.post.finish as finish
+import atr.post.ignores as ignores
import atr.post.vote as vote
ROUTES_MODULE: Final[Literal[True]] = True
-__all__ = ["announce", "candidate", "distribution", "draft", "vote"]
+__all__ = [
+ "announce",
+ "candidate",
+ "distribution",
+ "draft",
+ "finish",
+ "ignores",
+ "vote",
+]
diff --git a/atr/get/__init__.py b/atr/post/finish.py
similarity index 54%
copy from atr/get/__init__.py
copy to atr/post/finish.py
index d3b96cc..4ea8b0e 100644
--- a/atr/get/__init__.py
+++ b/atr/post/finish.py
@@ -15,28 +15,21 @@
# specific language governing permissions and limitations
# under the License.
-from typing import Final, Literal
+from collections.abc import Awaitable, Callable
-import atr.get.announce as announce
-import atr.get.candidate as candidate
-import atr.get.committees as committees
-import atr.get.compose as compose
-import atr.get.distribution as distribution
-import atr.get.docs as docs
-import atr.get.download as download
-import atr.get.draft as draft
-import atr.get.vote as vote
+import quart.wrappers.response as quart_response
+import werkzeug.wrappers.response as response
-ROUTES_MODULE: Final[Literal[True]] = True
+import atr.blueprints.post as post
+import atr.shared as shared
+import atr.web as web
-__all__ = [
- "announce",
- "candidate",
- "committees",
- "compose",
- "distribution",
- "docs",
- "download",
- "draft",
- "vote",
-]
+type Respond = Callable[[int, str], Awaitable[tuple[quart_response.Response,
int] | response.Response]]
+
+
[email protected]("/finish/<project_name>/<version_name>")
+async def selected(
+ session: web.Committer, project_name: str, version_name: str
+) -> tuple[quart_response.Response, int] | response.Response | str:
+ """Finish a release preview."""
+ return await shared.finish.selected(session, project_name, version_name)
diff --git a/atr/post/ignores.py b/atr/post/ignores.py
new file mode 100644
index 0000000..647da04
--- /dev/null
+++ b/atr/post/ignores.py
@@ -0,0 +1,121 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+import quart
+import werkzeug.wrappers.response as response
+
+import atr.blueprints.post as post
+import atr.get as get
+import atr.models.sql as sql
+import atr.shared as shared
+import atr.storage as storage
+import atr.web as web
+
+
[email protected]("/ignores/<committee_name>/add")
+async def ignores_committee_add(session: web.Committer, committee_name: str)
-> str | response.Response:
+ data = await quart.request.form
+ form = await shared.ignores.AddIgnoreForm.create_form(data=data)
+ if not (await form.validate_on_submit()):
+ return await session.redirect(get.ignores.ignores, error="Form
validation errors")
+
+ status = sql.CheckResultStatusIgnore.from_form_field(form.status.data)
+
+ async with storage.write() as write:
+ wacm = write.as_committee_member(committee_name)
+ await wacm.checks.ignore_add(
+ release_glob=form.release_glob.data or None,
+ revision_number=form.revision_number.data or None,
+ checker_glob=form.checker_glob.data or None,
+ primary_rel_path_glob=form.primary_rel_path_glob.data or None,
+ member_rel_path_glob=form.member_rel_path_glob.data or None,
+ status=status,
+ message_glob=form.message_glob.data or None,
+ )
+
+ return await session.redirect(
+ get.ignores.ignores,
+ committee_name=committee_name,
+ success="Ignore added",
+ )
+
+
[email protected]("/ignores/<committee_name>/delete")
+async def ignores_committee_delete(session: web.Committer, committee_name:
str) -> str | response.Response:
+ data = await quart.request.form
+ form = await shared.ignores.DeleteIgnoreForm.create_form(data=data)
+ if not (await form.validate_on_submit()):
+ return await session.redirect(
+ get.ignores.ignores,
+ committee_name=committee_name,
+ error="Form validation errors",
+ )
+
+ if not isinstance(form.id.data, str):
+ return await session.redirect(
+ get.ignores.ignores,
+ committee_name=committee_name,
+ error="Invalid ignore ID",
+ )
+
+ cri_id = int(form.id.data)
+ async with storage.write() as write:
+ wacm = write.as_committee_member(committee_name)
+ await wacm.checks.ignore_delete(id=cri_id)
+
+ return await session.redirect(
+ get.ignores.ignores,
+ committee_name=committee_name,
+ success="Ignore deleted",
+ )
+
+
[email protected]("/ignores/<committee_name>/update")
+async def ignores_committee_update(session: web.Committer, committee_name:
str) -> str | response.Response:
+ data = await quart.request.form
+ form = await shared.ignores.UpdateIgnoreForm.create_form(data=data)
+ if not (await form.validate_on_submit()):
+ return await session.redirect(get.ignores.ignores, error="Form
validation errors")
+
+ status = sql.CheckResultStatusIgnore.from_form_field(form.status.data)
+ if not isinstance(form.id.data, str):
+ return await session.redirect(
+ get.ignores.ignores,
+ committee_name=committee_name,
+ error="Invalid ignore ID",
+ )
+ cri_id = int(form.id.data)
+
+ async with storage.write() as write:
+ wacm = write.as_committee_member(committee_name)
+ await wacm.checks.ignore_update(
+ id=cri_id,
+ release_glob=form.release_glob.data or None,
+ revision_number=form.revision_number.data or None,
+ checker_glob=form.checker_glob.data or None,
+ primary_rel_path_glob=form.primary_rel_path_glob.data or None,
+ member_rel_path_glob=form.member_rel_path_glob.data or None,
+ status=status,
+ message_glob=form.message_glob.data or None,
+ )
+
+ return await session.redirect(
+ get.ignores.ignores,
+ committee_name=committee_name,
+ success="Ignore updated",
+ )
diff --git a/atr/routes/__init__.py b/atr/routes/__init__.py
index 4ab4b88..a3c6e18 100644
--- a/atr/routes/__init__.py
+++ b/atr/routes/__init__.py
@@ -15,9 +15,6 @@
# specific language governing permissions and limitations
# under the License.
-import atr.routes.file as file
-import atr.routes.finish as finish
-import atr.routes.ignores as ignores
import atr.routes.keys as keys
import atr.routes.preview as preview
import atr.routes.projects as projects
@@ -36,9 +33,6 @@ import atr.routes.user as user
import atr.routes.voting as voting
__all__ = [
- "file",
- "finish",
- "ignores",
"keys",
"preview",
"projects",
diff --git a/atr/routes/ignores.py b/atr/routes/ignores.py
deleted file mode 100644
index 4240e92..0000000
--- a/atr/routes/ignores.py
+++ /dev/null
@@ -1,272 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from typing import Final
-
-import markupsafe
-import quart
-import werkzeug.wrappers.response as response
-import wtforms
-
-import atr.forms as forms
-import atr.htm as htm
-import atr.models.sql as sql
-import atr.route as route
-import atr.storage as storage
-import atr.template as template
-import atr.util as util
-
-# TODO: Port to TypeScript and move to static files
-_UPDATE_IGNORE_FORM: Final[str] = """
-document.querySelectorAll("table.page-details
input.form-control").forEach(function (input) {
- var row = input.closest("tr");
- var updateBtn = row.querySelector("button.btn-primary");
- function check() {
- if (input.value !== input.dataset.value) {
- updateBtn.classList.remove("disabled");
- } else {
- updateBtn.classList.add("disabled");
- }
- }
- input.addEventListener("input", check);
- check();
-});
-"""
-
-
-class AddIgnoreForm(forms.Typed):
- # TODO: Validate that at least one field is set
- 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_glob = forms.optional("Message pattern")
- submit = forms.submit("Add ignore")
-
-
-class DeleteIgnoreForm(forms.Typed):
- id = forms.hidden()
- submit = forms.submit("Delete")
-
-
-class UpdateIgnoreForm(forms.Typed):
- # TODO: Validate that at least one field is set
- id = forms.hidden()
- 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_glob = forms.optional("Message pattern")
- submit = forms.submit("Update ignore")
-
-
[email protected]("/ignores/<committee_name>", methods=["GET", "POST"])
-async def ignores(session: route.CommitterSession, committee_name: str) -> str
| response.Response:
- async with storage.read() as read:
- ragp = read.as_general_public()
- ignores = await ragp.checks.ignores(committee_name)
-
- content = htm.div[
- htm.h1["Ignored checks"],
- htm.p[f"Manage ignored checks for committee {committee_name}."],
- _add_ignore(committee_name),
- _existing_ignores(ignores),
- _script_dom_loaded(_UPDATE_IGNORE_FORM),
- ]
-
- return await template.blank("Ignored checks", content)
-
-
[email protected]("/ignores/<committee_name>/add", methods=["POST"])
-async def ignores_committee_add(session: route.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")
-
- status = sql.CheckResultStatusIgnore.from_form_field(form.status.data)
-
- async with storage.write() as write:
- wacm = write.as_committee_member(committee_name)
- await wacm.checks.ignore_add(
- release_glob=form.release_glob.data or None,
- revision_number=form.revision_number.data or None,
- checker_glob=form.checker_glob.data or None,
- primary_rel_path_glob=form.primary_rel_path_glob.data or None,
- member_rel_path_glob=form.member_rel_path_glob.data or None,
- status=status,
- message_glob=form.message_glob.data or None,
- )
-
- return await session.redirect(
- ignores,
- committee_name=committee_name,
- success="Ignore added",
- )
-
-
[email protected]("/ignores/<committee_name>/delete", methods=["POST"])
-async def ignores_committee_delete(session: route.CommitterSession,
committee_name: str) -> str | response.Response:
- data = await quart.request.form
- form = await DeleteIgnoreForm.create_form(data=data)
- if not (await form.validate_on_submit()):
- return await session.redirect(
- ignores,
- committee_name=committee_name,
- error="Form validation errors",
- )
-
- if not isinstance(form.id.data, str):
- return await session.redirect(
- ignores,
- committee_name=committee_name,
- error="Invalid ignore ID",
- )
-
- cri_id = int(form.id.data)
- async with storage.write() as write:
- wacm = write.as_committee_member(committee_name)
- await wacm.checks.ignore_delete(id=cri_id)
-
- return await session.redirect(
- ignores,
- committee_name=committee_name,
- success="Ignore deleted",
- )
-
-
[email protected]("/ignores/<committee_name>/update", methods=["POST"])
-async def ignores_committee_update(session: route.CommitterSession,
committee_name: str) -> str | response.Response:
- data = await quart.request.form
- form = await UpdateIgnoreForm.create_form(data=data)
- if not (await form.validate_on_submit()):
- return await session.redirect(ignores, error="Form validation errors")
-
- status = sql.CheckResultStatusIgnore.from_form_field(form.status.data)
- if not isinstance(form.id.data, str):
- return await session.redirect(
- ignores,
- committee_name=committee_name,
- error="Invalid ignore ID",
- )
- cri_id = int(form.id.data)
-
- async with storage.write() as write:
- wacm = write.as_committee_member(committee_name)
- await wacm.checks.ignore_update(
- id=cri_id,
- release_glob=form.release_glob.data or None,
- revision_number=form.revision_number.data or None,
- checker_glob=form.checker_glob.data or None,
- primary_rel_path_glob=form.primary_rel_path_glob.data or None,
- member_rel_path_glob=form.member_rel_path_glob.data or None,
- status=status,
- message_glob=form.message_glob.data or None,
- )
-
- return await session.redirect(
- ignores,
- committee_name=committee_name,
- success="Ignore updated",
- )
-
-
-def _check_result_ignore_card(cri: sql.CheckResultIgnore) -> htm.Element:
- h3_id = cri.id or ""
- h3_asf_uid = cri.asf_uid
- h3_created = util.format_datetime(cri.created)
- card_header_h3 = htm.h3(".mt-3.mb-0")[f"{h3_id} - {h3_asf_uid} -
{h3_created}"]
-
- form_update = UpdateIgnoreForm(id=cri.id)
-
- def set_field(field: wtforms.StringField | wtforms.SelectField, value: str
| None) -> None:
- if value is not None:
- field.data = value
-
- set_field(form_update.release_glob, cri.release_glob)
- set_field(form_update.revision_number, cri.revision_number)
- set_field(form_update.checker_glob, cri.checker_glob)
- set_field(form_update.primary_rel_path_glob, cri.primary_rel_path_glob)
- set_field(form_update.member_rel_path_glob, cri.member_rel_path_glob)
- set_field(form_update.status, cri.status.to_form_field() if cri.status
else "None")
- set_field(form_update.message_glob, cri.message_glob)
-
- form_path_update = util.as_url(ignores_committee_update,
committee_name=cri.committee_name)
- form_update_html = forms.render_table(form_update, form_path_update)
-
- form_delete = DeleteIgnoreForm(id=cri.id)
- form_path_delete = util.as_url(ignores_committee_delete,
committee_name=cri.committee_name)
- form_delete_html = forms.render_simple(
- form_delete,
- form_path_delete,
- form_classes=".mt-2.mb-0",
- submit_classes="btn-danger",
- )
-
- card = htm.div(".card.mb-5")[
- htm.div(".card-header.d-flex.justify-content-between")[card_header_h3,
form_delete_html],
- htm.div(".card-body")[form_update_html],
- ]
-
- return card
-
-
-def _add_ignore(committee_name: str) -> htm.Element:
- form_path = util.as_url(ignores_committee_add,
committee_name=committee_name)
- return htm.div[
- htm.h2["Add ignore"],
- htm.p["Add a new ignore for a check result."],
- forms.render_columns(AddIgnoreForm(), form_path),
- ]
-
-
-def _existing_ignores(ignores: list[sql.CheckResultIgnore]) -> htm.Element:
- return htm.div[
- htm.h2["Existing ignores"],
- [_check_result_ignore_card(cri) for cri in ignores] or htm.p["No
ignores found."],
- ]
-
-
-def _script_dom_loaded(text: str) -> htm.Element:
- script_text = markupsafe.Markup(f"""
-document.addEventListener("DOMContentLoaded", function () {{
-{text}
-}});
-""")
- return htm.script[script_text]
diff --git a/atr/routes/mapping.py b/atr/routes/mapping.py
index 5adf040..930f52d 100644
--- a/atr/routes/mapping.py
+++ b/atr/routes/mapping.py
@@ -22,7 +22,6 @@ import werkzeug.wrappers.response as response
import atr.get as get
import atr.models.sql as sql
import atr.route as route
-import atr.routes.finish as finish
import atr.routes.release as routes_release
import atr.util as util
import atr.web as web
@@ -45,7 +44,7 @@ def release_as_route(release: sql.Release) -> Callable:
case sql.ReleasePhase.RELEASE_CANDIDATE:
return get.vote.selected
case sql.ReleasePhase.RELEASE_PREVIEW:
- return finish.selected
+ return get.finish.selected
case sql.ReleasePhase.RELEASE:
return routes_release.finished
diff --git a/atr/routes/resolve.py b/atr/routes/resolve.py
index dede4be..dd9a6e4 100644
--- a/atr/routes/resolve.py
+++ b/atr/routes/resolve.py
@@ -21,11 +21,9 @@ import quart
import werkzeug.wrappers.response as response
import atr.forms as forms
-import atr.get.compose as compose
-import atr.get.vote as vote
+import atr.get as get
import atr.models.sql as sql
import atr.route as route
-import atr.routes.finish as finish
import atr.storage as storage
import atr.tabulate as tabulate
import atr.template as template
@@ -115,9 +113,9 @@ async def manual_selected_post(
async with storage.write_as_project_committee_member(project_name) as wacm:
success_message = await wacm.vote.resolve_manually(project_name,
release, vote_result)
if vote_result == "passed":
- destination = finish.selected
+ destination = get.finish.selected
else:
- destination = compose.selected
+ destination = get.compose.selected
return await session.redirect(
destination, project_name=project_name, version_name=version_name,
success=success_message
@@ -135,7 +133,7 @@ async def submit_selected(
if not (await resolve_form.validate_on_submit()):
# TODO: Render the page again with errors
return await session.redirect(
- vote.selected,
+ get.vote.selected,
project_name=project_name,
version_name=version_name,
error="Invalid form submission.",
@@ -155,11 +153,11 @@ async def submit_selected(
await quart.flash(error_message, "error")
if vote_result == "passed":
if voting_round == 1:
- destination = vote.selected
+ destination = get.vote.selected
else:
- destination = finish.selected
+ destination = get.finish.selected
else:
- destination = compose.selected
+ destination = get.compose.selected
return await session.redirect(
destination, project_name=project_name, version_name=version_name,
success=success_message
diff --git a/atr/shared/__init__.py b/atr/shared/__init__.py
index 352c86b..df2ffe0 100644
--- a/atr/shared/__init__.py
+++ b/atr/shared/__init__.py
@@ -28,6 +28,8 @@ import atr.models.sql as sql
import atr.shared.announce as announce
import atr.shared.distribution as distribution
import atr.shared.draft as draft
+import atr.shared.finish as finish
+import atr.shared.ignores as ignores
import atr.shared.vote as vote
import atr.storage as storage
import atr.template as template
@@ -177,5 +179,8 @@ __all__ = [
"announce",
"check",
"distribution",
+ "draft",
+ "finish",
+ "ignores",
"vote",
]
diff --git a/atr/shared/distribution.py b/atr/shared/distribution.py
index a584195..67b32fd 100644
--- a/atr/shared/distribution.py
+++ b/atr/shared/distribution.py
@@ -29,7 +29,6 @@ import atr.get as get
import atr.htm as htm
import atr.models.distribution as distribution
import atr.models.sql as sql
-import atr.routes.finish as finish
import atr.storage as storage
import atr.template as template
import atr.util as util
@@ -124,7 +123,7 @@ def html_nav_phase(block: htm.Block, project: str, version:
str, staging: bool)
label: Phase
route, label = (get.compose.selected, "COMPOSE")
if not staging:
- route, label = (finish.selected, "FINISH")
+ route, label = (get.finish.selected, "FINISH")
html_nav(
block,
util.as_url(
diff --git a/atr/routes/finish.py b/atr/shared/finish.py
similarity index 98%
rename from atr/routes/finish.py
rename to atr/shared/finish.py
index 3f60489..9cc227f 100644
--- a/atr/routes/finish.py
+++ b/atr/shared/finish.py
@@ -34,12 +34,12 @@ import atr.db as db
import atr.forms as forms
import atr.log as log
import atr.models.sql as sql
-import atr.route as route
import atr.routes.mapping as mapping
import atr.routes.root as root
import atr.storage as storage
import atr.template as template
import atr.util as util
+import atr.web as web
type Respond = Callable[[int, str], Awaitable[tuple[quart_response.Response,
int] | response.Response]]
@@ -81,7 +81,7 @@ class RemoveRCTagsForm(forms.Typed):
@dataclasses.dataclass
class ProcessFormDataArgs:
formdata: datastructures.MultiDict
- session: route.CommitterSession
+ session: web.Committer
project_name: str
version_name: str
move_form: MoveFileForm
@@ -99,9 +99,8 @@ class RCTagAnalysisResult:
total_paths: int
[email protected]("/finish/<project_name>/<version_name>", methods=["GET",
"POST"])
async def selected(
- session: route.CommitterSession, project_name: str, version_name: str
+ session: web.Committer, project_name: str, version_name: str
) -> tuple[quart_response.Response, int] | response.Response | str:
"""Finish a release preview."""
await session.check_access(project_name)
@@ -244,7 +243,7 @@ async def _deletable_choices(latest_revision_dir:
pathlib.Path, target_dirs: set
async def _delete_empty_directory(
dir_to_delete_rel: pathlib.Path,
- session: route.CommitterSession,
+ session: web.Committer,
project_name: str,
version_name: str,
respond: Respond,
@@ -265,7 +264,7 @@ async def _delete_empty_directory(
async def _move_file_to_revision(
source_files_rel: list[pathlib.Path],
target_dir_rel: pathlib.Path,
- session: route.CommitterSession,
+ session: web.Committer,
project_name: str,
version_name: str,
respond: Respond,
@@ -306,7 +305,7 @@ async def _move_file_to_revision(
async def _remove_rc_tags(
- session: route.CommitterSession,
+ session: web.Committer,
project_name: str,
version_name: str,
respond: Respond,
diff --git a/atr/shared/ignores.py b/atr/shared/ignores.py
new file mode 100644
index 0000000..c06632e
--- /dev/null
+++ b/atr/shared/ignores.py
@@ -0,0 +1,68 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+import atr.forms as forms
+import atr.models.sql as sql
+
+
+class AddIgnoreForm(forms.Typed):
+ # TODO: Validate that at least one field is set
+ 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_glob = forms.optional("Message pattern")
+ submit = forms.submit("Add ignore")
+
+
+class DeleteIgnoreForm(forms.Typed):
+ id = forms.hidden()
+ submit = forms.submit("Delete")
+
+
+class UpdateIgnoreForm(forms.Typed):
+ # TODO: Validate that at least one field is set
+ id = forms.hidden()
+ 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_glob = forms.optional("Message pattern")
+ submit = forms.submit("Update ignore")
diff --git a/atr/templates/announce-selected.html
b/atr/templates/announce-selected.html
index d868edb..0627bb7 100644
--- a/atr/templates/announce-selected.html
+++ b/atr/templates/announce-selected.html
@@ -25,7 +25,7 @@
{% block content %}
<p class="d-flex justify-content-between align-items-center">
- <a href="{{ as_url(routes.finish.selected,
project_name=release.project.name, version_name=release.version) }}"
+ <a href="{{ as_url(get.finish.selected, project_name=release.project.name,
version_name=release.version) }}"
class="atr-back-link">← Back to Finish {{ release.short_display_name
}}</a>
<span>
<span class="atr-phase-symbol-other">①</span>
diff --git a/atr/templates/check-selected-path-table.html
b/atr/templates/check-selected-path-table.html
index b67368b..b2375b7 100644
--- a/atr/templates/check-selected-path-table.html
+++ b/atr/templates/check-selected-path-table.html
@@ -44,7 +44,7 @@
{% endif %}
</td>
<td class="py-2">
- <a href="{{ as_url(routes.file.selected_path,
project_name=project_name, version_name=version_name, file_path=path) }}"
+ <a href="{{ as_url(get.file.selected_path,
project_name=project_name, version_name=version_name, file_path=path) }}"
title="View file {{ path }}"
class="text-decoration-none text-reset">
{% if has_errors or has_warnings %}
diff --git a/atr/templates/check-selected.html
b/atr/templates/check-selected.html
index 37567ee..d229cd0 100644
--- a/atr/templates/check-selected.html
+++ b/atr/templates/check-selected.html
@@ -162,7 +162,7 @@
<p>No ignored checks found.</p>
{% endif %}
<p>
- You can also <a href="{{ as_url(routes.ignores.ignores,
committee_name=release.committee.name) }}">manage which check results are
ignored</a>.
+ You can also <a href="{{ as_url(get.ignores.ignores,
committee_name=release.committee.name) }}">manage which check results are
ignored</a>.
</p>
<h3 id="debugging" class="mt-4">Debugging</h3>
diff --git a/atr/templates/phase-view.html b/atr/templates/phase-view.html
index 61ff04f..486bd9c 100644
--- a/atr/templates/phase-view.html
+++ b/atr/templates/phase-view.html
@@ -34,7 +34,7 @@
<span class="atr-phase-symbol-other">③</span>
</span>
{% elif phase_key == "preview" %}
- <a href="{{ as_url(routes.finish.selected,
project_name=release.project.name, version_name=release.version) }}"
+ <a href="{{ as_url(get.finish.selected,
project_name=release.project.name, version_name=release.version) }}"
class="atr-back-link">← Back to Finish {{ release.short_display_name
}}</a>
<span>
<span class="atr-phase-symbol-other">①</span>
@@ -99,7 +99,7 @@
<td>
{% if stat.is_file %}
{% if phase_key == "draft" %}
- {% set file_url = as_url(routes.file.selected_path,
project_name=release.project.name, version_name=release.version,
file_path=stat.path) %}
+ {% set file_url = as_url(get.file.selected_path,
project_name=release.project.name, version_name=release.version,
file_path=stat.path) %}
{% elif phase_key == "candidate" %}
{% set file_url = as_url(get.candidate.view_path,
project_name=release.project.name, version_name=release.version,
file_path=stat.path) %}
{% elif phase_key == "preview" %}
diff --git a/atr/templates/revisions-selected.html
b/atr/templates/revisions-selected.html
index 0dddf84..8ba4049 100644
--- a/atr/templates/revisions-selected.html
+++ b/atr/templates/revisions-selected.html
@@ -22,7 +22,7 @@
<span class="atr-phase-symbol-other">③</span>
</span>
{% elif phase_key == "preview" %}
- <a href="{{ as_url(routes.finish.selected,
project_name=release.project.name, version_name=release.version) }}"
+ <a href="{{ as_url(get.finish.selected,
project_name=release.project.name, version_name=release.version) }}"
class="atr-back-link">← Back to Finish {{ release.short_display_name
}}</a>
<span>
<span class="atr-phase-symbol-other">①</span>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]