Skip to content

Commit

Permalink
TDRD-225 Metadata Reviews page (#4003)
Browse files Browse the repository at this point in the history
  • Loading branch information
thanhz authored Jul 12, 2024
1 parent 630be9d commit 309a519
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 14 deletions.
4 changes: 4 additions & 0 deletions app/auth/TokenSecurity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ trait TokenSecurity extends OidcSecurity with I18nSupport {
consignmentTypeAction(consignmentId, "standard")(action)
}

def tnaUserAction(action: Request[AnyContent] => Future[Result]): Action[AnyContent] = secureAction.async { request =>
createResult(action, request, request.token.isTNAUser)
}

private def createResult(action: Request[AnyContent] => Future[Result], request: AuthenticatedRequest[AnyContent], isPermitted: Boolean) = {
if (isPermitted) {
action(request)
Expand Down
24 changes: 24 additions & 0 deletions app/controllers/MetadataReviewController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package controllers

import auth.TokenSecurity
import configuration.KeycloakConfiguration
import org.pac4j.play.scala.SecurityComponents
import play.api.mvc.{Action, AnyContent, Request}
import services.ConsignmentService

import javax.inject.Inject

class MetadataReviewController @Inject() (
val keycloakConfiguration: KeycloakConfiguration,
val controllerComponents: SecurityComponents,
val consignmentService: ConsignmentService
) extends TokenSecurity {

def metadataReviews(): Action[AnyContent] = tnaUserAction { implicit request: Request[AnyContent] =>
for {
consignments <- consignmentService.getConsignmentsForReview(request.token.bearerAccessToken)
} yield {
Ok(views.html.tna.metadataReviews(consignments))
}
}
}
7 changes: 7 additions & 0 deletions app/services/ConsignmentService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import graphql.codegen.GetConsignmentSummary.getConsignmentSummary
import graphql.codegen.GetConsignmentType.{getConsignmentType => gct}
import graphql.codegen.GetConsignments.getConsignments.Consignments
import graphql.codegen.GetConsignments.{getConsignments => gcs}
import graphql.codegen.GetConsignmentsForMetadataReview.{getConsignmentsForMetadataReview => gcfmr}
import graphql.codegen.GetFileCheckProgress.{getFileCheckProgress => gfcp}
import graphql.codegen.UpdateConsignmentSeriesId.updateConsignmentSeriesId
import graphql.codegen.types._
Expand All @@ -43,6 +44,7 @@ class ConsignmentService @Inject() (val graphqlConfiguration: GraphQLConfigurati
private val updateConsignmentSeriesIdClient = graphqlConfiguration.getClient[updateConsignmentSeriesId.Data, updateConsignmentSeriesId.Variables]()
private val getConsignments = graphqlConfiguration.getClient[gcs.Data, gcs.Variables]()
private val gctClient = graphqlConfiguration.getClient[gct.Data, gct.Variables]()
private val getConsignmentsForReviewClient = graphqlConfiguration.getClient[gcfmr.Data, gcfmr.Variables]()

def fileCheckProgress(consignmentId: UUID, token: BearerAccessToken): Future[gfcp.GetConsignment] = {
val variables = gfcp.Variables(consignmentId)
Expand Down Expand Up @@ -187,6 +189,11 @@ class ConsignmentService @Inject() (val graphqlConfiguration: GraphQLConfigurati
.map(data => data.consignments)
}

def getConsignmentsForReview(token: BearerAccessToken): Future[List[gcfmr.GetConsignmentsForMetadataReview]] = {
sendApiRequest(getConsignmentsForReviewClient, gcfmr.document, token, gcfmr.Variables())
.map(data => data.getConsignmentsForMetadataReview)
}

private def getFileFilters(metadataType: Option[String], fileIds: Option[List[UUID]], additionalProperties: Option[List[String]]): Option[FileFilters] = {
val metadataTypeFilter = metadataType match {
case None => additionalProperties.map(p => FileMetadataFilters(None, None, Some(p)))
Expand Down
4 changes: 2 additions & 2 deletions app/views/main.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*@
@import views.html.helper.CSPNonce
@import views.html.partials.feedbackLink
@(title: String, hasError: Boolean = false, isLoggedIn: Boolean = true, name: String = "", isJudgmentUser: Boolean = false, backLink: Option[Html] = None)(content: Html)(implicit messages: Messages, request: RequestHeader)
@(title: String, hasError: Boolean = false, isLoggedIn: Boolean = true, name: String = "", isJudgmentUser: Boolean = false, isTnaUser: Boolean = false, backLink: Option[Html] = None)(content: Html)(implicit messages: Messages, request: RequestHeader)

<!DOCTYPE html>
<html lang="en" class="govuk-template">
Expand Down Expand Up @@ -61,7 +61,7 @@
<button type="button" class="govuk-header__menu-button govuk-js-header-toggle" aria-controls="navigation" aria-label="Show or hide menu" hidden>
Menu</button>
<ul id="navigation" class="govuk-header__navigation-list">
@if(!isJudgmentUser) {
@if(!isJudgmentUser && !isTnaUser) {
<li class="govuk-header__navigation-item">
<a class="govuk-header__link" href="/view-transfers">
View transfers
Expand Down
4 changes: 2 additions & 2 deletions app/views/tna/homepage.scala.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
@(name:String)(implicit messages: Messages, request: RequestHeader)
@defining("Welcome to the Transfer Digital Records service") { title =>
@main(title, name = name) {
@main(title, name = name, isTnaUser = true) {
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l govuk-!-margin-bottom-9">@title</h1>
<a href="#" role="button" draggable="false" class="govuk-button" data-module="govuk-button">Transfers for Review</a>
<a href="@routes.MetadataReviewController.metadataReviews()" role="button" draggable="false" class="govuk-button" data-module="govuk-button">Transfers for Review</a>
</div>
</div>
}
Expand Down
42 changes: 42 additions & 0 deletions app/views/tna/metadataReviews.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@import graphql.codegen.GetConsignmentsForMetadataReview.{getConsignmentsForMetadataReview => gcfmr}
@(consignments: List[gcfmr.GetConsignmentsForMetadataReview])(implicit messages: Messages, request: RequestHeader)

@main("Metadata Reviews", isTnaUser= true) {
<div class="govuk-grid-row">
<div class="govuk-grid-column-full">

<span class="govuk-caption-l">Transfer Digital records</span>
<h1 class="govuk-heading-l">Metadata Reviews</h1>
<p class="govuk-body">These are requested reviews that have not been responded to.</p>

<table data-module="table-row-expander" class="govuk-table govuk-table--tdr-transfers govuk-!-margin-bottom-2">
<thead class="govuk-table__head">
<tr class="govuk-table__row">
<th scope="col" class="govuk-table__header">Consignment</th>
<th scope="col" class="govuk-table__header">Status</th>
<th scope="col" class="govuk-table__header">Department</th>
<th scope="col" class="govuk-table__header">Series</th>
<th scope="col" class="govuk-table__header"></th>
</tr>
</thead>
<tbody>
@for((consignment, index) <- consignments.zipWithIndex) {
<tr class="govuk-table__row">
<th scope="row" class="govuk-table__header">@consignment.consignmentReference</th>
<td class="govuk-table__cell">
<strong class="tdr-tag tdr-tag--green">Requested</strong>
</td>
<td class="govuk-table__cell">@consignment.transferringBodyName</td>
<td class="govuk-table__cell">@consignment.seriesName</td>
<td class="govuk-table__cell">
<div class="tdr-link-group">
<a href="#" class="govuk-link govuk-link--no-visited-state">View request</a>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ libraryDependencies ++= Seq(
"com.softwaremill.sttp.client" %% "async-http-client-backend-future" % sttpVersion,
"uk.gov.nationalarchives" %% "tdr-graphql-client" % "0.0.167",
"uk.gov.nationalarchives" %% "tdr-auth-utils" % "0.0.204",
"uk.gov.nationalarchives" %% "tdr-generated-graphql" % "0.0.377",
"uk.gov.nationalarchives" %% "tdr-generated-graphql" % "0.0.378",
"uk.gov.nationalarchives" %% "tdr-metadata-validation" % "0.0.33",
"uk.gov.nationalarchives" %% "s3-utils" % "0.1.191",
"uk.gov.nationalarchives" %% "sns-utils" % "0.1.191",
Expand Down
4 changes: 4 additions & 0 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,7 @@ GET /judgment/:consignmentId/upload
GET /judgment/:consignmentId/file-checks controllers.FileChecksController.judgmentFileChecksPage(consignmentId: java.util.UUID, uploadFailed: Option[String])
GET /judgment/:consignmentId/continue-transfer controllers.FileChecksController.judgmentCompleteTransfer(consignmentId: java.util.UUID)
GET /judgment/:consignmentId/transfer-complete controllers.TransferCompleteController.judgmentTransferComplete(consignmentId: java.util.UUID)


# Routes for TNA-User
GET /admin/metadata-review controllers.MetadataReviewController.metadataReviews()
6 changes: 3 additions & 3 deletions test/controllers/HomepageControllerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class HomepageControllerSpec extends FrontEndTestHelper {
|</a>""".stripMargin

val transfersForReviewButton: String =
"""<a href="#" role="button" draggable="false" class="govuk-button" data-module="govuk-button">Transfers for Review</a>"""
"""<a href="/admin/metadata-review" role="button" draggable="false" class="govuk-button" data-module="govuk-button">Transfers for Review</a>"""

"HomepageController GET" should {

Expand Down Expand Up @@ -77,7 +77,7 @@ class HomepageControllerSpec extends FrontEndTestHelper {
checkPageForStaticElements.checkContentOfPagesThatUseMainScala(homepagePageAsString, userType = userType, consignmentExists = false)
checkForContentOnHomepagePage(homepagePageAsString, userType = userType)
homepagePageAsString must include(viewTransferButton)
homepagePageAsString must not include (transfersForReviewButton)
homepagePageAsString must not include transfersForReviewButton
}

"render the judgment homepage page with an authenticated judgment user" in {
Expand All @@ -94,7 +94,7 @@ class HomepageControllerSpec extends FrontEndTestHelper {
checkPageForStaticElements.checkContentOfPagesThatUseMainScala(homepagePageAsString, userType = userType, consignmentExists = false)
checkForContentOnHomepagePage(homepagePageAsString, userType = userType)
homepagePageAsString must not include viewTransferButton
homepagePageAsString must not include (transfersForReviewButton)
homepagePageAsString must not include transfersForReviewButton
}

"render the DTA review homepage page with an authenticated tna user" in {
Expand Down
90 changes: 90 additions & 0 deletions test/controllers/MetadataReviewControllerSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package controllers

import com.github.tomakehurst.wiremock.WireMockServer
import configuration.GraphQLConfiguration
import org.scalatest.matchers.should.Matchers._
import play.api.Play.materializer
import play.api.http.Status.{FORBIDDEN, FOUND, OK}
import play.api.test.CSRFTokenHelper.CSRFRequest
import play.api.test.FakeRequest
import play.api.test.Helpers.{GET, contentAsString, contentType, defaultAwaitTimeout, redirectLocation, status}
import services.ConsignmentService
import testUtils.{CheckPageForStaticElements, FrontEndTestHelper}

import scala.concurrent.ExecutionContext

class MetadataReviewControllerSpec extends FrontEndTestHelper {
val wiremockServer = new WireMockServer(9006)

override def beforeEach(): Unit = {
wiremockServer.start()
}

override def afterEach(): Unit = {
wiremockServer.resetAll()
wiremockServer.stop()
}

val checkPageForStaticElements = new CheckPageForStaticElements

private val TNAUserType = "tna"

implicit val ec: ExecutionContext = ExecutionContext.global

"MetadataReviewController GET" should {

"render the metadata review page" in {
setGetConsignmentsForMetadataReviewResponse(wiremockServer)
val graphQLConfiguration = new GraphQLConfiguration(app.configuration)
val consignmentService = new ConsignmentService(graphQLConfiguration)
val controller = new MetadataReviewController(getValidTNAUserKeycloakConfiguration, getAuthorisedSecurityComponents, consignmentService)
val response = controller
.metadataReviews()
.apply(FakeRequest(GET, s"/metadata-review").withCSRFToken)
val metadataReviewPageAsString = contentAsString(response)

status(response) mustBe OK
contentType(response) mustBe Some("text/html")

checkPageForStaticElements.checkContentOfPagesThatUseMainScala(metadataReviewPageAsString, userType = TNAUserType, consignmentExists = false)
checkForExpectedMetadataReviewPageContent(metadataReviewPageAsString)
}

"return 403 if the metadata review page is accessed by a non TNA user" in {
val graphQLConfiguration = new GraphQLConfiguration(app.configuration)
val consignmentService = new ConsignmentService(graphQLConfiguration)
val controller = new MetadataReviewController(getValidKeycloakConfiguration, getAuthorisedSecurityComponents, consignmentService)
val response = controller
.metadataReviews()
.apply(FakeRequest(GET, s"/metadata-review").withCSRFToken)

status(response) mustBe FORBIDDEN
}

"redirect to the login page if the page is accessed by a logged out user" in {
val graphQLConfiguration = new GraphQLConfiguration(app.configuration)
val consignmentService = new ConsignmentService(graphQLConfiguration)
val controller = new MetadataReviewController(getValidKeycloakConfiguration, getUnauthorisedSecurityComponents, consignmentService)
val response = controller
.metadataReviews()
.apply(FakeRequest(GET, s"/metadata-review").withCSRFToken)

status(response) mustBe FOUND
redirectLocation(response).get must startWith("/auth/realms/tdr/protocol/openid-connect/auth")
}
}

def checkForExpectedMetadataReviewPageContent(metadataReviewPageAsString: String, consignmentExists: Boolean = true): Unit = {
metadataReviewPageAsString must include("<h1 class=\"govuk-heading-l\">Metadata Reviews</h1>")
metadataReviewPageAsString must include("""<th scope="col" class="govuk-table__header">Consignment</th>""")
metadataReviewPageAsString must include("""<th scope="col" class="govuk-table__header">Status</th>""")
metadataReviewPageAsString must include("""<th scope="col" class="govuk-table__header">Department</th>""")
metadataReviewPageAsString must include("""<th scope="col" class="govuk-table__header">Series</th>""")
metadataReviewPageAsString must include("""<th scope="col" class="govuk-table__header"></th>""")
metadataReviewPageAsString must include(s"""These are requested reviews that have not been responded to.""")
metadataReviewPageAsString must include(s"""<th scope="row" class="govuk-table__header">TDR-2024-TEST</th>""")
metadataReviewPageAsString must include(s"""<strong class="tdr-tag tdr-tag--green">Requested</strong>""")
metadataReviewPageAsString must include(s"""<td class="govuk-table__cell">TransferringBody</td>""")
metadataReviewPageAsString must include(s"""<td class="govuk-table__cell">SeriesName</td>""")
}
}
4 changes: 3 additions & 1 deletion test/testUtils/CheckPageForStaticElements.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ class CheckPageForStaticElements() {
| <a class="govuk-link" href="/sign-out">Sign me out</a>
| </div>
| </dialog>""".stripMargin)
if (userType == "judgment" || userType == "tna") {
page must not include ("View transfers")
}
if (userType == "judgment") {
checkHeaderOfSignedInPagesForFeedbackLink(page, survey = "5YDPSA")
page must not include ("View transfers")
page must include("""href="/judgment/faq">""")
page must include("""href="/judgment/help">""")
if (consignmentExists) {
Expand Down
25 changes: 20 additions & 5 deletions test/testUtils/FrontEndTestHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package testUtils

import cats.implicits.catsSyntaxOptionId
import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock.{containing, equalToJson, okJson, post, urlEqualTo}
import com.github.tomakehurst.wiremock.client.WireMock._
import com.github.tomakehurst.wiremock.stubbing.StubMapping
import com.nimbusds.oauth2.sdk.token.BearerAccessToken
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata
Expand All @@ -11,6 +11,7 @@ import graphql.codegen.AddBulkFileMetadata.addBulkFileMetadata.UpdateBulkFileMet
import graphql.codegen.AddBulkFileMetadata.{addBulkFileMetadata => abfm}
import graphql.codegen.AddConsignmentStatus.addConsignmentStatus.AddConsignmentStatus
import graphql.codegen.AddConsignmentStatus.{addConsignmentStatus => acs}
import graphql.codegen.AddFinalTransferConfirmation.{addFinalTransferConfirmation => aftc}
import graphql.codegen.DeleteFileMetadata.deleteFileMetadata.DeleteFileMetadata
import graphql.codegen.DeleteFileMetadata.{deleteFileMetadata => dfm}
import graphql.codegen.GetAllDescendants.getAllDescendantIds
Expand All @@ -28,11 +29,13 @@ import graphql.codegen.GetConsignments.getConsignments.Consignments.Edges
import graphql.codegen.GetConsignments.getConsignments.Consignments.Edges.Node
import graphql.codegen.GetConsignments.getConsignments.Consignments.Edges.Node.{ConsignmentStatuses => ecs}
import graphql.codegen.GetConsignments.{getConsignments => gc}
import graphql.codegen.GetConsignmentsForMetadataReview.{getConsignmentsForMetadataReview, getConsignmentsForMetadataReview => gcfmr}
import graphql.codegen.GetCustomMetadata.customMetadata.CustomMetadata.Values
import graphql.codegen.GetCustomMetadata.customMetadata.CustomMetadata.Values.Dependencies
import graphql.codegen.GetCustomMetadata.{customMetadata => cm}
import graphql.codegen.GetDisplayProperties.{displayProperties => dp}
import graphql.codegen.UpdateConsignmentStatus.{updateConsignmentStatus => ucs}
import graphql.codegen.UpdateTransferInitiated.{updateTransferInitiated => ut}
import graphql.codegen.types.DataType.{Boolean, DateTime, Integer, Text}
import graphql.codegen.types.PropertyType.{Defined, Supplied}
import io.circe.Printer
Expand All @@ -44,11 +47,13 @@ import org.mockito.Mockito._
import org.pac4j.core.client.Clients
import org.pac4j.core.config.Config
import org.pac4j.core.context.session.{SessionStore, SessionStoreFactory}
import org.pac4j.core.context.{CallContext, FrameworkParameters}
import org.pac4j.core.engine.DefaultSecurityLogic
import org.pac4j.core.http.ajax.AjaxRequestResolver
import org.pac4j.core.util.Pac4jConstants
import org.pac4j.oidc.client.OidcClient
import org.pac4j.oidc.config.OidcConfiguration
import org.pac4j.oidc.metadata.OidcOpMetadataResolver
import org.pac4j.oidc.profile.{OidcProfile, OidcProfileDefinition}
import org.pac4j.oidc.redirect.OidcRedirectionActionBuilder
import org.pac4j.play.PlayWebContext
Expand All @@ -71,10 +76,6 @@ import play.api.{Application, Configuration}
import services.Statuses.{InProgressValue, SeriesType}
import uk.gov.nationalarchives.tdr.GraphQLClient
import uk.gov.nationalarchives.tdr.keycloak.Token
import org.pac4j.core.context.{CallContext, FrameworkParameters}
import org.pac4j.oidc.metadata.OidcOpMetadataResolver
import graphql.codegen.AddFinalTransferConfirmation.{addFinalTransferConfirmation => aftc}
import graphql.codegen.UpdateTransferInitiated.{updateTransferInitiated => ut}
import viewsapi.FrontEndInfo

import java.io.File
Expand Down Expand Up @@ -361,6 +362,20 @@ trait FrontEndTestHelper extends PlaySpec with MockitoSugar with Injecting with
)
}

def setGetConsignmentsForMetadataReviewResponse(wiremockServer: WireMockServer): StubMapping = {
val client = new GraphQLConfiguration(app.configuration).getClient[gcfmr.Data, gcfmr.Variables]()
val consignment = gcfmr.GetConsignmentsForMetadataReview("TDR-2024-TEST", Some("SeriesName"), Some("TransferringBody"))
val getConsignmentsForReviewResponse: getConsignmentsForMetadataReview.Data = gcfmr.Data(List(consignment))
val data = client.GraphqlData(Some(getConsignmentsForReviewResponse))
val dataString: String = data.asJson.printWith(Printer(dropNullValues = false, ""))

wiremockServer.stubFor(
post(urlEqualTo("/graphql"))
.withRequestBody(containing("getConsignmentsForMetadataReview"))
.willReturn(okJson(dataString))
)
}

def setConsignmentViewTransfersResponse(
wiremockServer: WireMockServer,
consignmentType: String,
Expand Down

0 comments on commit 309a519

Please sign in to comment.