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 dee03da Add a release policy form to project pages, and fix policy
defaults
dee03da is described below
commit dee03daa4f8176854deac52fcfb50e93f32812ab
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu May 29 16:08:27 2025 +0100
Add a release policy form to project pages, and fix policy defaults
---
atr/db/models.py | 18 +++---
atr/routes/projects.py | 47 ++++++++++++++-
atr/templates/project-view.html | 126 +++++++++++++++++++++++++++++++---------
3 files changed, 153 insertions(+), 38 deletions(-)
diff --git a/atr/db/models.py b/atr/db/models.py
index e22b26a..683cfea 100644
--- a/atr/db/models.py
+++ b/atr/db/models.py
@@ -315,43 +315,47 @@ Thanks,
@property
def policy_announce_release_template(self) -> str:
- if ((policy := self.release_policy) is None) or
(policy.announce_release_template is None):
+ if ((policy := self.release_policy) is None) or
(policy.announce_release_template == ""):
return self.policy_announce_release_default
return policy.announce_release_template
@property
def policy_mailto_addresses(self) -> list[str]:
- if ((policy := self.release_policy) is None) or
(policy.mailto_addresses is None):
+ if ((policy := self.release_policy) is None) or (not
policy.mailto_addresses):
+ # TODO: We actually need the top level project name here, not the
project name
return [f"dev@{self.name}.apache.org"]
return policy.mailto_addresses
@property
def policy_manual_vote(self) -> bool:
- if ((policy := self.release_policy) is None) or (policy.manual_vote is
None):
+ if (policy := self.release_policy) is None:
return False
return policy.manual_vote
@property
def policy_min_hours(self) -> int:
- if ((policy := self.release_policy) is None) or (policy.min_hours is
None):
+ if ((policy := self.release_policy) is None) or (policy.min_hours ==
0):
+ # Not sure what the default should be
+ # Also, we can't use 0 as "default" because it's also "unlimited"
+ # This suggests that we make min_hours nullable and use None for
the default value
return 72
return policy.min_hours
@property
def policy_pause_for_rm(self) -> bool:
- if ((policy := self.release_policy) is None) or (policy.pause_for_rm
is None):
+ if (policy := self.release_policy) is None:
return False
return policy.pause_for_rm
@property
def policy_release_checklist(self) -> str:
- if ((policy := self.release_policy) is None) or
(policy.release_checklist is None):
+ if ((policy := self.release_policy) is None) or
(policy.release_checklist == ""):
return ""
return policy.release_checklist
@property
def policy_start_vote_template(self) -> str:
- if ((policy := self.release_policy) is None) or
(policy.start_vote_template is None):
+ if ((policy := self.release_policy) is None) or
(policy.start_vote_template == ""):
return self.policy_start_vote_default
return policy.start_vote_template
diff --git a/atr/routes/projects.py b/atr/routes/projects.py
index 1e04207..3bc00d3 100644
--- a/atr/routes/projects.py
+++ b/atr/routes/projects.py
@@ -57,7 +57,7 @@ class ReleasePolicyForm(util.QuartFormTyped):
wtforms.validators.InputRequired("Please provide a valid email
address"),
wtforms.validators.Email(),
],
- render_kw={"size": 30},
+ render_kw={"size": 30, "placeholder": "E.g.
[email protected]"},
description="Note: This field determines where vote and finished
release announcement emails are sent."
" You can set this value to your own mailing list, but ATR will
currently only let you send to"
" [email protected].",
@@ -275,12 +275,51 @@ async def select(session: routes.CommitterSession) -> str:
return await template.render("project-select.html",
user_projects=user_projects)
[email protected]("/projects/<name>")
-async def view(name: str) -> str:
[email protected]("/projects/<name>", methods=["GET", "POST"])
+async def view(session: routes.CommitterSession, name: str) ->
response.Response | str:
+ form = None
+ can_edit_policy = False
async with db.session() as data:
project = await data.project(name=name,
_committee_public_signing_keys=True, _release_policy=True).demand(
http.client.HTTPException(404)
)
+
+ if project.committee and session.uid:
+ can_edit_policy = user.is_committee_member(project.committee,
session.uid) or user.is_admin(session.uid)
+
+ if can_edit_policy:
+ if quart.request.method == "POST":
+ form = await ReleasePolicyForm.create_form(data=await
quart.request.form)
+ if await form.validate_on_submit():
+ if project.release_policy is None:
+ project.release_policy =
models.ReleasePolicy(project=project)
+ data.add(project.release_policy)
+
+ project.release_policy.mailto_addresses =
[util.unwrap(form.mailto_addresses.entries[0].data)]
+ project.release_policy.manual_vote =
util.unwrap(form.manual_vote.data)
+ project.release_policy.min_hours =
util.unwrap(form.min_hours.data)
+ project.release_policy.release_checklist =
util.unwrap(form.release_checklist.data)
+ project.release_policy.start_vote_template =
util.unwrap(form.start_vote_template.data)
+ project.release_policy.announce_release_template =
util.unwrap(form.announce_release_template.data)
+ project.release_policy.pause_for_rm =
util.unwrap(form.pause_for_rm.data)
+ await data.commit()
+ await quart.flash("Release policy updated successfully.",
"success")
+ return quart.redirect(util.as_url(view, name=project.name))
+
+ if form is None:
+ form = await ReleasePolicyForm.create_form()
+ form.project_name.data = project.name
+ if project.policy_mailto_addresses:
+ form.mailto_addresses.entries[0].data =
project.policy_mailto_addresses[0]
+ else:
+ form.mailto_addresses.entries[0].data =
f"dev@{project.name}.apache.org"
+ form.min_hours.data = project.policy_min_hours
+ form.manual_vote.data = project.policy_manual_vote
+ form.release_checklist.data = project.policy_release_checklist
+ form.start_vote_template.data =
project.policy_start_vote_template
+ form.announce_release_template.data =
project.policy_announce_release_template
+ form.pause_for_rm.data = project.policy_pause_for_rm
+
return await template.render(
"project-view.html",
project=project,
@@ -292,6 +331,8 @@ async def view(name: str) -> str:
number_of_release_files=util.number_of_release_files,
now=datetime.datetime.now(datetime.UTC),
empty_form=await util.EmptyForm.create_form(),
+ form=form,
+ can_edit_policy=can_edit_policy,
)
diff --git a/atr/templates/project-view.html b/atr/templates/project-view.html
index e114661..32b93f7 100644
--- a/atr/templates/project-view.html
+++ b/atr/templates/project-view.html
@@ -92,23 +92,90 @@
<div class="card mb-4">
<div class="card-header bg-light d-flex justify-content-between
align-items-center">
<h3 class="mb-0">Release policy</h3>
- {% if not project.is_retired %}
- {% if project.release_policy and (is_committee_member or is_admin) %}
- <div>
- <a class="btn btn-primary btn-sm"
- href="{{ as_url(routes.projects.release_policy_edit,
project_name=project.name) }}"><i class="bi bi-pencil-square"></i></a>
- </div>
- {% elif (is_committee_member or is_admin) %}
- <div>
- <a class="btn btn-primary btn-sm"
- href="{{ as_url(routes.projects.release_policy_add,
project_name=project.name) }}"><i class="bi bi-plus"></i></a>
- </div>
- {% endif %}
- {% endif %}
</div>
<div class="card-body">
- {% if project.release_policy %}
- {% set rp = project.release_policy %}
+ {% if can_edit_policy and form %}
+ <form method="post"
+ action="{{ as_url(routes.projects.view, name=project.name) }}"
+ class="atr-canary py-4 px-5"
+ novalidate>
+ {{ form.hidden_tag() if form.hidden_tag }}
+ {{ form.project_name(value=project.name) }}
+
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(form.mailto_addresses.entries[0], col="md3") }}
+ <div class="col-sm-8">
+ {{ forms.widget(form.mailto_addresses.entries[0]) }}
+ {{ forms.errors(form.mailto_addresses.entries[0]) }}
+ {{ forms.description(form.mailto_addresses.entries[0]) }}
+ </div>
+ </div>
+
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(form.manual_vote, col="md3-high") }}
+ <div class="col-sm-8">
+ <div class="form-check">
+ {{ forms.widget(form.manual_vote, classes="form-check-input")
}}
+ {{ forms.errors(form.manual_vote, classes="invalid-feedback
d-block") }}
+ <label class="form-check-label" for="{{ form.manual_vote.id
}}">{{ form.manual_vote.label.text }}</label>
+ </div>
+ {{ forms.description(form.manual_vote) }}
+ </div>
+ </div>
+
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(form.min_hours, col="md3") }}
+ <div class="col-sm-8">
+ {{ forms.widget(form.min_hours) }}
+ {{ forms.errors(form.min_hours) }}
+ {{ forms.description(form.min_hours) }}
+ </div>
+ </div>
+
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(form.release_checklist, col="md3") }}
+ <div class="col-sm-8">
+ {{ forms.widget(form.release_checklist, rows="10",
classes="form-control font-monospace") }}
+ {{ forms.errors(form.release_checklist) }}
+ {{ forms.description(form.release_checklist) }}
+ </div>
+ </div>
+
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(form.start_vote_template, col="md3") }}
+ <div class="col-sm-8">
+ {{ forms.widget(form.start_vote_template, rows="10",
classes="form-control font-monospace") }}
+ {{ forms.errors(form.start_vote_template) }}
+ {{ forms.description(form.start_vote_template) }}
+ </div>
+ </div>
+
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(form.announce_release_template, col="md3") }}
+ <div class="col-sm-8">
+ {{ forms.widget(form.announce_release_template, rows="10",
classes="form-control font-monospace") }}
+ {{ forms.errors(form.announce_release_template) }}
+ {{ forms.description(form.announce_release_template) }}
+ </div>
+ </div>
+
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(form.pause_for_rm, col="md3-high") }}
+ <div class="col-sm-8">
+ <div class="form-check">
+ {{ forms.widget(form.pause_for_rm, classes="form-check-input")
}}
+ {{ forms.errors(form.pause_for_rm, classes="invalid-feedback
d-block") }}
+ <label class="form-check-label" for="{{ form.pause_for_rm.id
}}">{{ form.pause_for_rm.label.text }}</label>
+ </div>
+ {{ forms.description(form.pause_for_rm) }}
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-sm-9 offset-sm-3">{{ form.submit(class_="btn
btn-primary mt-2") }}</div>
+ </div>
+ </form>
+ {% elif project.release_policy or project.name %}
<div class="card h-100 border">
<div class="card-body">
<table class="table mb-0">
@@ -116,22 +183,26 @@
<tr>
<th class="border-0 w-25">Email</th>
<td class="text-break border-0">
- <a href="mailto:{{ vp.mailto_addresses[0] }}">{{
vp.mailto_addresses[0] }}</a>
+ {% if project.policy_mailto_addresses %}
+ <a href="mailto:{{ project.policy_mailto_addresses[0]
}}">{{ project.policy_mailto_addresses[0] }}</a>
+ {% else %}
+ Not set
+ {% endif %}
</td>
</tr>
<tr>
<th class="border-0">Manual vote process</th>
- <td class="text-break border-0">{{ vp.manual_vote }}</td>
+ <td class="text-break border-0">{{
project.policy_manual_vote }}</td>
</tr>
<tr>
<th class="border-0">Minimum voting period</th>
- <td class="text-break border-0">{{ vp.min_hours }}h</td>
+ <td class="text-break border-0">{{ project.policy_min_hours
}}h</td>
</tr>
<tr>
<th class="border-0">Release checklist</th>
<td class="text-break border-0">
- {% if vp.release_checklist|length > 0 %}
- <textarea readonly class="form-control font-monospace"
rows="10">{{ vp.release_checklist }}</textarea>
+ {% if project.policy_release_checklist|length > 0 %}
+ <textarea readonly class="form-control font-monospace"
rows="10">{{ project.policy_release_checklist }}</textarea>
{% else %}
None
{% endif %}
@@ -140,28 +211,27 @@
<tr>
<th class="border-0">Start vote template</th>
<td class="text-break border-0">
- {% if vp.start_vote_template|length > 0 %}
- <textarea readonly class="form-control font-monospace"
rows="10">{{ vp.start_vote_template }}</textarea>
+ {% if project.policy_start_vote_template|length > 0 %}
+ <textarea readonly class="form-control font-monospace"
rows="10">{{ project.policy_start_vote_template }}</textarea>
{% else %}
- None
+ None (System default will be used)
{% endif %}
</td>
</tr>
<tr>
<th class="border-0">Announce release template</th>
<td class="text-break border-0">
- {% if vp.announce_release_template|length > 0 %}
- <textarea readonly class="form-control font-monospace"
rows="10">{{ vp.announce_release_template }}</textarea>
+ {% if project.policy_announce_release_template|length > 0
%}
+ <textarea readonly class="form-control font-monospace"
rows="10">{{ project.policy_announce_release_template }}</textarea>
{% else %}
- None
+ None (System default will be used)
{% endif %}
</td>
</tr>
<tr>
<th class="border-0">Pause for RM</th>
- <td class="text-break border-0">{{ vp.pause_for_rm }}</td>
+ <td class="text-break border-0">{{
project.policy_pause_for_rm }}</td>
</tr>
-
</tbody>
</table>
</div>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]