diff --git a/src/lando/api/legacy/api/__init__.py b/src/lando/api/legacy/api/__init__.py index 7f92ea5b..37229788 100644 --- a/src/lando/api/legacy/api/__init__.py +++ b/src/lando/api/legacy/api/__init__.py @@ -1,3 +1,3 @@ -from lando.api.legacy.api import stacks, transplants +from lando.api.legacy.api import stacks, transplants, uplift -__all__ = ["stacks", "transplants"] +__all__ = ["stacks", "transplants", "uplift"] diff --git a/src/lando/api/legacy/api/uplift.py b/src/lando/api/legacy/api/uplift.py index 1f94def0..dc9dc697 100644 --- a/src/lando/api/legacy/api/uplift.py +++ b/src/lando/api/legacy/api/uplift.py @@ -1,57 +1,28 @@ import logging +from django.http import Http404 + +from lando.api.legacy.api.stacks import HTTP_404_STRING from lando.api.legacy.uplift import ( create_uplift_revision, get_local_uplift_repo, get_uplift_conduit_state, - get_uplift_repositories, ) from lando.api.legacy.validation import revision_id_to_int from lando.main.auth import require_authenticated_user, require_phabricator_api_key -from lando.main.models import Repo -from lando.main.support import problem from lando.utils.phabricator import PhabricatorClient logger = logging.getLogger(__name__) -@require_phabricator_api_key(optional=True) -def get(phab: PhabricatorClient): - """Return the list of valid uplift repositories.""" - repos = [ - phab.expect(repo, "fields", "shortName") - for repo in get_uplift_repositories(phab) - ] - - return {"repos": repos}, 201 - - -@require_phabricator_api_key(optional=False) @require_authenticated_user -def create(phab: PhabricatorClient, data: dict): +@require_phabricator_api_key(optional=False) +def create(phab: PhabricatorClient, request, data: dict): """Create new uplift requests for requested repository & revision""" - repo_name = data["repository"] + repository = data["repository"] + repo_name = repository.name revision_id = revision_id_to_int(data["revision_id"]) - # Validate repository. - all_repos = Repo.get_mapping() - repository = all_repos.get(repo_name) - if repository is None: - return problem( - 400, - f"Repository {repo_name} is not a repository known to Lando.", - "Please select an uplift repository to create the uplift request.", - type="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400", - ) - - if not repository.approval_required: - return problem( - 400, - f"Repository {repo_name} is not an uplift repository.", - "Please select an uplift repository to create the uplift request.", - type="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400", - ) - try: logger.info( "Checking approval state", @@ -72,12 +43,7 @@ def create(phab: PhabricatorClient, data: dict): "Hit an error retreiving uplift state from conduit.", extra={"error": str(err)}, ) - return problem( - 404, - "Revision not found", - err.args[0], - type="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404", - ) + raise Http404(HTTP_404_STRING) revision_phid = next( rev["phid"] @@ -138,4 +104,4 @@ def create(phab: PhabricatorClient, data: dict): output = {rev["revision_phid"]: rev for rev in commit_stack} output["tip_differential"] = commit_stack[-1] - return output, 201 + return output diff --git a/src/lando/api/legacy/uplift.py b/src/lando/api/legacy/uplift.py index 3d75b78c..a68c7f3e 100644 --- a/src/lando/api/legacy/uplift.py +++ b/src/lando/api/legacy/uplift.py @@ -22,7 +22,7 @@ request_extended_revision_data, ) from lando.main.models import Repo -from lando.utils.phabricator import PhabricatorClient +from lando.utils.phabricator import PhabricatorClient, get_phabricator_client logger = logging.getLogger(__name__) @@ -59,18 +59,16 @@ def get_uplift_request_form(revision: dict) -> Optional[str]: return bug -# @cache.cached( -# key_prefix="uplift-repositories" -# ) -def get_uplift_repositories(phab: PhabricatorClient) -> list: +def get_uplift_repositories() -> list: + # TODO: cache + phab = get_phabricator_client() repos = phab.call_conduit( "diffusion.repository.search", constraints={"projects": ["uplift"]}, ) repos = phab.expect(repos, "data") - - return repos + return [phab.expect(repo, "fields", "shortName") for repo in repos] def get_revisions_without_bugs(phab: PhabricatorClient, revisions: dict) -> set[str]: diff --git a/src/lando/ui/jinja2/stack/stack.html b/src/lando/ui/jinja2/stack/stack.html index 076b638d..b61474da 100644 --- a/src/lando/ui/jinja2/stack/stack.html +++ b/src/lando/ui/jinja2/stack/stack.html @@ -129,7 +129,7 @@

Landing is blocked:

{% include "stack/partials/landing-preview.html" %} diff --git a/src/lando/ui/legacy/forms.py b/src/lando/ui/legacy/forms.py index 5b9a2290..14c0fb43 100644 --- a/src/lando/ui/legacy/forms.py +++ b/src/lando/ui/legacy/forms.py @@ -1,5 +1,8 @@ from django import forms +from lando.api.legacy.uplift import get_uplift_repositories +from lando.main.models import Repo + class TransplantRequestForm(forms.Form): landing_path = forms.JSONField(widget=forms.widgets.HiddenInput) @@ -12,8 +15,32 @@ class TransplantRequestForm(forms.Form): class UpliftRequestForm(forms.Form): """Form used to request uplift of a stack.""" - revision_id = forms.RegexField(regex="D[0-9]+$") - repository = forms.CharField() + revision_id = forms.RegexField( + regex="D[0-9]+$", + widget=forms.widgets.HiddenInput, + required=False, + ) + repository = forms.ChoiceField( + widget=forms.Select(), + choices=((repo, repo) for repo in get_uplift_repositories()), + ) + + def clean_repository(self): + repo_name = self.cleaned_data["repository"] + all_repos = Repo.get_mapping() + repository = all_repos.get(repo_name) + if repository is None: + raise forms.ValidationError( + f"Repository {repo_name} is not a repository known to Lando. " + "Please select an uplift repository to create the uplift request." + ) + + if not repository.approval_required: + raise forms.ValidationError( + f"Repository {repo_name} is not an uplift repository. " + "Please select an uplift repository to create the uplift request." + ) + return repository class UserSettingsForm(forms.Form): diff --git a/src/lando/ui/legacy/revisions.py b/src/lando/ui/legacy/revisions.py index 24bf248a..40f94a92 100644 --- a/src/lando/ui/legacy/revisions.py +++ b/src/lando/ui/legacy/revisions.py @@ -10,7 +10,7 @@ from lando.main.auth import force_auth_refresh from lando.ui.legacy.forms import ( TransplantRequestForm, - # UpliftRequestForm, + UpliftRequestForm, ) from lando.ui.legacy.stacks import Edge, draw_stack_graph, sort_stack_topological from lando.ui.views import LandoView @@ -21,6 +21,49 @@ # revisions.before_request(set_last_local_referrer) +class Uplift(LandoView): + @force_auth_refresh + def post(self, request): + """Process the uplift request submission.""" + uplift_request_form = UpliftRequestForm(request.POST) + errors = [] + + if not request.user.is_authenticated: + raise PermissionError() + + if uplift_request_form.is_valid() and not errors: + revision_id = uplift_request_form.cleaned_data["revision_id"] + repository = uplift_request_form.cleaned_data["repository"] + + response = legacy_api.uplift.create( + request, + data={ + "revision_id": revision_id, + "repository": repository, + }, + ) + + # Redirect to the tip revision's URL. + # TODO add js for auto-opening the uplift request Phabricator form. + # See https://bugzilla.mozilla.org/show_bug.cgi?id=1810257. + revision_id = response["tip_differential"]["revision_id"] + return redirect("revisions-page", revision_id=revision_id) + + if uplift_request_form.errors: + errors += [ + f"{field}: {', '.join(field_errors)}" + for field, field_errors in uplift_request_form.errors.items() + ] + + for error in errors: + messages.add_message(request, messages.ERROR, error) + + # Not ideal, but because we do not have access to the revision ID + # we will just redirect the user back to the referring page and + # they will see the flash messages. + return redirect(request.META.get("HTTP_REFERER")) + + class Revision(LandoView): def get( self, request: HttpRequest, revision_id: int, *args, **kwargs @@ -33,13 +76,8 @@ def get( form = TransplantRequestForm() errors = [] - # TODO - see bug 1915695. - # uplift_request_form = UpliftRequestForm() - - # # Get the list of available uplift repos and populate the form with it. - # uplift_request_form.repository.choices = get_uplift_repos(api) - # uplift_request_form.revision_id.data = revision_id - # END TODO. + uplift_request_form = UpliftRequestForm() + uplift_request_form.fields["revision_id"].initial = f"D{revision_id}" # Build a mapping from phid to revision and identify # the data for the revision used to load this page. @@ -136,7 +174,7 @@ def get( "form": form, "flags": target_repo["commit_flags"] if target_repo else [], "existing_flags": existing_flags, - # "uplift_request_form": uplift_request_form, + "uplift_request_form": uplift_request_form, } return TemplateResponse( @@ -152,14 +190,6 @@ def post( form = TransplantRequestForm(request.POST) errors = [] - # TODO - see bug 1915695. - # uplift_request_form = UpliftRequestForm() - - # # Get the list of available uplift repos and populate the form with it. - # uplift_request_form.repository.choices = get_uplift_repos(api) - # uplift_request_form.revision_id.data = revision_id - # END TODO. - if not request.user.is_authenticated: errors.append("You must be logged in to request a landing") diff --git a/src/lando/urls.py b/src/lando/urls.py index d6c63b26..c3ec932d 100644 --- a/src/lando/urls.py +++ b/src/lando/urls.py @@ -31,6 +31,7 @@ path("", pages.Index.as_view()), path("D/", revisions.Revision.as_view(), name="revisions-page"), path("manage_api_key/", user_settings.manage_api_key, name="user-settings"), + path("uplift/", revisions.Uplift.as_view(), name="uplift-page"), ] # "API" endpoints ported from legacy API app.