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 7ca36bd Reserve a type for confirmation fields and make them more
consistent
7ca36bd is described below
commit 7ca36bd54b6c35c28f9e86f87b049fd6c0653a79
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Dec 23 16:14:37 2025 +0000
Reserve a type for confirmation fields and make them more consistent
---
atr/admin/__init__.py | 18 ++-------------
atr/form.py | 3 +++
atr/get/announce.py | 2 +-
atr/shared/announce.py | 13 +++++------
atr/static/js/src/announce-confirm.js | 43 +++++++++++++++++++++++++++++++++++
playwright/test.py | 19 ++++++++++------
tests/e2e/announce/test_get.py | 20 ++++++++++++++++
7 files changed, 87 insertions(+), 31 deletions(-)
diff --git a/atr/admin/__init__.py b/atr/admin/__init__.py
index 85010f4..63b5a62 100644
--- a/atr/admin/__init__.py
+++ b/atr/admin/__init__.py
@@ -68,26 +68,12 @@ class BrowseAsUserForm(form.Form):
class DeleteCommitteeKeysForm(form.Form):
committee_name: str = form.label("Committee", widget=form.Widget.SELECT)
- confirm_delete: str = form.label("Confirmation", "Type DELETE KEYS to
confirm")
-
- @pydantic.field_validator("confirm_delete")
- @classmethod
- def validate_confirm_delete(cls, v: str) -> str:
- if v != "DELETE KEYS":
- raise ValueError("You must type DELETE KEYS exactly to confirm
deletion")
- return v
+ confirm_delete: Literal["DELETE KEYS"] = form.label("Confirmation", "Type
DELETE KEYS to confirm.")
class DeleteReleaseForm(form.Form):
releases_to_delete: form.StrList = form.label("Select releases to delete",
widget=form.Widget.CUSTOM)
- confirm_delete: str = form.label("Confirmation", "Please type DELETE
exactly to confirm deletion.")
-
- @pydantic.field_validator("confirm_delete")
- @classmethod
- def validate_confirm_delete(cls, v: str) -> str:
- if v != "DELETE":
- raise ValueError("You must type DELETE exactly to confirm
deletion")
- return v
+ confirm_delete: Literal["DELETE"] = form.label("Confirmation", "Type
DELETE to confirm.")
@pydantic.field_validator("releases_to_delete")
@classmethod
diff --git a/atr/form.py b/atr/form.py
index 78292bc..97566a6 100644
--- a/atr/form.py
+++ b/atr/form.py
@@ -681,6 +681,9 @@ def _get_widget_type(field_info: pydantic.fields.FieldInfo)
-> Widget: # noqa:
return Widget.NUMBER
if origin is Literal:
+ args = get_args(annotation)
+ if len(args) == 1:
+ return Widget.TEXT
return Widget.SELECT
if origin is set:
diff --git a/atr/get/announce.py b/atr/get/announce.py
index 1ff7327..1025aee 100644
--- a/atr/get/announce.py
+++ b/atr/get/announce.py
@@ -82,7 +82,7 @@ async def selected(session: web.Committer, project_name: str,
version_name: str)
title=f"Announce and distribute {release.project.display_name}
{release.version}",
description=f"Announce and distribute {release.project.display_name}
{release.version} as a release.",
content=content,
- javascripts=["announce-preview", "copy-variable"],
+ javascripts=["announce-confirm", "announce-preview", "copy-variable"],
)
diff --git a/atr/shared/announce.py b/atr/shared/announce.py
index e844841..7d208fd 100644
--- a/atr/shared/announce.py
+++ b/atr/shared/announce.py
@@ -15,6 +15,8 @@
# specific language governing permissions and limitations
# under the License.
+from typing import Literal
+
import pydantic
import atr.form as form
@@ -31,7 +33,10 @@ class AnnounceForm(form.Form):
subject: str = form.label("Subject")
body: str = form.label("Body", widget=form.Widget.CUSTOM)
download_path_suffix: str = form.label("Download path suffix",
widget=form.Widget.CUSTOM)
- confirm_announce: form.Bool = form.label("Confirm")
+ confirm_announce: Literal["CONFIRM"] = form.label(
+ "Confirm",
+ "Type CONFIRM (in capitals) to enable the submit button.",
+ )
@pydantic.field_validator("download_path_suffix")
@classmethod
@@ -49,9 +54,3 @@ class AnnounceForm(form.Form):
if "/." in suffix:
raise ValueError("Download path suffix must not contain /.")
return suffix
-
- @pydantic.model_validator(mode="after")
- def validate_confirm(self) -> "AnnounceForm":
- if not self.confirm_announce:
- raise ValueError("You must confirm the announcement by checking
the box")
- return self
diff --git a/atr/static/js/src/announce-confirm.js
b/atr/static/js/src/announce-confirm.js
new file mode 100644
index 0000000..f33177a
--- /dev/null
+++ b/atr/static/js/src/announce-confirm.js
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+function initAnnounceConfirm() {
+ const confirmInput = document.getElementById("confirm_announce");
+ const announceForm = document.querySelector("form.atr-canary");
+
+ if (!confirmInput || !announceForm) {
+ return;
+ }
+
+ const submitButton =
announceForm.querySelector('button[type="submit"]');
+ if (!submitButton) {
+ return;
+ }
+
+ const updateButtonState = () => {
+ const isConfirmed = confirmInput.value === "CONFIRM";
+ submitButton.disabled = !isConfirmed;
+ };
+
+ confirmInput.addEventListener("input", updateButtonState);
+
+ updateButtonState();
+}
+
+document.addEventListener("DOMContentLoaded", initAnnounceConfirm);
diff --git a/playwright/test.py b/playwright/test.py
index bbca524..ebe6fc2 100755
--- a/playwright/test.py
+++ b/playwright/test.py
@@ -274,16 +274,21 @@ def lifecycle_06_announce_preview(page: Page,
credentials: Credentials, version_
form_locator =
page.locator(f'form[action="/announce/{TEST_PROJECT}/{esc_id(version_name)}"]')
expect(form_locator).to_be_visible()
- logging.info("Locating the confirmation checkbox within the form")
- checkbox_locator = form_locator.locator('input[name="confirm_announce"]')
- expect(checkbox_locator).to_be_visible()
+ logging.info("Locating the confirmation input within the form")
+ confirm_input_locator =
form_locator.locator('input[name="confirm_announce"]')
+ expect(confirm_input_locator).to_be_visible()
- logging.info("Checking the confirmation checkbox")
- checkbox_locator.check()
-
- logging.info("Locating and activating the announce button within the form")
+ logging.info("Verifying submit button is initially disabled")
submit_button_locator = form_locator.get_by_role("button", name="Send
announcement email")
+ expect(submit_button_locator).to_be_disabled()
+
+ logging.info("Typing CONFIRM in the confirmation input")
+ confirm_input_locator.fill("CONFIRM")
+
+ logging.info("Verifying submit button is now enabled")
expect(submit_button_locator).to_be_enabled()
+
+ logging.info("Clicking the announce button")
submit_button_locator.click()
logging.info("Waiting for navigation to /releases after submitting
announcement")
diff --git a/tests/e2e/announce/test_get.py b/tests/e2e/announce/test_get.py
index 6e8aa64..65731e1 100644
--- a/tests/e2e/announce/test_get.py
+++ b/tests/e2e/announce/test_get.py
@@ -103,3 +103,23 @@ def test_preview_updates_on_body_input(page_announce:
Page) -> None:
preview_tab.click()
expect(preview_content).not_to_have_text(initial_preview or "")
+
+
+def test_submit_button_disabled_until_confirm_typed(page_announce: Page) ->
None:
+ """The submit button should be disabled until CONFIRM is typed."""
+ submit_button = page_announce.get_by_role("button", name="Send
announcement email")
+ confirm_input = page_announce.locator("#confirm_announce")
+
+ expect(submit_button).to_be_disabled()
+
+ confirm_input.fill("confirm")
+ expect(submit_button).to_be_disabled()
+
+ confirm_input.fill("CONFIRM")
+ expect(submit_button).to_be_enabled()
+
+ confirm_input.fill("CONFIRME")
+ expect(submit_button).to_be_disabled()
+
+ confirm_input.fill("CONFIRM")
+ expect(submit_button).to_be_enabled()
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]