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 881f4de Remove the drafts page and update the browser tests 881f4de is described below commit 881f4de4808e178cd328952267cd63da5a1c81bd Author: Sean B. Palmer <s...@miscoranda.com> AuthorDate: Fri Apr 25 17:19:50 2025 +0100 Remove the drafts page and update the browser tests --- atr/routes/compose.py | 2 +- atr/routes/download.py | 4 +- atr/routes/draft.py | 38 +++------------ atr/routes/release.py | 8 +-- atr/templates/draft-evaluate.html | 2 +- atr/templates/drafts.html | 100 -------------------------------------- playwright/test.py | 25 +++++----- 7 files changed, 28 insertions(+), 151 deletions(-) diff --git a/atr/routes/compose.py b/atr/routes/compose.py index 33c4774..393fc9b 100644 --- a/atr/routes/compose.py +++ b/atr/routes/compose.py @@ -89,7 +89,7 @@ async def release(session: routes.CommitterSession, project_name: str, version_n delete_file_form = await draft.DeleteFileForm.create_form() return await quart.render_template( - "draft-content.html", + "compose-release.html", project_name=project_name, version_name=version_name, release=release, diff --git a/atr/routes/download.py b/atr/routes/download.py index 5e53793..cfb3c90 100644 --- a/atr/routes/download.py +++ b/atr/routes/download.py @@ -25,7 +25,7 @@ import quart import werkzeug.wrappers.response as response import atr.routes as routes -import atr.routes.draft as draft +import atr.routes.root as root import atr.util as util @@ -46,7 +46,7 @@ async def phase( # Even using the following type declaration, mypy does not know the type # The same pattern is used in release.py, so this is a bug in mypy # TODO: Report the bug upstream to mypy - return await session.redirect(draft.drafts, error="File not found") + return await session.redirect(root.index, error="File not found") # Send the file with original filename return await quart.send_file( diff --git a/atr/routes/draft.py b/atr/routes/draft.py index 5dff789..b0ea93f 100644 --- a/atr/routes/draft.py +++ b/atr/routes/draft.py @@ -43,6 +43,7 @@ import atr.revision as revision import atr.routes as routes import atr.routes.candidate as candidate import atr.routes.compose as compose +import atr.routes.root as root import atr.routes.upload as upload import atr.tasks.sbom as sbom import atr.tasks.vote as tasks_vote @@ -165,17 +166,17 @@ async def delete(session: routes.CommitterSession) -> response.Response: for _field, errors in form.errors.items(): for error in errors: await quart.flash(f"{error}", "error") - return await session.redirect(drafts) + return await session.redirect(root.index) candidate_draft_name = form.candidate_draft_name.data if not candidate_draft_name: - return await session.redirect(drafts, error="Missing required parameters") + return await session.redirect(root.index, error="Missing required parameters") # Extract project name and version try: project_name, version = candidate_draft_name.rsplit("-", 1) except ValueError: - return await session.redirect(drafts, error="Invalid candidate draft name format") + return await session.redirect(root.index, error="Invalid candidate draft name format") await session.check_access(project_name) # Delete the metadata from the database @@ -185,7 +186,7 @@ async def delete(session: routes.CommitterSession) -> response.Response: await _delete_candidate_draft(data, candidate_draft_name) except Exception as e: logging.exception("Error deleting candidate draft:") - return await session.redirect(drafts, error=f"Error deleting candidate draft: {e!s}") + return await session.redirect(root.index, error=f"Error deleting candidate draft: {e!s}") # Delete the files on disk, including all revisions # We can't use util.release_directory_base here because we don't have the release object @@ -197,7 +198,7 @@ async def delete(session: routes.CommitterSession) -> response.Response: # Yet it works in preview.py await aioshutil.rmtree(draft_dir) # type: ignore[call-arg] - return await session.redirect(drafts, success="Candidate draft deleted successfully") + return await session.redirect(root.index, success="Candidate draft deleted successfully") @routes.committer("/draft/delete-file/<project_name>/<version_name>", methods=["POST"]) @@ -258,29 +259,6 @@ async def delete_file(session: routes.CommitterSession, project_name: str, versi ) -@routes.committer("/drafts") -async def drafts(session: routes.CommitterSession) -> str: - """Allow the user to view current candidate drafts.""" - # Do them outside of the template rendering call to ensure order - # The user_candidate_drafts call can use cached results from user_projects - # TODO: admin users should be able to view and manipulate all candidates if needed - user_projects = await session.user_projects - user_candidate_drafts = await session.user_candidate_drafts - - # Create the delete form - delete_form = await DeleteForm.create_form() - - return await quart.render_template( - "drafts.html", - asf_id=session.uid, - projects=user_projects, - server_domain=session.host, - number_of_release_files=util.number_of_release_files, - candidate_drafts=user_candidate_drafts, - delete_form=delete_form, - ) - - @routes.committer("/draft/evaluate/<project_name>/<version_name>") async def evaluate(session: routes.CommitterSession, project_name: str, version_name: str) -> response.Response | str: """Evaluate all the files in the rsync upload directory for a release.""" @@ -905,7 +883,7 @@ async def vote_preview(session: routes.CommitterSession) -> quart.wrappers.respo form = await VotePreviewForm.create_form(data=await quart.request.form) if not await form.validate_on_submit(): - return await session.redirect(drafts, error="Invalid form data") + return await session.redirect(root.index, error="Invalid form data") body = await mail.generate_preview( util.unwrap(form.body.data), util.unwrap(form.asfuid.data), util.unwrap(form.vote_duration.data) @@ -1023,7 +1001,7 @@ Thanks, if email_to != sender: error = await _promote(data, release.name, project_name, version, revision) if error: - return await session.redirect(drafts, error=error) + return await session.redirect(root.index, error=error) # This is now handled by the _promote call, above # # Update the release phase to the voting phase only if not sending a test message to the user diff --git a/atr/routes/release.py b/atr/routes/release.py index af7f9ee..617a2e6 100644 --- a/atr/routes/release.py +++ b/atr/routes/release.py @@ -28,7 +28,7 @@ import werkzeug.wrappers.response as response import atr.db as db import atr.db.models as models import atr.routes as routes -import atr.routes.draft as draft +import atr.routes.root as root import atr.util as util if asfquart.APP is ...: @@ -42,11 +42,11 @@ async def bulk_status(session: routes.CommitterSession, task_id: int) -> str | r # Query for the task with the given ID task = await data.task(id=task_id).get() if not task: - return await session.redirect(draft.drafts, error=f"Task with ID {task_id} not found.") + return await session.redirect(root.index, error=f"Task with ID {task_id} not found.") # Verify this is a bulk download task if task.task_type != "package_bulk_download": - return await session.redirect(draft.drafts, error=f"Task with ID {task_id} is not a bulk download task.") + return await session.redirect(root.index, error=f"Task with ID {task_id} is not a bulk download task.") # If result is a list or tuple with a single item, extract it if isinstance(task.result, list | tuple) and (len(task.result) == 1): @@ -65,7 +65,7 @@ async def bulk_status(session: routes.CommitterSession, task_id: int) -> str | r if (session.uid not in release.committee.committee_members) and ( session.uid not in release.committee.committers ): - return await session.redirect(draft.drafts, error="You don't have permission to view this task.") + return await session.redirect(root.index, error="You don't have permission to view this task.") return await quart.render_template("release-bulk.html", task=task, release=release, TaskStatus=models.TaskStatus) diff --git a/atr/templates/draft-evaluate.html b/atr/templates/draft-evaluate.html index 5a452a6..ec17f01 100644 --- a/atr/templates/draft-evaluate.html +++ b/atr/templates/draft-evaluate.html @@ -183,7 +183,7 @@ </details> {% else %} <div class="alert alert-info"> - This release does not have any files yet. You can add files using rsync from the <a href="{{ as_url(routes.draft.drafts) }}">review drafts</a> page. + This release does not have any files yet. You can add files using rsync from the review drafts page. </div> {% endif %} </div> diff --git a/atr/templates/drafts.html b/atr/templates/drafts.html deleted file mode 100644 index 88475cc..0000000 --- a/atr/templates/drafts.html +++ /dev/null @@ -1,100 +0,0 @@ -{% extends "layouts/base.html" %} - -{% block title %} - Candidate draft directory ~ ATR -{% endblock title %} - -{% block description %} - Review and modify candidate drafts. -{% endblock description %} - -{% import 'macros/dialog.html' as dialog %} - -{% block content %} - <h1>Candidate drafts</h1> - <p> - A <strong>candidate draft</strong> is an editable set of files which can be <em>frozen and promoted into a candidate release</em> for voting on by the PMC. - </p> - <ul> - <li>You can only create a new candidate draft if you are a member of the PMC</li> - <li>Projects can work on multiple candidate drafts for different versions simultaneously</li> - <li>A candidate draft is only editable until submitted for voting</li> - </ul> - - <div class="row row-cols-1 g-4 mb-5"> - {% for release in candidate_drafts %} - {% set release_id = release.name %} - <div class="col" id="{{ release.name }}"> - <div class="card h-100"> - <div class="card-body position-relative"> - <div class="position-absolute top-0 end-0 m-2"> - <span class="badge bg-success">Draft</span> - </div> - <h5 class="card-title">{{ release.project.display_name }} {{ release.version }}</h5> - {% if release.project.committee %} - <h6 class="card-subtitle mb-2 text-muted">{{ release.project.committee.display_name }}</h6> - {% endif %} - <div class="d-flex gap-2 mb-2"> - <a href="{{ as_url(routes.draft.evaluate, project_name=release.project.name, version_name=release.version) }}" - title="Checks for {{ release.project.display_name }} {{ release.version }}" - class="btn btn-sm btn-outline-secondary">Checks</a> - <a href="{{ as_url(routes.draft.view, project_name=release.project.name, version_name=release.version) }}" - title="Files for {{ release.project.display_name }} {{ release.version }}" - class="btn btn-sm btn-outline-secondary">Files</a> - <a href="{{ as_url(routes.draft.revisions, project_name=release.project.name, version_name=release.version) }}" - title="Revisions for {{ release.project.display_name }} {{ release.version }}" - class="btn btn-sm btn-outline-secondary">Revisions</a> - <a href="{{ as_url(routes.projects.view, name=release.project.name) }}" - title="Project for {{ release.project.display_name }} {{ release.version }}" - class="btn btn-sm btn-outline-secondary">Project</a> - <br /> - <a href="{{ as_url(routes.upload.release, project_name=release.project.name, version_name=release.version) }}" - title="Add files to {{ release.project.display_name }} {{ release.version }}" - class="btn btn-sm btn-outline-primary">Add files</a> - <a href="{{ as_url(routes.draft.vote_start, project_name=release.project.name, version=release.version, revision=release.revision) }}" - title="Start vote for {{ release.project.display_name }} {{ release.version }}" - class="btn btn-sm btn-outline-success">Start vote</a> - <button class="btn btn-sm btn-outline-danger" - title="Delete {{ release.project.display_name }} {{ release.version }}" - data-bs-toggle="modal" - data-bs-target="#delete-{{ release_id }}">Delete</button> - </div> - <p class="card-text mt-3"> - {% if number_of_release_files(release) > 0 %} - This candidate draft has {{ number_of_release_files(release) }} - {% if number_of_release_files(release) == 1 %} - file. - {% else %} - files. - {% endif %} - {% else %} - This candidate draft doesn't have any files yet. - {% endif %} - Use the command below to add or modify files in this draft: - </p> - </div> - <div class="card-footer bg-light border-1 pt-4 pb-4 position-relative"> - <button class="btn btn-sm btn-outline-secondary atr-copy-btn fs-6 position-absolute top-0 end-0 m-2" - data-clipboard-target="#cmd-{{ release.name|slugify }}"> - <i class="fas fa-clipboard"></i> Copy - </button> - <pre class="small mb-0" id="cmd-{{ release.name|slugify }}">rsync -av -e 'ssh -p 2222' ${YOUR_FILES}/ {{ asf_id }}@{{ server_domain }}:/{{ release.project.name }}/{{ release.version }}/</pre> - </div> - </div> - </div> - {{ dialog.delete_modal_with_confirm(release_id, "Delete candidate draft", "candidate draft", as_url(routes.draft.delete) , delete_form, "candidate_draft_name") }} - {% endfor %} - {% if candidate_drafts|length == 0 %} - <div class="col-12"> - <div class="alert alert-info">There are currently no candidate drafts.</div> - </div> - {% endif %} - </div> -{% endblock content %} - -{% block javascripts %} - {{ super() }} - <script> - init(); - </script> -{% endblock javascripts %} diff --git a/playwright/test.py b/playwright/test.py index 4b0cc88..8f982e6 100644 --- a/playwright/test.py +++ b/playwright/test.py @@ -120,9 +120,11 @@ def lifecycle_01_add_draft(page: sync_api.Page, credentials: Credentials, versio def lifecycle_02_check_draft_added(page: sync_api.Page, credentials: Credentials, version_name: str) -> None: logging.info(f"Checking for draft 'tooling-test-example {version_name}'") - go_to_path(page, "/drafts") - draft_card_locator = page.locator(f"#tooling-test-example-{esc_id(version_name)}") - sync_api.expect(draft_card_locator).to_be_visible() + go_to_path(page, f"/compose/tooling-test-example/{version_name}") + h1_strong_locator = page.locator("h1 strong:has-text('Tooling Test Example')") + sync_api.expect(h1_strong_locator).to_be_visible() + h1_em_locator = page.locator(f"h1 em:has-text('{esc_id(version_name)}')") + sync_api.expect(h1_em_locator).to_be_visible() logging.info(f"Draft 'tooling-test-example {version_name}' found successfully") @@ -147,21 +149,18 @@ def lifecycle_03_add_file(page: sync_api.Page, credentials: Credentials, version wait_for_path(page, f"/compose/tooling-test-example/{version_name}") logging.info("Add file actions completed successfully") - logging.info("Navigating back to /drafts") - go_to_path(page, "/drafts") - logging.info("Navigation back to /drafts completed successfully") + logging.info("Navigating back to /compose/tooling-test-example") + go_to_path(page, f"/compose/tooling-test-example/{version_name}") + logging.info("Navigation back to /compose/tooling-test-example completed successfully") def lifecycle_04_start_vote(page: sync_api.Page, credentials: Credentials, version_name: str) -> None: - logging.info(f"Navigating to the drafts page for tooling-test-example {version_name}") - go_to_path(page, "/drafts") - logging.info("Drafts page loaded successfully") + logging.info(f"Navigating to the compose/tooling-test-example page for tooling-test-example {version_name}") + go_to_path(page, f"/compose/tooling-test-example/{version_name}") + logging.info("Compose/tooling-test-example page loaded successfully") logging.info(f"Locating start vote link for tooling-test-example {version_name}") - draft_card_locator = page.locator(f"#tooling-test-example-{esc_id(version_name)}") - start_vote_link_locator = draft_card_locator.locator( - f'a[title="Start vote for Apache Tooling Test Example {version_name}"]' - ) + start_vote_link_locator = page.locator('a[title="Start a vote on this draft"]') sync_api.expect(start_vote_link_locator).to_be_visible() logging.info("Follow the start vote link") --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@tooling.apache.org For additional commands, e-mail: commits-h...@tooling.apache.org