This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/sbp by this push:
     new 473002ea Show how long is left in a vote and when it ends
473002ea is described below

commit 473002ea8579b4119d6516fec6a93fc6d6e5f8b5
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Mar 26 15:07:59 2026 +0000

    Show how long is left in a vote and when it ends
---
 atr/get/vote.py | 34 ++++++++++++++++++++++++++++++++--
 1 file changed, 32 insertions(+), 2 deletions(-)

diff --git a/atr/get/vote.py b/atr/get/vote.py
index d527c239..de70497e 100644
--- a/atr/get/vote.py
+++ b/atr/get/vote.py
@@ -17,6 +17,7 @@
 
 from __future__ import annotations
 
+import datetime
 import enum
 import urllib.parse
 from typing import TYPE_CHECKING, Literal
@@ -116,7 +117,7 @@ async def render_options_page(
     archive_url = await _get_archive_url(release, session, latest_vote_task)
 
     page = htm.Block()
-    _render_header(page, release, show_resolve_section)
+    _render_header(page, release, show_resolve_section, latest_vote_task)
     _render_section_download(page, release, session, user_category)
     _render_section_checks(page, release, file_totals)
     await _render_section_vote(page, release, session, user_category, 
archive_url)
@@ -277,6 +278,29 @@ def _download_zip(release: sql.Release) -> htm.Element:
     ]
 
 
+def _format_vote_end(vote_end: datetime.datetime) -> str:
+    now = datetime.datetime.now(datetime.UTC)
+    timestamp = vote_end.strftime("%a %Y-%m-%d at %H:%M UTC")
+
+    remaining = vote_end - now
+    total_seconds = int(remaining.total_seconds())
+    if total_seconds <= 0:
+        return f"The vote ended on {timestamp}."
+
+    days = total_seconds // 86400
+    hours = (total_seconds % 86400) // 3600
+    minutes = (total_seconds % 3600) // 60
+
+    parts: list[str] = []
+    if days > 0:
+        parts.append(f"{days}d")
+    if (days > 0) or (hours > 0):
+        parts.append(f"{hours}h")
+    parts.append(f"{minutes}m")
+
+    return f"The vote ends in {' '.join(parts)}, on {timestamp}."
+
+
 async def _get_archive_url(
     release: sql.Release, session: web.Committer | None, latest_vote_task: 
sql.Task | None
 ) -> str | None:
@@ -314,7 +338,9 @@ def _render_checklist_card(page: htm.Block, release: 
sql.Release) -> None:
     page.append(card.collect())
 
 
-def _render_header(page: htm.Block, release: sql.Release, 
show_resolve_section: bool) -> None:
+def _render_header(
+    page: htm.Block, release: sql.Release, show_resolve_section: bool, 
latest_vote_task: sql.Task | None
+) -> None:
     render.html_nav(
         page,
         back_url=util.as_url(root.index),
@@ -339,6 +365,10 @@ def _render_header(page: htm.Block, release: sql.Release, 
show_resolve_section:
         f" {release.project.display_name} {release.version}.",
     ]
 
+    vote_end = interaction.vote_end_get(latest_vote_task)
+    if vote_end is not None:
+        page.p[_format_vote_end(vote_end)]
+
     page.p["To participate in this vote, please select your next step:"]
 
     steps = htm.Block(htpy.ol, classes=".atr-steps")


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to