Skip to content

Commit

Permalink
HIPP-1533 - Add RequestProductionAccessSelectApis page
Browse files Browse the repository at this point in the history
  • Loading branch information
victorarbuesmallada committed Sep 20, 2024
1 parent d05a927 commit 86800d4
Show file tree
Hide file tree
Showing 17 changed files with 767 additions and 21 deletions.
31 changes: 31 additions & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -844,3 +844,34 @@ header {
margin-bottom: 0.8em;
font-size: 1rem;
}

.hip-warning-text {
display: inline-block;
padding: 20px;
padding-right: 40px;
border: 1px solid $light-mid-grey;
border-radius: 5px;
background-color: $off-white;

.govuk-warning-text__icon {
margin-left: 15px;
}

.govuk-warning-text__text {
padding-left: 40px;
}

.hip-warning-list {
li {
margin-top: 5px;
padding: 10px;
padding-left: 20px;
font-size: 1rem;
background-color: $light-mid-grey;
border-color: $govuk-border-colour;
font-weight: lighter;
list-style: none;
border-radius: 2px;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 2024 HM Revenue & Customs
*
* Licensed 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.
*/

package controllers.application

import controllers.actions.*
import controllers.helpers.{ApplicationApiBuilder, ErrorResultBuilder}
import controllers.routes
import forms.application.RequestProductionAccessSelectApisFormProvider
import models.requests.DataRequest
import models.{Mode, NormalMode}
import navigation.Navigator
import pages.AccessRequestApplicationIdPage
import pages.application.accessrequest.RequestProductionAccessSelectApisPage
import play.api.i18n.{I18nSupport, MessagesApi}
import play.api.mvc.{Action, AnyContent, MessagesControllerComponents, Result}
import repositories.AccessRequestSessionRepository
import services.ApiHubService
import uk.gov.hmrc.http.HeaderCarrier
import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController
import viewmodels.application.ApplicationApi
import views.html.application.RequestProductionAccessSelectApisView

import javax.inject.Inject
import scala.concurrent.{ExecutionContext, Future}

class RequestProductionAccessSelectApisController @Inject()(
override val messagesApi: MessagesApi,
sessionRepository: AccessRequestSessionRepository,
navigator: Navigator,
identify: IdentifierAction,
getData: AccessRequestDataRetrievalAction,
requireData: DataRequiredAction,
formProvider: RequestProductionAccessSelectApisFormProvider,
val controllerComponents: MessagesControllerComponents,
view: RequestProductionAccessSelectApisView,
applicationApiBuilder: ApplicationApiBuilder,
apiHubService: ApiHubService,
errorResultBuilder: ErrorResultBuilder,
)(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport:


def onPageLoad(mode: Mode): Action[AnyContent] = (identify andThen getData andThen requireData).async {
implicit request =>
viewWithApiApplications(
mode,
(applicationApis: Seq[ApplicationApi], applicationApisPendingRequest: Seq[ApplicationApi]) =>
Future.successful(Ok(view(preparedForm(applicationApis), mode, applicationApis, applicationApisPendingRequest)))
)
}

def onSubmit(mode: Mode): Action[AnyContent] = (identify andThen getData andThen requireData).async {
implicit request =>

viewWithApiApplications(
mode,
(applicationApis: Seq[ApplicationApi], applicationApisPendingRequest: Seq[ApplicationApi]) =>
formProvider(applicationApis.toSet).bindFromRequest().fold(
formWithErrors =>
Future.successful(BadRequest(view(formWithErrors, mode, applicationApis, applicationApisPendingRequest))),

selectedApiIds =>
for {
updatedAnswers <- Future.fromTry(request.userAnswers.set(RequestProductionAccessSelectApisPage, selectedApiIds))
_ <- sessionRepository.set(updatedAnswers)
} yield Redirect(navigator.nextPage(RequestProductionAccessSelectApisPage, mode, updatedAnswers))
)
)
}

private def partitionApplicationApis(applicationApis: Seq[ApplicationApi]): (Seq[ApplicationApi], Seq[ApplicationApi]) =
val (applicationApisPendingRequest, applicationApisNoPendingRequest) = applicationApis.partition(_.hasPendingAccessRequest)
val applicationsThatCanRequestAccess = applicationApisNoPendingRequest.filter(api => !api.isMissing && api.needsProductionAccessRequest)
(applicationsThatCanRequestAccess, applicationApisPendingRequest)

private def preparedForm(applicationApis: Seq[ApplicationApi])(implicit request: DataRequest[?]) =
val form = formProvider(applicationApis.toSet)
request.userAnswers.get(RequestProductionAccessSelectApisPage) match {
case None => form
case Some(value) => form.fill(value)
}

private def viewWithApiApplications(
mode: Mode,
result: (
applicationApis: Seq[ApplicationApi],
applicationApisPendingRequests: Seq[ApplicationApi]
) => Future[Result]
)(implicit hc: HeaderCarrier, request: DataRequest[?]): Future[Result] =
request.userAnswers.get(AccessRequestApplicationIdPage) match {
case Some(application) =>
applicationApiBuilder.build(application).flatMap(apis => result.tupled(partitionApplicationApis(apis)))
case _ => Future.successful(Redirect(routes.JourneyRecoveryController.onPageLoad()))
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@

package controllers.application

import config.FrontendAppConfig
import controllers.actions.{ApplicationAuthActionProvider, IdentifierAction}
import models.UserAnswers
import models.{NormalMode, UserAnswers}
import pages.AccessRequestApplicationIdPage
import play.api.i18n.I18nSupport
import play.api.mvc.{Action, AnyContent, MessagesControllerComponents}
Expand All @@ -33,17 +34,20 @@ class RequestProductionAccessStartController @Inject()(
identify: IdentifierAction,
accessRequestSessionRepository: AccessRequestSessionRepository,
clock: Clock,
applicationAuth: ApplicationAuthActionProvider)(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport {
applicationAuth: ApplicationAuthActionProvider,
appConfig: FrontendAppConfig,
)(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport {

def onPageLoad(id: String): Action[AnyContent] = (identify andThen applicationAuth(id, enrich = true)).async {
implicit request =>
for {
userAnswers <- Future.fromTry(UserAnswers(
id = request.identifierRequest.user.userId,
lastUpdated = clock.instant()
).set(AccessRequestApplicationIdPage, request.application))
id = request.identifierRequest.user.userId,
lastUpdated = clock.instant()
).set(AccessRequestApplicationIdPage, request.application))
_ <- accessRequestSessionRepository.set(userAnswers)
} yield Redirect(controllers.application.routes.RequestProductionAccessController.onPageLoad())

} yield Redirect(controllers.application.routes.RequestProductionAccessSelectApisController.onPageLoad(NormalMode))
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2024 HM Revenue & Customs
*
* Licensed 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.
*/

package forms.application

import forms.mappings.Mappings
import models.{AvailableEndpoints, Enumerable}
import play.api.data.Form
import play.api.data.Forms.set
import viewmodels.application.ApplicationApi

import javax.inject.Inject

class RequestProductionAccessSelectApisFormProvider @Inject() extends Mappings {

def apply(applicationAPIs: Set[ApplicationApi]): Form[Set[String]] =
val validApplicationAPIs = applicationAPIs.filterNot(_.isMissing)

given enumerableScopes: Enumerable[String] = (str: String) => {
validApplicationAPIs.collectFirst { case api if api.apiId == str => api.apiId }
}

Form(
"value" -> set(enumerable[String]("requestProductionAccessSelectApis.error.required"))
.verifying(nonEmptySet("requestProductionAccessSelectApis.error.required"))
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2024 HM Revenue & Customs
*
* Licensed 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.
*/

package pages.application.accessrequest

import pages.QuestionPage
import play.api.libs.json.JsPath

case object RequestProductionAccessSelectApisPage extends QuestionPage[Set[String]] {

override def path: JsPath = JsPath \ toString

override def toString: String = "requestProductionAccessSelectApis"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2024 HM Revenue & Customs
*
* Licensed 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.
*/

package viewmodels.checkAnswers.application.accessrequest

import controllers.application.routes
import models.{CheckMode, UserAnswers}
import pages.application.accessrequest.RequestProductionAccessSelectApisPage
import play.api.i18n.Messages
import play.twirl.api.HtmlFormat
import uk.gov.hmrc.govukfrontend.views.viewmodels.content.HtmlContent
import uk.gov.hmrc.govukfrontend.views.viewmodels.summarylist.SummaryListRow
import viewmodels.govuk.summarylist.*
import viewmodels.implicits.*

object RequestProductionAccessSelectApisSummary {

def row(answers: UserAnswers)(implicit messages: Messages): Option[SummaryListRow] =
answers.get(RequestProductionAccessSelectApisPage).map {
answers =>

val value = ValueViewModel(
HtmlContent(
answers.map {
answer => HtmlFormat.escape(messages(s"requestProductionAccessSelectApis.$answer")).toString
}
.mkString(",<br>")
)
)

SummaryListRowViewModel(
key = "requestProductionAccessSelectApis.checkYourAnswersLabel",
value = value,
actions = Seq(
ActionItemViewModel("site.change", routes.RequestProductionAccessSelectApisController.onPageLoad(CheckMode).url)
.withVisuallyHiddenText(messages("requestProductionAccessSelectApis.change.hidden"))
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
@*
* Copyright 2024 HM Revenue & Customs
*
* Licensed 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.
*@

@import viewmodels.application.ApplicationApi

@this(
layout: templates.Layout,
formHelper: FormWithCSRF,
govukErrorSummary: GovukErrorSummary,
govukCheckboxes: GovukCheckboxes,
govukButton: GovukButton,
govukWarningText: GovukWarningText
)

@(form: Form[?], mode: Mode, applicationApis: Seq[ApplicationApi], applicationApisPendingRequest: Seq[ApplicationApi])(implicit request: Request[?], messages: Messages)

@pendingRequestsContent() = {
<p class="govuk-body">
<strong class="govuk-warning-text__text ">
@messages("requestProductionAccessSelectApis.pendingRequests.heading")
</strong>
<ul class="hip-warning-list">
@applicationApisPendingRequest.map(applicationApi =>
<li>
{applicationApi.apiTitle}
</li>
)
</ul>
</p>
}

@layout(pageTitle = title(form, messages("requestProductionAccessSelectApis.title")), fullWidth = true) {

@formHelper(action = controllers.application.routes.RequestProductionAccessSelectApisController.onSubmit(mode), Symbol("autoComplete") -> "off") {

@if(form.errors.nonEmpty) {
@govukErrorSummary(ErrorSummaryViewModel(form, errorLinkOverrides = Map("value" -> "value_0")))
}

<h1 class="govuk-heading-xl">
@messages("requestProductionAccessSelectApis.heading")
</h1>

@if(applicationApisPendingRequest.nonEmpty){
@govukWarningText(WarningText(
iconFallbackText = Some("Warning"),
content = HtmlContent(pendingRequestsContent()),
classes = "hip-warning-text"
))
}

@govukCheckboxes(
CheckboxesViewModel(
form = form,
name = "value",
legend = Legend(),
items = applicationApis.zipWithIndex.map { case (applicationApi, index) =>
CheckboxItemViewModel(
content = Text(applicationApi.apiTitle),
fieldId = "value",
index = index,
value = applicationApi.apiId
)
}
)
)

@govukButton(
ButtonViewModel(messages("site.continue"))
)
}
}
Loading

0 comments on commit 86800d4

Please sign in to comment.