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-atr-experiments.git


The following commit(s) were added to refs/heads/main by this push:
     new 5d29f5a  Add a form field to select the artifact type
5d29f5a is described below

commit 5d29f5acc5902b5f90e059f785870588aee86cb1
Author: Sean B. Palmer <s...@miscoranda.com>
AuthorDate: Thu Feb 20 16:10:40 2025 +0200

    Add a form field to select the artifact type
---
 atr/db/models.py                                   |   2 +
 atr/routes.py                                      | 109 +++++++++++++--------
 atr/templates/candidate-attach.html                |  49 ++++++++-
 docs/plan.html                                     |   7 +-
 docs/plan.md                                       |   7 +-
 ...al_schema.py => 6776f795ec62_initial_schema.py} |   6 +-
 6 files changed, 128 insertions(+), 52 deletions(-)

diff --git a/atr/db/models.py b/atr/db/models.py
index 4c07ae6..226b73f 100644
--- a/atr/db/models.py
+++ b/atr/db/models.py
@@ -137,6 +137,8 @@ class Package(SQLModel, table=True):
     # The SHA3-256 hash of the file, used as filename in storage
     # TODO: We should discuss making this unique
     artifact_sha3: str = Field(primary_key=True)
+    # The type of artifact (source, binary, reproducible binary)
+    artifact_type: str
     # Original filename from uploader
     filename: str
     # SHA-512 hash of the file
diff --git a/atr/routes.py b/atr/routes.py
index 2ef95a5..f6d8ba6 100644
--- a/atr/routes.py
+++ b/atr/routes.py
@@ -99,52 +99,23 @@ async def ephemeral_gpg_home():
 
 async def release_attach_post(session: ClientSession, request: Request) -> 
Response:
     """Handle POST request for attaching package artifacts to a release."""
-
-    async def flash_error_and_redirect(message: str) -> Response:
-        await flash(message, "error")
-        return redirect(url_for("root_candidate_attach"))
-
-    form = await request.form
-
-    # TODO: Check that the submitter is a committer of the project
-
-    release_key = form.get("release_key")
-    if not release_key:
-        return await flash_error_and_redirect("Release key is required")
-
-    # Get all uploaded files
-    files = await request.files
-
-    # Get the release artifact, checksum, and signature files
-    artifact_file = files.get("release_artifact")
-    checksum_file = files.get("release_checksum")
-    signature_file = files.get("release_signature")
-    if not isinstance(artifact_file, FileStorage):
-        return await flash_error_and_redirect("Release artifact file is 
required")
+    res = await release_attach_post_validate(request)
+    # Not particularly elegant
+    if isinstance(res, Response):
+        return res
+    release_key, artifact_file, checksum_file, signature_file, artifact_type = 
res
+    # This must come here to appease the type checker
     if artifact_file.filename is None:
-        return await flash_error_and_redirect("Release artifact filename is 
required")
-    if checksum_file is not None and not isinstance(checksum_file, 
FileStorage):
-        return await flash_error_and_redirect("Problem with checksum file")
-    if signature_file is not None and not isinstance(signature_file, 
FileStorage):
-        return await flash_error_and_redirect("Problem with signature file")
+        await flash("Release artifact filename is required", "error")
+        return redirect(url_for("root_candidate_attach"))
 
     # Save files and create package record in one transaction
     async with get_session() as db_session:
         async with db_session.begin():
-            # First check for duplicates
-            statement = select(Package).where(
-                Package.release_key == release_key,
-                Package.filename == artifact_file.filename,
-            )
-            duplicate = (await db_session.execute(statement)).first()
-
-            if duplicate:
-                return await flash_error_and_redirect("This release artifact 
has already been uploaded")
-
             # Process and save the files
             try:
-                ok, artifact_sha3, artifact_size, artifact_sha512, 
signature_sha3 = await release_attach_post_helper(
-                    artifact_file, checksum_file, signature_file
+                ok, artifact_sha3, artifact_size, artifact_sha512, 
signature_sha3 = await release_attach_post_session(
+                    db_session, release_key, artifact_file, checksum_file, 
signature_file
                 )
                 if not ok:
                     # The flash error is already set by the helper
@@ -153,6 +124,7 @@ async def release_attach_post(session: ClientSession, 
request: Request) -> Respo
                 # Create the package record
                 package = Package(
                     artifact_sha3=artifact_sha3,
+                    artifact_type=artifact_type,
                     filename=artifact_file.filename,
                     signature_sha3=signature_sha3,
                     sha512=artifact_sha512,
@@ -163,16 +135,69 @@ async def release_attach_post(session: ClientSession, 
request: Request) -> Respo
                 db_session.add(package)
 
             except Exception as e:
-                return await flash_error_and_redirect(f"Error processing 
files: {e!s}")
+                await flash(f"Error processing files: {e!s}", "error")
+                return redirect(url_for("root_candidate_attach"))
 
     # Otherwise redirect to review page
     return redirect(url_for("root_candidate_review"))
 
 
-async def release_attach_post_helper(
-    artifact_file: FileStorage, checksum_file: FileStorage | None, 
signature_file: FileStorage | None
+async def release_attach_post_validate(
+    request: Request,
+) -> Response | tuple[str, FileStorage, FileStorage | None, FileStorage | 
None, str]:
+    async def flash_error_and_redirect(message: str) -> Response:
+        await flash(message, "error")
+        return redirect(url_for("root_candidate_attach"))
+
+    form = await request.form
+
+    # TODO: Check that the submitter is a committer of the project
+
+    release_key = form.get("release_key")
+    if (not release_key) or (not isinstance(release_key, str)):
+        return await flash_error_and_redirect("Release key is required")
+
+    # Get all uploaded files
+    files = await request.files
+    artifact_file = files.get("release_artifact")
+    checksum_file = files.get("release_checksum")
+    signature_file = files.get("release_signature")
+    if not isinstance(artifact_file, FileStorage):
+        return await flash_error_and_redirect("Release artifact file is 
required")
+    if checksum_file is not None and not isinstance(checksum_file, 
FileStorage):
+        return await flash_error_and_redirect("Problem with checksum file")
+    if signature_file is not None and not isinstance(signature_file, 
FileStorage):
+        return await flash_error_and_redirect("Problem with signature file")
+
+    # Get and validate artifact type
+    artifact_type = form.get("artifact_type")
+    if (not artifact_type) or (not isinstance(artifact_type, str)):
+        return await flash_error_and_redirect("Artifact type is required")
+    if artifact_type not in ["source", "binary", "reproducible"]:
+        return await flash_error_and_redirect("Invalid artifact type")
+
+    return release_key, artifact_file, checksum_file, signature_file, 
artifact_type
+
+
+async def release_attach_post_session(
+    db_session: AsyncSession,
+    release_key: str,
+    artifact_file: FileStorage,
+    checksum_file: FileStorage | None,
+    signature_file: FileStorage | None,
 ) -> tuple[bool, str, int, str, str | None]:
     """Helper function for release_attach_post."""
+    # First check for duplicates
+    statement = select(Package).where(
+        Package.release_key == release_key,
+        Package.filename == artifact_file.filename,
+    )
+    duplicate = (await db_session.execute(statement)).first()
+
+    if duplicate:
+        await flash("This release artifact has already been uploaded", "error")
+        return False, "", 0, "", None
+
     # Save files using their hashes as filenames
     uploads_path = Path(get_release_storage_dir())
     try:
diff --git a/atr/templates/candidate-attach.html 
b/atr/templates/candidate-attach.html
index c92c8e2..010d0c6 100644
--- a/atr/templates/candidate-attach.html
+++ b/atr/templates/candidate-attach.html
@@ -82,6 +82,23 @@
           border: 1px solid #c3e6cb;
           color: #155724;
       }
+
+      .radio-group {
+          margin-bottom: 0.5rem;
+      }
+
+      .radio-group div {
+          margin: 0.5rem 0;
+      }
+
+      .radio-group label {
+          margin-left: 0.5rem;
+          cursor: pointer;
+      }
+
+      .radio-group input[type="radio"] {
+          cursor: pointer;
+      }
   </style>
 {% endblock stylesheets %}
 
@@ -94,7 +111,12 @@
 
   {% with messages = get_flashed_messages(with_categories=true) %}
     {% if messages %}
-      {% for category, message in messages %}<div class="flash-message 
flash-{{ category }}">{{ message }}</div>{% endfor %}
+      {% for category, message in messages %}
+        <div class="flash-message flash-{{ category }}">
+          {% if category == 'error' %}<strong>Error:</strong>{% endif %}
+          {{ message }}
+        </div>
+      {% endfor %}
     {% endif %}
   {% endwith %}
 
@@ -139,6 +161,31 @@
           </td>
         </tr>
 
+        <tr>
+          <th>
+            <label>Artifact Type:</label>
+          </th>
+          <td>
+            <div class="radio-group">
+              <div>
+                <input type="radio" id="source" name="artifact_type" 
value="source" required />
+                <label for="source">Source archive</label>
+              </div>
+              <div>
+                <input type="radio" id="binary" name="artifact_type" 
value="binary" />
+                <label for="binary">Binary archive</label>
+              </div>
+              <div>
+                <input type="radio"
+                       id="reproducible"
+                       name="artifact_type"
+                       value="reproducible" />
+                <label for="reproducible">Reproducible binary archive</label>
+              </div>
+            </div>
+          </td>
+        </tr>
+
         <tr>
           <th>
             <label for="release_checksum">SHA-512 Checksum File:</label>
diff --git a/docs/plan.html b/docs/plan.html
index ffccd1e..782115c 100644
--- a/docs/plan.html
+++ b/docs/plan.html
@@ -5,10 +5,11 @@
 <li>
 <p>Improve RC workflow</p>
 <ul>
-<li>Allow upload of checksum file alongside artifacts and signatures</li>
-<li>Add a checkbox to choose the RC artifact type</li>
-<li>Allow extra types of artifact, such as reproducible binary and convenience 
binary</li>
+<li>[DONE] Allow upload of checksum file alongside artifacts and 
signatures</li>
+<li>[DONE] Add a form field to choose the RC artifact type</li>
+<li>[DONE] Allow extra types of artifact, such as reproducible binary and 
convenience binary</li>
 <li>Differentiate between podling PPMCs and top level PMCs</li>
+<li>Allow package deletion</li>
 </ul>
 </li>
 <li>
diff --git a/docs/plan.md b/docs/plan.md
index 40fc238..ad55597 100644
--- a/docs/plan.md
+++ b/docs/plan.md
@@ -5,10 +5,11 @@ This is a rough plan of immediate tasks. The priority of 
these tasks may change,
 ## UX improvements
 
 1. Improve RC workflow
-   - Allow upload of checksum file alongside artifacts and signatures
-   - Add a checkbox to choose the RC artifact type
-   - Allow extra types of artifact, such as reproducible binary and 
convenience binary
+   - [DONE] Allow upload of checksum file alongside artifacts and signatures
+   - [DONE] Add a form field to choose the RC artifact type
+   - [DONE] Allow extra types of artifact, such as reproducible binary and 
convenience binary
    - Differentiate between podling PPMCs and top level PMCs
+   - Allow package deletion
 
 2. Enhance RC display
    - [DONE] Augment raw file hashes with the original filenames in the UI
diff --git a/migrations/versions/d8434a3de3da_initial_schema.py 
b/migrations/versions/6776f795ec62_initial_schema.py
similarity index 84%
rename from migrations/versions/d8434a3de3da_initial_schema.py
rename to migrations/versions/6776f795ec62_initial_schema.py
index 82f8c3a..3f1e9ef 100644
--- a/migrations/versions/d8434a3de3da_initial_schema.py
+++ b/migrations/versions/6776f795ec62_initial_schema.py
@@ -1,15 +1,15 @@
 """initial_schema
 
-Revision ID: d8434a3de3da
+Revision ID: 6776f795ec62
 Revises:
-Create Date: 2025-02-20 15:24:43.391427
+Create Date: 2025-02-20 16:02:41.618093
 
 """
 
 from collections.abc import Sequence
 
 # revision identifiers, used by Alembic.
-revision: str = "d8434a3de3da"
+revision: str = "6776f795ec62"
 down_revision: str | None = None
 branch_labels: str | Sequence[str] | None = None
 depends_on: str | Sequence[str] | None = None


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tooling.apache.org
For additional commands, e-mail: dev-h...@tooling.apache.org

Reply via email to