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 e388eaa Document the available variables in the vote and announce
forms
e388eaa is described below
commit e388eaad0a25932dfdd3ec1a2e8c24f91b988209
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Dec 4 16:27:44 2025 +0000
Document the available variables in the vote and announce forms
---
atr/construct.py | 27 ++++++++++
atr/get/announce.py | 49 ++----------------
atr/get/voting.py | 51 +++----------------
atr/render.py | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 180 insertions(+), 89 deletions(-)
diff --git a/atr/construct.py b/atr/construct.py
index 7d3dfee..3a4d389 100644
--- a/atr/construct.py
+++ b/atr/construct.py
@@ -16,6 +16,7 @@
# under the License.
import dataclasses
+from typing import Literal
import aiofiles.os
import quart
@@ -26,6 +27,24 @@ import atr.db.interaction as interaction
import atr.models.sql as sql
import atr.util as util
+type Context = Literal["vote", "announce"]
+
+TEMPLATE_VARIABLES: list[tuple[str, str, set[Context]]] = [
+ ("COMMITTEE", "Committee display name", {"vote", "announce"}),
+ ("DOWNLOAD_URL", "URL to download the release", {"announce"}),
+ ("DURATION", "Vote duration in hours", {"vote"}),
+ ("KEYS_FILE", "URL to the KEYS file", {"vote"}),
+ ("PROJECT", "Project display name", {"vote", "announce"}),
+ ("RELEASE_CHECKLIST", "Release checklist content", {"vote"}),
+ ("REVIEW_URL", "URL to review the release", {"vote"}),
+ ("REVISION", "Revision number", {"vote", "announce"}),
+ ("TAG", "Revision tag, if set", {"vote", "announce"}),
+ ("VERSION", "Version name", {"vote", "announce"}),
+ ("VOTE_ENDS_UTC", "Vote end date and time in UTC", {"vote"}),
+ ("YOUR_ASF_ID", "Your Apache UID", {"vote", "announce"}),
+ ("YOUR_FULL_NAME", "Your full name", {"vote", "announce"}),
+]
+
@dataclasses.dataclass
class AnnounceReleaseOptions:
@@ -100,6 +119,10 @@ async def announce_release_default(project_name: str) ->
str:
return project.policy_announce_release_template
+def announce_template_variables() -> list[tuple[str, str]]:
+ return [(name, desc) for (name, desc, contexts) in TEMPLATE_VARIABLES if
"announce" in contexts]
+
+
async def start_vote_body(body: str, options: StartVoteOptions) -> str:
async with db.session() as data:
# Do not limit by phase, as it may be at RELEASE_CANDIDATE here if
called by the task
@@ -166,3 +189,7 @@ async def start_vote_default(project_name: str) -> str:
)
return project.policy_start_vote_template
+
+
+def vote_template_variables() -> list[tuple[str, str]]:
+ return [(name, desc) for (name, desc, contexts) in TEMPLATE_VARIABLES if
"vote" in contexts]
diff --git a/atr/get/announce.py b/atr/get/announce.py
index b3ab3dd..b8034c3 100644
--- a/atr/get/announce.py
+++ b/atr/get/announce.py
@@ -27,6 +27,7 @@ import atr.form as form
import atr.htm as htm
import atr.models.sql as sql
import atr.post as post
+import atr.render as render
import atr.shared as shared
import atr.shared.distribution as distribution
import atr.template as template
@@ -168,49 +169,7 @@ def _render_release_card(release: sql.Release) ->
htm.Element:
def _render_body_tabs(default_body: str) -> htm.Element:
"""Render the tabbed interface for body editing and preview."""
-
- tabs_ul = htm.ul("#announceBodyTab.nav.nav-tabs", role="tablist")[
- htm.li(".nav-item", role="presentation")[
- htpy.button(
- "#edit-announce-body-tab.nav-link.active",
- data_bs_toggle="tab",
- data_bs_target="#edit-announce-body-pane",
- type="button",
- role="tab",
- aria_controls="edit-announce-body-pane",
- aria_selected="true",
- )["Edit"]
- ],
- htm.li(".nav-item", role="presentation")[
- htpy.button(
- "#text-preview-announce-body-tab.nav-link",
- data_bs_toggle="tab",
- data_bs_target="#text-preview-announce-body-pane",
- type="button",
- role="tab",
- aria_controls="text-preview-announce-body-pane",
- aria_selected="false",
- )["Text preview"]
- ],
- ]
-
- edit_pane = htm.div("#edit-announce-body-pane.tab-pane.fade.show.active",
role="tabpanel")[
- htpy.textarea(
- "#body.form-control.font-monospace.mt-2",
- name="body",
- rows="12",
- )[default_body]
- ]
-
- preview_pane = htm.div("#text-preview-announce-body-pane.tab-pane.fade",
role="tabpanel")[
-
htm.pre(".mt-2.p-3.bg-light.border.rounded.font-monospace.overflow-auto")[
- htm.code("#announce-text-preview-content")["Loading preview..."]
- ]
- ]
-
- tab_content = htm.div("#announceBodyTabContent.tab-content")[edit_pane,
preview_pane]
-
- return htm.div[tabs_ul, tab_content]
+ return render.body_tabs("announce-body", default_body,
construct.announce_template_variables())
def _render_mailing_list_with_warning(choices: list[tuple[str, str]],
default_value: str) -> htm.Element:
@@ -282,7 +241,7 @@ def _render_javascript(release: sql.Release,
download_path_description: str) ->
const debounceDelay = 500;
const bodyTextarea = document.getElementById("body");
- const textPreviewContent =
document.getElementById("announce-text-preview-content");
+ const textPreviewContent =
document.getElementById("announce-body-preview-content");
const announceForm = document.querySelector("form.atr-canary");
if (!bodyTextarea || !textPreviewContent || !announceForm) {{
@@ -337,6 +296,8 @@ def _render_javascript(release: sql.Release,
download_path_description: str) ->
fetchAndUpdateAnnouncePreview();
+ {render.copy_javascript()}
+
const pathInput = document.getElementById("download_path_suffix");
const pathHelpText = pathInput ?
pathInput.parentElement.querySelector(".form-text") : null;
diff --git a/atr/get/voting.py b/atr/get/voting.py
index 99d5855..0fff05b 100644
--- a/atr/get/voting.py
+++ b/atr/get/voting.py
@@ -30,6 +30,7 @@ import atr.get.keys as keys
import atr.htm as htm
import atr.models.sql as sql
import atr.post as post
+import atr.render as render
import atr.shared as shared
import atr.template as template
import atr.util as util
@@ -65,7 +66,7 @@ async def selected_revision(
revision_obj = await data.revision(release_name=release.name,
number=revision).get()
if revision_obj and revision_obj.tag:
- subject_suffix = f" (tag: {revision_obj.tag})"
+ subject_suffix = f" ({revision_obj.tag})"
else:
subject_suffix = f" (revision {revision})"
@@ -173,49 +174,7 @@ async def _render_page(
def _render_body_tabs(default_body: str) -> htm.Element:
"""Render the tabbed interface for body editing and preview."""
-
- tabs_ul = htm.ul("#voteBodyTab.nav.nav-tabs", role="tablist")[
- htm.li(".nav-item", role="presentation")[
- htpy.button(
- "#edit-vote-body-tab.nav-link.active",
- data_bs_toggle="tab",
- data_bs_target="#edit-vote-body-pane",
- type="button",
- role="tab",
- aria_controls="edit-vote-body-pane",
- aria_selected="true",
- )["Edit"]
- ],
- htm.li(".nav-item", role="presentation")[
- htpy.button(
- "#text-preview-vote-body-tab.nav-link",
- data_bs_toggle="tab",
- data_bs_target="#text-preview-vote-body-pane",
- type="button",
- role="tab",
- aria_controls="text-preview-vote-body-pane",
- aria_selected="false",
- )["Text preview"]
- ],
- ]
-
- edit_pane = htm.div("#edit-vote-body-pane.tab-pane.fade.show.active",
role="tabpanel")[
- htpy.textarea(
- "#body.form-control.font-monospace.mt-2",
- name="body",
- rows="12",
- )[default_body]
- ]
-
- preview_pane = htm.div("#text-preview-vote-body-pane.tab-pane.fade",
role="tabpanel")[
-
htm.pre(".mt-2.p-3.bg-light.border.rounded.font-monospace.overflow-auto")[
- htm.code("#vote-text-preview-content")["Loading preview..."]
- ]
- ]
-
- tab_content = htm.div("#voteBodyTabContent.tab-content")[edit_pane,
preview_pane]
-
- return htm.div[tabs_ul, tab_content]
+ return render.body_tabs("vote-body", default_body,
construct.vote_template_variables())
def _render_javascript(release, min_hours: int) -> htm.Element:
@@ -231,7 +190,7 @@ def _render_javascript(release, min_hours: int) ->
htm.Element:
const bodyTextarea = document.getElementById("body");
const voteDurationInput = document.getElementById("vote_duration");
- const textPreviewContent =
document.getElementById("vote-text-preview-content");
+ const textPreviewContent =
document.getElementById("vote-body-preview-content");
const voteForm = document.querySelector("form.atr-canary");
if (!bodyTextarea || !voteDurationInput || !textPreviewContent ||
!voteForm) {{
@@ -292,6 +251,8 @@ def _render_javascript(release, min_hours: int) ->
htm.Element:
}});
fetchAndUpdateVotePreview();
+
+ {render.copy_javascript()}
}});
"""
diff --git a/atr/render.py b/atr/render.py
new file mode 100644
index 0000000..9907e0f
--- /dev/null
+++ b/atr/render.py
@@ -0,0 +1,142 @@
+# 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 htpy
+
+import atr.htm as htm
+
+
+def body_tabs(
+ tab_id_prefix: str,
+ default_body: str,
+ template_variables: list[tuple[str, str]],
+) -> htm.Element:
+ tabs = htm.Block(htm.ul(f"#{tab_id_prefix}-tab.nav.nav-tabs",
role="tablist"))
+ tabs.li(".nav-item", role="presentation")[
+ htpy.button(
+ f"#edit-{tab_id_prefix}-tab.nav-link.active",
+ data_bs_toggle="tab",
+ data_bs_target=f"#edit-{tab_id_prefix}-pane",
+ type="button",
+ role="tab",
+ aria_controls=f"edit-{tab_id_prefix}-pane",
+ aria_selected="true",
+ )["Edit"]
+ ]
+ tabs.li(".nav-item", role="presentation")[
+ htpy.button(
+ f"#preview-{tab_id_prefix}-tab.nav-link",
+ data_bs_toggle="tab",
+ data_bs_target=f"#preview-{tab_id_prefix}-pane",
+ type="button",
+ role="tab",
+ aria_controls=f"preview-{tab_id_prefix}-pane",
+ aria_selected="false",
+ )["Text preview"]
+ ]
+ tabs.append(variables_tab_button(tab_id_prefix))
+
+ edit_pane =
htm.div(f"#edit-{tab_id_prefix}-pane.tab-pane.fade.show.active",
role="tabpanel")[
+ htpy.textarea(
+ "#body.form-control.font-monospace.mt-2",
+ name="body",
+ rows="12",
+ )[default_body]
+ ]
+
+ preview_pane = htm.div(f"#preview-{tab_id_prefix}-pane.tab-pane.fade",
role="tabpanel")[
+
htm.pre(".mt-2.p-3.bg-light.border.rounded.font-monospace.overflow-auto")[
+ htm.code(f"#{tab_id_prefix}-preview-content")["Loading preview..."]
+ ]
+ ]
+
+ variables_pane = variables_tab(tab_id_prefix, template_variables)
+
+ tab_content =
htm.div(f"#{tab_id_prefix}-tab-content.tab-content")[edit_pane, preview_pane,
variables_pane]
+
+ return htm.div[tabs.collect(), tab_content]
+
+
+def copy_javascript() -> str:
+ # TODO: We need to ensure that all JS files are standalone
+ # We have far too many embedded snippets
+ return """
+ document.querySelectorAll(".copy-var-btn").forEach(btn => {
+ btn.addEventListener("click", () => {
+ const variable = btn.dataset.variable;
+ navigator.clipboard.writeText(variable).then(() => {
+ const originalText = btn.textContent;
+ btn.textContent = "Copied!";
+ btn.classList.remove("btn-outline-secondary");
+ btn.classList.add("btn-success");
+ setTimeout(() => {
+ btn.textContent = originalText;
+ btn.classList.remove("btn-success");
+ btn.classList.add("btn-outline-secondary");
+ }, 1500);
+ });
+ });
+ });
+ """
+
+
+def variables_tab(
+ tab_id_prefix: str,
+ template_variables: list[tuple[str, str]],
+) -> htm.Element:
+ variable_rows = []
+ for name, description in template_variables:
+ variable_rows.append(
+ htm.tr[
+ htm.td(".font-monospace.text-nowrap")[f"[{name}]"],
+ htm.td[description],
+ htm.td(".text-end")[
+ htpy.button(
+ ".btn.btn-sm.btn-outline-secondary.copy-var-btn",
+ type="button",
+ data_variable=f"[{name}]",
+ )["Copy"]
+ ],
+ ]
+ )
+
+ variables_table = htm.table(".table.table-sm.mt-2")[
+ htm.thead[
+ htm.tr[
+ htm.th["Variable"],
+ htm.th["Description"],
+ htm.th[""],
+ ]
+ ],
+ htm.tbody[*variable_rows],
+ ]
+
+ return htm.div(f"#{tab_id_prefix}-variables-pane.tab-pane.fade",
role="tabpanel")[variables_table]
+
+
+def variables_tab_button(tab_id_prefix: str) -> htm.Element:
+ return htm.li(".nav-item", role="presentation")[
+ htpy.button(
+ f"#{tab_id_prefix}-variables-tab.nav-link",
+ data_bs_toggle="tab",
+ data_bs_target=f"#{tab_id_prefix}-variables-pane",
+ type="button",
+ role="tab",
+ aria_controls=f"{tab_id_prefix}-variables-pane",
+ aria_selected="false",
+ )["Variables"]
+ ]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]