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]

Reply via email to