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 f0f0623 Make the vote report form promote releases
f0f0623 is described below
commit f0f0623dda714e286bf3bf18fae0f9940ec2ef6f
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Jun 27 20:45:58 2025 +0100
Make the vote report form promote releases
---
atr/routes/resolve.py | 95 ++++++++++++++++++++++++++--------------
atr/routes/vote.py | 8 +---
atr/templates/vote-tabulate.html | 10 +++--
3 files changed, 71 insertions(+), 42 deletions(-)
diff --git a/atr/routes/resolve.py b/atr/routes/resolve.py
index b862583..3e91633 100644
--- a/atr/routes/resolve.py
+++ b/atr/routes/resolve.py
@@ -97,42 +97,36 @@ async def selected_post(
vote.selected, error="Invalid candidate name",
project_name=project_name, version_name=version_name
)
- # Check that the user has access to the project
- await session.check_access(project_name)
-
- # Update release status in the database
- async with db.session() as data:
- async with data.begin():
- release = await session.release(
- project_name,
- version_name,
- with_project=True,
- phase=models.ReleasePhase.RELEASE_CANDIDATE,
- data=data,
- )
-
- # Update the release phase based on vote result
- if vote_result == "passed":
- release.phase = models.ReleasePhase.RELEASE_PREVIEW
- success_message = "Vote marked as passed"
- destination = finish.selected
- else:
- release.phase = models.ReleasePhase.RELEASE_CANDIDATE_DRAFT
- success_message = "Vote marked as failed"
- destination = compose.selected
+ release, success_message = await _resolve_vote(session, project_name,
version_name, vote_result, resolution_body)
+ destination = finish.selected if (vote_result == "passed") else
compose.selected
- description = "Create a preview revision from the last candidate draft"
- async with revision.create_and_manage(
- project_name, release.version, session.uid, description=description
- ) as _creating:
- pass
+ return await session.redirect(
+ destination, success=success_message, project_name=project_name,
version_name=release.version
+ )
- error_message = await _send_resolution(session, release, vote_result,
resolution_body)
- if error_message is not None:
- await quart.flash(error_message, "error")
[email protected]("/resolve/<project_name>/<version_name>/report",
methods=["POST"])
+async def selected_report(
+ session: routes.CommitterSession, project_name: str, version_name: str
+) -> response.Response | str:
+ """Resolve a vote."""
+ await session.check_access(project_name)
+ release = await session.release(project_name, version_name,
phase=models.ReleasePhase.RELEASE_CANDIDATE)
+ latest_vote_task = await release_latest_vote_task(release)
+ if latest_vote_task is None:
+ return "No vote task found, unable to send resolution message."
+ resolve_form = await vote.ResolveVoteForm.create_form()
+ if not resolve_form.validate_on_submit():
+ # TODO: Render the page again with errors
+ return await session.redirect(
+ vote.tabulate, project_name=project_name,
version_name=version_name, error="Invalid form submission."
+ )
+ email_body = util.unwrap(resolve_form.email_body.data)
+ vote_result = util.unwrap(resolve_form.vote_result.data)
+ release, success_message = await _resolve_vote(session, project_name,
version_name, vote_result, email_body)
+ destination = finish.selected if (vote_result == "passed") else
compose.selected
return await session.redirect(
- destination, success=success_message, project_name=project_name,
version_name=release.version
+ destination, project_name=project_name, version_name=version_name,
success=success_message
)
@@ -161,6 +155,43 @@ def task_mid_get(latest_vote_task: models.Task) -> str |
None:
return task_mid
+async def _resolve_vote(
+ session: routes.CommitterSession, project_name: str, version_name: str,
vote_result: str, resolution_body: str
+) -> tuple[models.Release, str]:
+ # Check that the user has access to the project
+ await session.check_access(project_name)
+
+ # Update release status in the database
+ async with db.session() as data:
+ async with data.begin():
+ release = await session.release(
+ project_name,
+ version_name,
+ with_project=True,
+ phase=models.ReleasePhase.RELEASE_CANDIDATE,
+ data=data,
+ )
+
+ # Update the release phase based on vote result
+ if vote_result == "passed":
+ release.phase = models.ReleasePhase.RELEASE_PREVIEW
+ success_message = "Vote marked as passed"
+
+ description = "Create a preview revision from the last
candidate draft"
+ async with revision.create_and_manage(
+ project_name, release.version, session.uid,
description=description
+ ) as _creating:
+ pass
+ else:
+ release.phase = models.ReleasePhase.RELEASE_CANDIDATE_DRAFT
+ success_message = "Vote marked as failed"
+
+ error_message = await _send_resolution(session, release, vote_result,
resolution_body)
+ if error_message is not None:
+ await quart.flash(error_message, "error")
+ return release, success_message
+
+
async def _send_resolution(
session: routes.CommitterSession,
release: models.Release,
diff --git a/atr/routes/vote.py b/atr/routes/vote.py
index 461a613..eb7dc52 100644
--- a/atr/routes/vote.py
+++ b/atr/routes/vote.py
@@ -53,8 +53,8 @@ class CastVoteForm(util.QuartFormTyped):
class ResolveVoteForm(util.QuartFormTyped):
"""Form for resolving a vote."""
- # email_to = wtforms.HiddenField("email_to")
email_body = wtforms.TextAreaField("Email body")
+ vote_result = wtforms.HiddenField("Vote result")
submit = wtforms.SubmitField("Resolve vote")
@@ -181,17 +181,13 @@ async def tabulate(session: routes.CommitterSession,
project_name: str, version_
summary = _tabulate_vote_summary(tabulated_votes)
passed, outcome = _tabulate_vote_outcome(release, start_unixtime,
tabulated_votes)
resolve_form = await ResolveVoteForm.create_form()
- # latest_vote_task = await resolve.release_latest_vote_task(release)
- # if latest_vote_task is None:
- # resolve_form = None
- # else:
- # resolve_form.email_to.data = latest_vote_task.task_args["email_to"]
if (committee is None) or (tabulated_votes is None) or (summary is None)
or (passed is None) or (outcome is None):
resolve_form = None
else:
resolve_form.email_body.data = _tabulate_vote_resolution(
committee, release, tabulated_votes, summary, passed, outcome,
full_name, asf_uid
)
+ resolve_form.vote_result.data = "passed" if passed else "failed"
return await template.render(
"vote-tabulate.html",
release=release,
diff --git a/atr/templates/vote-tabulate.html b/atr/templates/vote-tabulate.html
index cb87e8b..c107b8b 100644
--- a/atr/templates/vote-tabulate.html
+++ b/atr/templates/vote-tabulate.html
@@ -100,13 +100,15 @@
If, after careful manual review of the information above, you concur
with the automatically determined outcome of the vote, please enter the
resolution email body here. Sending this will send the email to a new vote
result thread, and the vote will be resolved.
</p>
<form class="atr-canary py-3 px-4 mb-4 border rounded"
- action="{{ as_url(routes.root.todo) }}"
+ action="{{ as_url(routes.resolve.selected_report,
project_name=release.project.name, version_name=release.version) }}"
method="post">
{{ forms.errors_summary(resolve_form) }}
{{ resolve_form.hidden_tag() }}
- {{ forms.label(resolve_form.email_body) }}
- {{ forms.widget(resolve_form.email_body, rows=24) }}
- {{ forms.errors(resolve_form.email_body, classes="invalid-feedback
d-block") }}
+ <div class="form-group">
+ {{ forms.label(resolve_form.email_body) }}
+ {{ forms.widget(resolve_form.email_body, rows=24) }}
+ {{ forms.errors(resolve_form.email_body, classes="invalid-feedback
d-block") }}
+ </div>
{{ resolve_form.submit(class="btn btn-primary mt-2") }}
</form>
{% else %}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]