From 152f58c6ec382b089706629651c63e1844e9dbd7 Mon Sep 17 00:00:00 2001 From: Shnick <1132919+Shnick@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:43:05 +0000 Subject: [PATCH 1/2] BDOG-3337 add bobby reports page --- .../BobbyExplorerController.scala | 48 ---- .../bobby/BobbyExplorerController.scala | 90 ++++++++ .../BobbyRulesTrendController.scala | 6 +- .../{service => bobby}/BobbyService.scala | 6 +- .../view/BobbyExplorerPage.scala.html | 2 +- .../bobby/view/BobbyReportsPage.scala.html | 218 ++++++++++++++++++ .../view/BobbyRulesTrendPage.scala.html | 2 +- .../ServiceDependenciesConnector.scala | 13 +- .../connector/model/BobbyReport.scala | 66 ++++++ .../connector/model/BobbyRule.scala | 3 +- .../connector/model/RepoBobbyRules.scala | 38 +++ .../search/SearchIndex.scala | 3 +- .../view/IndexPage.scala.html | 3 +- .../bobby_violations_banner.scala.html | 3 +- .../partials/dependency_section.scala.html | 5 +- .../view/standard_layout.scala.html | 3 +- conf/app.routes | 6 +- .../{service => bobby}/BobbyServiceSpec.scala | 2 +- .../ServiceDependenciesConnectorSpec.scala | 71 +++++- 19 files changed, 509 insertions(+), 79 deletions(-) delete mode 100644 app/uk/gov/hmrc/cataloguefrontend/BobbyExplorerController.scala create mode 100644 app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyExplorerController.scala rename app/uk/gov/hmrc/cataloguefrontend/{ => bobby}/BobbyRulesTrendController.scala (96%) rename app/uk/gov/hmrc/cataloguefrontend/{service => bobby}/BobbyService.scala (97%) rename app/uk/gov/hmrc/cataloguefrontend/{ => bobby}/view/BobbyExplorerPage.scala.html (98%) create mode 100644 app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyReportsPage.scala.html rename app/uk/gov/hmrc/cataloguefrontend/{ => bobby}/view/BobbyRulesTrendPage.scala.html (97%) create mode 100644 app/uk/gov/hmrc/cataloguefrontend/connector/model/BobbyReport.scala create mode 100644 app/uk/gov/hmrc/cataloguefrontend/connector/model/RepoBobbyRules.scala rename test/uk/gov/hmrc/cataloguefrontend/{service => bobby}/BobbyServiceSpec.scala (99%) diff --git a/app/uk/gov/hmrc/cataloguefrontend/BobbyExplorerController.scala b/app/uk/gov/hmrc/cataloguefrontend/BobbyExplorerController.scala deleted file mode 100644 index fb17521de..000000000 --- a/app/uk/gov/hmrc/cataloguefrontend/BobbyExplorerController.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2023 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 uk.gov.hmrc.cataloguefrontend - -import play.api.mvc.{Action, AnyContent, MessagesControllerComponents, RequestHeader} -import uk.gov.hmrc.cataloguefrontend.auth.CatalogueAuthBuilders -import uk.gov.hmrc.cataloguefrontend.connector.ServiceDependenciesConnector -import uk.gov.hmrc.cataloguefrontend.service.BobbyService -import uk.gov.hmrc.cataloguefrontend.view.html.BobbyExplorerPage -import uk.gov.hmrc.internalauth.client.FrontendAuthComponents -import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendController - -import javax.inject.Inject -import scala.concurrent.ExecutionContext - -class BobbyExplorerController @Inject() ( - override val mcc : MessagesControllerComponents, - page : BobbyExplorerPage, - bobbyService : BobbyService, - serviceDeps : ServiceDependenciesConnector, - override val auth: FrontendAuthComponents -)(using - override val ec: ExecutionContext -) extends FrontendController(mcc) - with CatalogueAuthBuilders: - - def list(selector: Option[String]): Action[AnyContent] = - BasicAuthAction.async: request => - given RequestHeader = request - for - rules <- bobbyService.getRules() - counts <- serviceDeps.getBobbyRuleViolations() - response = Ok(page(rules, counts)) - yield response diff --git a/app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyExplorerController.scala b/app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyExplorerController.scala new file mode 100644 index 000000000..da22ec504 --- /dev/null +++ b/app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyExplorerController.scala @@ -0,0 +1,90 @@ +/* + * Copyright 2023 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 uk.gov.hmrc.cataloguefrontend.bobby + +import cats.data.EitherT +import play.api.data.{Form, Forms} +import play.api.mvc.{Action, AnyContent, MessagesControllerComponents, MessagesRequest, Result, RequestHeader} +import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendController +import uk.gov.hmrc.internalauth.client.FrontendAuthComponents +import uk.gov.hmrc.cataloguefrontend.auth.CatalogueAuthBuilders +import uk.gov.hmrc.cataloguefrontend.bobby.view.html.{BobbyExplorerPage, BobbyReportsPage} +import uk.gov.hmrc.cataloguefrontend.connector.{RepoType, ServiceDependenciesConnector, TeamsAndRepositoriesConnector} +import uk.gov.hmrc.cataloguefrontend.model.{DigitalService, SlugInfoFlag, TeamName} + +import javax.inject.Inject +import scala.concurrent.{ExecutionContext, Future} + +class BobbyExplorerController @Inject() ( + teamsAndReposConnector : TeamsAndRepositoriesConnector +, serviceDeps : ServiceDependenciesConnector +, bobbyService : BobbyService +, bobbyReportsPage : BobbyReportsPage +, bobbyExplorerPage : BobbyExplorerPage +, override val mcc : MessagesControllerComponents +, override val auth : FrontendAuthComponents +)(using + override val ec: ExecutionContext +) extends FrontendController(mcc) + with CatalogueAuthBuilders: + + def bobbyReports(team: Option[TeamName], digitalService: Option[DigitalService], flag: Option[String]): Action[AnyContent] = + BasicAuthAction.async: request => + given MessagesRequest[AnyContent] = request + ( for + teams <- EitherT.right[Result](teamsAndReposConnector.allTeams()) + digitalServices <- EitherT.right[Result](teamsAndReposConnector.allDigitalServices()) + form = BobbyReportFilter.form.bindFromRequest() + filter <- EitherT.fromEither[Future](form.fold( + formErrors => Left(BadRequest(bobbyReportsPage(form, teams, digitalServices, results = None))) + , formObject => Right(formObject) + )) + results <- EitherT.right[Result]: + serviceDeps.bobbyReports(filter.team, filter.digitalService, filter.repoType, filter.flag) + yield + Ok(bobbyReportsPage(form, teams, digitalServices, results = Some(results))) + ).merge + + def list(selector: Option[String]): Action[AnyContent] = + BasicAuthAction.async: request => + given RequestHeader = request + for + rules <- bobbyService.getRules() + counts <- serviceDeps.getBobbyRuleViolations() + yield Ok(bobbyExplorerPage(rules, counts)) + +case class BobbyReportFilter( + team : Option[TeamName] = None +, digitalService: Option[DigitalService] = None +, repoType : Option[RepoType] = None +, flag : SlugInfoFlag = SlugInfoFlag.Latest +, isActive : Option[Boolean] = None +, exempt : Boolean = false +) + +object BobbyReportFilter: + lazy val form: Form[BobbyReportFilter] = + Form( + Forms.mapping( + "team" -> Forms.optional(Forms.of[TeamName]) + , "digitalService" -> Forms.optional(Forms.of[DigitalService]) + , "repoType" -> Forms.optional(Forms.of[RepoType]) + , "flag" -> Forms.optional(Forms.of[SlugInfoFlag]).transform(_.getOrElse(SlugInfoFlag.Latest), Some.apply) + , "isActive" -> Forms.optional(Forms.boolean) + , "exempt" -> Forms.boolean + )(BobbyReportFilter.apply)(f => Some(Tuple.fromProductTyped(f))) + ) diff --git a/app/uk/gov/hmrc/cataloguefrontend/BobbyRulesTrendController.scala b/app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyRulesTrendController.scala similarity index 96% rename from app/uk/gov/hmrc/cataloguefrontend/BobbyRulesTrendController.scala rename to app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyRulesTrendController.scala index 419735d7d..6ed86ae26 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/BobbyRulesTrendController.scala +++ b/app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyRulesTrendController.scala @@ -14,16 +14,16 @@ * limitations under the License. */ -package uk.gov.hmrc.cataloguefrontend +package uk.gov.hmrc.cataloguefrontend.bobby import cats.data.EitherT import cats.instances.future._ import play.api.mvc.{Action, AnyContent, MessagesControllerComponents, MessagesRequest, Result} import uk.gov.hmrc.cataloguefrontend.auth.CatalogueAuthBuilders +import uk.gov.hmrc.cataloguefrontend.bobby.view.html.BobbyRulesTrendPage import uk.gov.hmrc.cataloguefrontend.connector.ServiceDependenciesConnector import uk.gov.hmrc.cataloguefrontend.model.{SlugInfoFlag, VersionRange} import uk.gov.hmrc.cataloguefrontend.serviceconfigs.ServiceConfigsConnector -import uk.gov.hmrc.cataloguefrontend.view.html.BobbyRulesTrendPage import uk.gov.hmrc.internalauth.client.FrontendAuthComponents import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendController @@ -134,7 +134,7 @@ class BobbyRulesTrendController @Inject() ( object BobbyRulesTrendController: def display(group: String, artefact: String, versionRange: VersionRange): String = - uk.gov.hmrc.cataloguefrontend.routes.BobbyRulesTrendController.display( + uk.gov.hmrc.cataloguefrontend.bobby.routes.BobbyRulesTrendController.display( `rules[]` = Seq(s"$group:$artefact:${versionRange.range}") ).toString diff --git a/app/uk/gov/hmrc/cataloguefrontend/service/BobbyService.scala b/app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyService.scala similarity index 97% rename from app/uk/gov/hmrc/cataloguefrontend/service/BobbyService.scala rename to app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyService.scala index fe70e4cfa..585583bae 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/service/BobbyService.scala +++ b/app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyService.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package uk.gov.hmrc.cataloguefrontend.service +package uk.gov.hmrc.cataloguefrontend.bobby import uk.gov.hmrc.cataloguefrontend.connector.model.{BobbyRule, BobbyRuleSet} import uk.gov.hmrc.cataloguefrontend.serviceconfigs.ServiceConfigsConnector @@ -33,8 +33,6 @@ class BobbyService @Inject() ( )(using ExecutionContext): def getRules()(using HeaderCarrier): Future[BobbyRulesView] = - val today = LocalDate.now(clock) - def ordering(dateAscending: Boolean) = Ordering.by: (br: BobbyRule) => ( br.from.atStartOfDayInstant.toEpochMilli.pipe(x => if dateAscending then x else -x) @@ -42,6 +40,7 @@ class BobbyService @Inject() ( , br.artefact ) + val today = LocalDate.now(clock) serviceConfigsConnector .bobbyRules() .map: ruleset => @@ -55,7 +54,6 @@ class BobbyService @Inject() ( BobbyRuleSet(activeLibraries.sorted, activePlugins.sorted) } ) - end getRules end BobbyService diff --git a/app/uk/gov/hmrc/cataloguefrontend/view/BobbyExplorerPage.scala.html b/app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyExplorerPage.scala.html similarity index 98% rename from app/uk/gov/hmrc/cataloguefrontend/view/BobbyExplorerPage.scala.html rename to app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyExplorerPage.scala.html index 22e25d867..bf35da4cc 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/view/BobbyExplorerPage.scala.html +++ b/app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyExplorerPage.scala.html @@ -14,7 +14,7 @@ * limitations under the License. *@ -@import uk.gov.hmrc.cataloguefrontend.{BobbyRulesTrendController, routes => appRoutes} +@import uk.gov.hmrc.cataloguefrontend.bobby.{BobbyRulesTrendController, routes => appRoutes} @import uk.gov.hmrc.cataloguefrontend.connector.RepoType @import uk.gov.hmrc.cataloguefrontend.connector.model.{BobbyRule, BobbyRuleSet, DependencyScope} @import uk.gov.hmrc.cataloguefrontend.dependency.DependencyExplorerController diff --git a/app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyReportsPage.scala.html b/app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyReportsPage.scala.html new file mode 100644 index 000000000..7e3ff745c --- /dev/null +++ b/app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyReportsPage.scala.html @@ -0,0 +1,218 @@ +@* + * Copyright 2023 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 uk.gov.hmrc.cataloguefrontend.{routes => appRoutes} +@import uk.gov.hmrc.cataloguefrontend.bobby.BobbyReportFilter +@import uk.gov.hmrc.cataloguefrontend.connector.{GitHubTeam, RepoType} +@import uk.gov.hmrc.cataloguefrontend.connector.model.BobbyReport +@import uk.gov.hmrc.cataloguefrontend.util.MarkdownLoader +@import uk.gov.hmrc.cataloguefrontend.util.DateHelper._ +@import views.html.helper.{FieldConstructor, select} + +@this( + teamNamesPartial: partials.TeamNamesPartial +) + +@(form : Form[BobbyReportFilter] +, teams : Seq[GitHubTeam] +, digitalServices: Seq[DigitalService] +, results : Option[Seq[BobbyReport]] +, now : java.time.LocalDate = java.time.LocalDate.now() +)(implicit + messages : Messages +, request : RequestHeader +) + +@implicitField: FieldConstructor = @{ FieldConstructor(catalogueFieldConstructor.f) } + +@standard_layout("Bobby Reports", active = "health") { +

Bobby Reports

+ +
+
+
+
+
+
+
+
+
+
+ @select( + field = form("repoType") + , options = RepoType.values.filterNot(_ == RepoType.Prototype).map(ds => ds.asString -> ds.asString) + , Symbol("_default") -> "All" + , Symbol("_label") -> "Repository Type" + , Symbol("_labelClass") -> "form-label" + , Symbol("id") -> "select-repo-type" + , Symbol("class") -> "form-select" + ) +
+
+ @select( + field = form("team") + , options = teams.map(t => t.name.asString -> t.name.asString) + , Symbol("_default") -> "All" + , Symbol("_label") -> "Team" + , Symbol("_labelClass") -> "form-label" + , Symbol("id") -> "select-team" + , Symbol("class") -> "form-select" + ) +
+
+ @select( + field = form("digitalService") + , options = digitalServices.map(ds => ds.asString -> ds.asString) + , Symbol("_default") -> "All" + , Symbol("_label") -> "Digital Service" + , Symbol("_labelClass") -> "form-label" + , Symbol("id") -> "select-digital-service" + , Symbol("class") -> "form-select" + ) +
+
+ @select( + field = form("flag") + , options = SlugInfoFlag.values.toSeq.filterNot(_ == SlugInfoFlag.ForEnvironment(Environment.Integration)).map(env => env.asString -> env.displayString) + , Symbol("_label") -> "Environment" + , Symbol("_labelClass") -> "form-label" + , Symbol("id") -> "select-environment" + , Symbol("class") -> "form-select" + ) +
+
+ @select( + field = form("isActive") + , options = Seq("true" -> "Active", "false" -> "Upcoming") + , Symbol("_default") -> "All" + , Symbol("_label") -> "Status" + , Symbol("_labelClass") -> "form-label" + , Symbol("id") -> "select-bobby-status" + , Symbol("class") -> "form-select" + ) +
+
+
+ + +
+
+
+
+ + @defining( + results + .getOrElse(Seq.empty[BobbyReport]) + .flatMap: x => + x.violations.collect: + case v + if v.exempt == form.get.exempt + && ( form.get.isActive.fold(true): + case true => now.isAfter(v.from) + case false => now.isBefore(v.from) || now.isEqual(v.from) + ) => (x.repoName, x.repoVersion, x.repoType, v) + ) { + case Nil => { +

This search did not return any results.

+
+} + + diff --git a/app/uk/gov/hmrc/cataloguefrontend/view/BobbyRulesTrendPage.scala.html b/app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyRulesTrendPage.scala.html similarity index 97% rename from app/uk/gov/hmrc/cataloguefrontend/view/BobbyRulesTrendPage.scala.html rename to app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyRulesTrendPage.scala.html index 7381e5f56..a27c5ab0c 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/view/BobbyRulesTrendPage.scala.html +++ b/app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyRulesTrendPage.scala.html @@ -14,7 +14,7 @@ * limitations under the License. *@ -@import uk.gov.hmrc.cataloguefrontend.{BobbyRulesTrendController, routes => appRoutes } +@import uk.gov.hmrc.cataloguefrontend.bobby.{BobbyRulesTrendController, routes => appRoutes } @import uk.gov.hmrc.cataloguefrontend.connector.model.BobbyRule @import helper._ diff --git a/app/uk/gov/hmrc/cataloguefrontend/connector/ServiceDependenciesConnector.scala b/app/uk/gov/hmrc/cataloguefrontend/connector/ServiceDependenciesConnector.scala index 00dc691e0..8dbe41999 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/connector/ServiceDependenciesConnector.scala +++ b/app/uk/gov/hmrc/cataloguefrontend/connector/ServiceDependenciesConnector.scala @@ -18,7 +18,7 @@ package uk.gov.hmrc.cataloguefrontend.connector import play.api.libs.json.Reads import uk.gov.hmrc.cataloguefrontend.connector.model._ -import uk.gov.hmrc.cataloguefrontend.model.{ServiceName, SlugInfoFlag, TeamName, Version, VersionRange} +import uk.gov.hmrc.cataloguefrontend.model.{DigitalService, ServiceName, SlugInfoFlag, TeamName, Version, VersionRange} import uk.gov.hmrc.cataloguefrontend.service.{ServiceDependencies, SlugVersionInfo} import uk.gov.hmrc.http.{HeaderCarrier, HttpReads} import uk.gov.hmrc.http.client.HttpClientV2 @@ -126,6 +126,17 @@ class ServiceDependenciesConnector @Inject() ( .execute[BobbyRulesSummary] .map(_.summary) + def bobbyReports( + teamName : Option[TeamName] + , digitalService: Option[DigitalService] + , repoType : Option[RepoType] + , flag : SlugInfoFlag + )(using HeaderCarrier): Future[Seq[BobbyReport]] = + given Reads[BobbyReport] = BobbyReport.reads + httpClientV2 + .get(url"$servicesDependenciesBaseUrl/api/bobbyReports?team=${teamName.map(_.asString)}&digitalService=${digitalService.map(_.asString)}&repoType=${repoType.map(_.asString)}&flag=${flag.asString}") + .execute[Seq[BobbyReport]] + def getHistoricBobbyRuleViolations(query: List[String], from: LocalDate, to: LocalDate)(using HeaderCarrier): Future[HistoricBobbyRulesSummary] = given Reads[HistoricBobbyRulesSummary] = HistoricBobbyRulesSummary.reads httpClientV2 diff --git a/app/uk/gov/hmrc/cataloguefrontend/connector/model/BobbyReport.scala b/app/uk/gov/hmrc/cataloguefrontend/connector/model/BobbyReport.scala new file mode 100644 index 000000000..5ddafb668 --- /dev/null +++ b/app/uk/gov/hmrc/cataloguefrontend/connector/model/BobbyReport.scala @@ -0,0 +1,66 @@ +/* + * Copyright 2023 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 uk.gov.hmrc.cataloguefrontend.connector.model + +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Json, Reads, __} +import uk.gov.hmrc.cataloguefrontend.model.{VersionRange, Version} +import uk.gov.hmrc.cataloguefrontend.connector.RepoType + +import java.time.{Instant, LocalDate} + +case class BobbyReport( + repoName : String +, repoVersion : Version +, repoType : RepoType +, violations : Seq[BobbyReport.Violation] +, lastUpdated : Instant +) + +object BobbyReport: + val reads: Reads[BobbyReport] = + given Reads[Violation] = Violation.reads + ( (__ \ "repoName" ).read[String] + ~ (__ \ "repoVersion").read[Version](Version.format) + ~ (__ \ "repoType" ).read[RepoType] + ~ (__ \ "violations" ).read[Seq[Violation]] + ~ (__ \ "lastUpdated").read[Instant] + )(BobbyReport.apply _) + + case class Violation( + depGroup : String + , depArtefact: String + , depVersion : Version + , depScopes : Set[DependencyScope] + , range : VersionRange + , reason : String + , from : LocalDate + , exempt : Boolean + ) + + object Violation: + val reads: Reads[Violation] = + given Reads[VersionRange] = VersionRange.format + ( (__ \ "depGroup" ).read[String] + ~ (__ \ "depArtefact").read[String] + ~ (__ \ "depVersion" ).read[Version](Version.format) + ~ (__ \ "depScopes" ).read[Set[DependencyScope]] + ~ (__ \ "range" ).read[VersionRange] + ~ (__ \ "reason" ).read[String] + ~ (__ \ "from" ).read[LocalDate] + ~ (__ \ "exempt" ).read[Boolean] + )(Violation.apply _) diff --git a/app/uk/gov/hmrc/cataloguefrontend/connector/model/BobbyRule.scala b/app/uk/gov/hmrc/cataloguefrontend/connector/model/BobbyRule.scala index 978f1b4b2..705923680 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/connector/model/BobbyRule.scala +++ b/app/uk/gov/hmrc/cataloguefrontend/connector/model/BobbyRule.scala @@ -1,5 +1,5 @@ /* - * Copyright 2023 HM Revenue & Customs + * 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. @@ -22,7 +22,6 @@ import uk.gov.hmrc.cataloguefrontend.model.VersionRange import java.time.LocalDate - case class BobbyRule( group : String, artefact : String, diff --git a/app/uk/gov/hmrc/cataloguefrontend/connector/model/RepoBobbyRules.scala b/app/uk/gov/hmrc/cataloguefrontend/connector/model/RepoBobbyRules.scala new file mode 100644 index 000000000..6254b0813 --- /dev/null +++ b/app/uk/gov/hmrc/cataloguefrontend/connector/model/RepoBobbyRules.scala @@ -0,0 +1,38 @@ +/* + * Copyright 2023 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 uk.gov.hmrc.cataloguefrontend.connector.model + +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Reads, __} +import uk.gov.hmrc.cataloguefrontend.model.Version +import uk.gov.hmrc.cataloguefrontend.connector.RepoType + +case class RepoBobbyRules( + repoName : String +, repoVersion: Version +, repoType : RepoType +, bobbyRules : Seq[BobbyRule] +) + +object RepoBobbyRules: + val reads: Reads[RepoBobbyRules] = + given Reads[BobbyRule] = BobbyRule.reads + ( (__ \ "repoName" ).read[String] + ~ (__ \ "repoVersion").read[Version](Version.format) + ~ (__ \ "repoType" ).read[RepoType] + ~ (__ \ "bobbyRules" ).read[Seq[BobbyRule]] + )(RepoBobbyRules.apply) diff --git a/app/uk/gov/hmrc/cataloguefrontend/search/SearchIndex.scala b/app/uk/gov/hmrc/cataloguefrontend/search/SearchIndex.scala index 39c14b019..b8e3e46af 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/search/SearchIndex.scala +++ b/app/uk/gov/hmrc/cataloguefrontend/search/SearchIndex.scala @@ -20,6 +20,7 @@ package uk.gov.hmrc.cataloguefrontend.search import play.api.Configuration import uk.gov.hmrc.cataloguefrontend.routes as catalogueRoutes import uk.gov.hmrc.cataloguefrontend.connector.{RepoType, TeamsAndRepositoriesConnector, UserManagementConnector} +import uk.gov.hmrc.cataloguefrontend.bobby.routes as bobbyRoutes import uk.gov.hmrc.cataloguefrontend.createrepository.routes as createRepoRoutes import uk.gov.hmrc.cataloguefrontend.dependency.routes as dependencyRoutes import uk.gov.hmrc.cataloguefrontend.deployments.routes as deployRoutes @@ -67,7 +68,7 @@ class SearchIndex @Inject()( private val hardcodedLinks = List( SearchTerm("explorer", "dependency", dependencyRoutes.DependencyExplorerController.landing.url, 1.0f, Set("depex")), - SearchTerm("explorer", "bobby", catalogueRoutes.BobbyExplorerController.list().url, 1.0f), + SearchTerm("explorer", "bobby", bobbyRoutes.BobbyExplorerController.list().url, 1.0f), SearchTerm("explorer", "jdk", catalogueRoutes.JdkVersionController.compareAllEnvironments().url, 1.0f, Set("jdk", "jre")), SearchTerm("explorer", "leaks", leakRoutes.LeakDetectionController.ruleSummaries.url, 1.0f, Set("lds")), SearchTerm("page", "whats running where (wrw)", wrwRoutes.WhatsRunningWhereController.releases().url, 1.0f, Set("wrw")), diff --git a/app/uk/gov/hmrc/cataloguefrontend/view/IndexPage.scala.html b/app/uk/gov/hmrc/cataloguefrontend/view/IndexPage.scala.html index 249e7f29d..79e0c17ed 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/view/IndexPage.scala.html +++ b/app/uk/gov/hmrc/cataloguefrontend/view/IndexPage.scala.html @@ -15,6 +15,7 @@ *@ @import uk.gov.hmrc.cataloguefrontend.{routes => appRoutes} +@import uk.gov.hmrc.cataloguefrontend.bobby.{routes => bobbyRoutes} @import uk.gov.hmrc.cataloguefrontend.connector.ConfluenceConnector @import uk.gov.hmrc.cataloguefrontend.deployments.{routes => deployRoutes} @import uk.gov.hmrc.cataloguefrontend.dependency.{routes => dependencyRoutes} @@ -45,7 +46,7 @@

Welcome to the MDTP Catalogue

what dependencies and config they use, and who maintains them. You can also shutter your service to the outside world, - view bobby rules, see what + view bobby rules, see what JDK versions are in use, discover which service corresponds to a given URL, and more to come! diff --git a/app/uk/gov/hmrc/cataloguefrontend/view/partials/bobby_violations_banner.scala.html b/app/uk/gov/hmrc/cataloguefrontend/view/partials/bobby_violations_banner.scala.html index b2261e2da..655b8db7f 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/view/partials/bobby_violations_banner.scala.html +++ b/app/uk/gov/hmrc/cataloguefrontend/view/partials/bobby_violations_banner.scala.html @@ -16,6 +16,7 @@ @import uk.gov.hmrc.cataloguefrontend.connector.model.Dependency @import uk.gov.hmrc.cataloguefrontend.{routes => appRoutes} +@import uk.gov.hmrc.cataloguefrontend.bobby.{routes => bobbyRoutes} @import uk.gov.hmrc.cataloguefrontend.teams.{routes => teamRoutes} @(environment : Option[Environment] @@ -29,7 +30,7 @@ @teamName.map { name => s"-${e.asString}")}" href="@teamRoutes.TeamsController.outOfDateTeamDependencies(name)">See Dependencies. }.getOrElse { - Please review the lists below, and refer to s"-${e.asString}")}" href="@appRoutes.BobbyExplorerController.list()">Bobby Rules. + Please review the lists below, and refer to s"-${e.asString}")}" href="@bobbyRoutes.BobbyExplorerController.list()">Bobby Rules. } } diff --git a/app/uk/gov/hmrc/cataloguefrontend/view/partials/dependency_section.scala.html b/app/uk/gov/hmrc/cataloguefrontend/view/partials/dependency_section.scala.html index b217bc9e5..f33c568e3 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/view/partials/dependency_section.scala.html +++ b/app/uk/gov/hmrc/cataloguefrontend/view/partials/dependency_section.scala.html @@ -15,6 +15,7 @@ *@ @import uk.gov.hmrc.cataloguefrontend.{ routes => appRoutes } +@import uk.gov.hmrc.cataloguefrontend.bobby.{routes => bobbyRoutes} @import uk.gov.hmrc.cataloguefrontend.vulnerabilities.{ routes => vulnerabilityRoutes} @import uk.gov.hmrc.cataloguefrontend.connector.model.{Dependency, ImportedBy, VersionState} @import uk.gov.hmrc.cataloguefrontend.connector.model.DependencyScope.It @@ -74,8 +75,8 @@ }
@dependency.versionState match { - case Some(VersionState.BobbyRulePending (violation)) => { See rule } - case Some(VersionState.BobbyRuleViolated(violation)) => { See rule } + case Some(VersionState.BobbyRulePending (violation)) => { See rule } + case Some(VersionState.BobbyRuleViolated(violation)) => { See rule } case _ => { } }
diff --git a/app/uk/gov/hmrc/cataloguefrontend/view/standard_layout.scala.html b/app/uk/gov/hmrc/cataloguefrontend/view/standard_layout.scala.html index d58474986..9c8ec69fb 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/view/standard_layout.scala.html +++ b/app/uk/gov/hmrc/cataloguefrontend/view/standard_layout.scala.html @@ -15,6 +15,7 @@ *@ @import uk.gov.hmrc.cataloguefrontend.{routes => appRoutes } +@import uk.gov.hmrc.cataloguefrontend.bobby.{routes => bobbyRoutes} @import uk.gov.hmrc.cataloguefrontend.cost.{routes => costRoutes} @import uk.gov.hmrc.cataloguefrontend.dependency.{routes => dependencyRoutes} @import uk.gov.hmrc.cataloguefrontend.prcommenter.{routes => prcommenterRoutes} @@ -162,7 +163,7 @@
  • Health-Indicators
  • Platform Initiatives
  • -
  • Bobby Explorer
  • +
  • Bobby Explorer
  • Leak Detection - Rules
  • Leak Detection - Repositories
  • diff --git a/conf/app.routes b/conf/app.routes index 86939e914..f534a6527 100644 --- a/conf/app.routes +++ b/conf/app.routes @@ -67,8 +67,6 @@ GET /dependencies/:name/:version/graphs uk.gov.hmrc.cataloguefro GET /dependencyexplorer uk.gov.hmrc.cataloguefrontend.dependency.DependencyExplorerController.landing GET /dependencyexplorer/results uk.gov.hmrc.cataloguefrontend.dependency.DependencyExplorerController.search(group: String ?= "", artefact: String ?= "", versionRange: Option[String] ?= None, team: Option[TeamName] ?= None, flag: Option[String] ?= None, `scope[]`: Option[Seq[String]] ?= None, `repoType[]`: Option[Seq[String]] ?= None, asCsv: Boolean ?= false) -GET /bobbyrulestrend uk.gov.hmrc.cataloguefrontend.BobbyRulesTrendController.display(`rules[]`: Seq[String] ?= Seq.empty, from: java.time.LocalDate ?= java.time.LocalDate.now().minusYears(2), to: java.time.LocalDate ?= java.time.LocalDate.now()) - GET /jdkexplorer/environment uk.gov.hmrc.cataloguefrontend.JdkVersionController.findLatestVersions(env: String, teamName: Option[TeamName] ?= None) GET /jdkexplorer uk.gov.hmrc.cataloguefrontend.JdkVersionController.compareAllEnvironments(teamName: Option[TeamName] ?= None) @@ -83,7 +81,9 @@ GET /config/warnings/search/results uk.gov.hmrc.cataloguefro GET /cost-explorer uk.gov.hmrc.cataloguefrontend.cost.CostController.costExplorer(team: Option[TeamName] ?= None, asCSV: Boolean ?= false) -GET /bobbyrules uk.gov.hmrc.cataloguefrontend.BobbyExplorerController.list(selector: Option[String] ?= None) +GET /bobby-reports uk.gov.hmrc.cataloguefrontend.bobby.BobbyExplorerController.bobbyReports(teamName: Option[TeamName] ?= None, digitalService: Option[DigitalService] ?= None, flag: Option[String] ?= None) +GET /bobbyrules uk.gov.hmrc.cataloguefrontend.bobby.BobbyExplorerController.list(selector: Option[String] ?= None) +GET /bobbyrulestrend uk.gov.hmrc.cataloguefrontend.bobby.BobbyRulesTrendController.display(`rules[]`: Seq[String] ?= Seq.empty, from: java.time.LocalDate ?= java.time.LocalDate.now().minusYears(2), to: java.time.LocalDate ?= java.time.LocalDate.now()) GET /pr-commenter/recommendations uk.gov.hmrc.cataloguefrontend.prcommenter.PrCommenterController.recommendations(name: Option[String] ?= None, teamName: Option[TeamName] ?= None, commentType: Option[String] ?= None) diff --git a/test/uk/gov/hmrc/cataloguefrontend/service/BobbyServiceSpec.scala b/test/uk/gov/hmrc/cataloguefrontend/bobby/BobbyServiceSpec.scala similarity index 99% rename from test/uk/gov/hmrc/cataloguefrontend/service/BobbyServiceSpec.scala rename to test/uk/gov/hmrc/cataloguefrontend/bobby/BobbyServiceSpec.scala index ccc485661..c4c64226b 100644 --- a/test/uk/gov/hmrc/cataloguefrontend/service/BobbyServiceSpec.scala +++ b/test/uk/gov/hmrc/cataloguefrontend/bobby/BobbyServiceSpec.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package uk.gov.hmrc.cataloguefrontend.service +package uk.gov.hmrc.cataloguefrontend.bobby import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar diff --git a/test/uk/gov/hmrc/cataloguefrontend/connector/ServiceDependenciesConnectorSpec.scala b/test/uk/gov/hmrc/cataloguefrontend/connector/ServiceDependenciesConnectorSpec.scala index 007be3f38..c860543d0 100644 --- a/test/uk/gov/hmrc/cataloguefrontend/connector/ServiceDependenciesConnectorSpec.scala +++ b/test/uk/gov/hmrc/cataloguefrontend/connector/ServiceDependenciesConnectorSpec.scala @@ -27,10 +27,12 @@ import play.api.inject.guice.GuiceApplicationBuilder import play.api.libs.json.{Json, Reads} import uk.gov.hmrc.http.test.WireMockSupport import uk.gov.hmrc.cataloguefrontend.connector.model._ -import uk.gov.hmrc.cataloguefrontend.model.{Environment, ServiceName, SlugInfoFlag, Version} +import uk.gov.hmrc.cataloguefrontend.model.{DigitalService, Environment, ServiceName, SlugInfoFlag, TeamName, Version, VersionRange} import uk.gov.hmrc.cataloguefrontend.service.ServiceDependencies import uk.gov.hmrc.http.HeaderCarrier +import java.time.{Instant, LocalDate} + class ServiceDependenciesConnectorSpec extends AnyWordSpec with Matchers @@ -55,8 +57,8 @@ class ServiceDependenciesConnectorSpec given HeaderCarrier = HeaderCarrier() - "getJdkVersions" should { - "returns JDK versions with vendor" in { + "getJdkVersions" should: + "returns JDK versions with vendor" in: stubFor( get(urlEqualTo(s"/api/jdkVersions?flag=${SlugInfoFlag.ForEnvironment(Environment.Production).asString}")) .willReturn( @@ -81,11 +83,9 @@ class ServiceDependenciesConnectorSpec response(1).version shouldBe Version("1.8.0_191") response(1).vendor shouldBe Vendor.OpenJDK response(1).kind shouldBe Kind.JRE - } - } - "JSON Reader" should { - "read json with java section" in { + "JSON Reader" should: + "read json with java section" in: given Reads[ServiceDependencies] = ServiceDependencies.reads val json = """{ @@ -132,6 +132,59 @@ class ServiceDependenciesConnectorSpec res.java.version shouldBe "1.8.0_191" res.java.vendor.asString shouldBe "OpenJDK" res.java.kind.asString shouldBe "JDK" - } - } + + "bobbyReports" should: + "return bobby reports for a given search" in: + stubFor: + get(urlEqualTo("/api/bobbyReports?team=some-team&digitalService=some-digital-service&repoType=Service&flag=latest")) + .willReturn(aResponse().withBody(""" + [{ + "repoName" : "some-repo", + "repoVersion": "1.165.0", + "repoType" : "Service", + "violations" : [{ + "depGroup" : "uk.gov.hmrc", + "depArtefact": "some-library", + "depVersion" : "4.0.0", + "depScopes" : ["compile","test"], + "range" : "[0.0.0,)", + "reason" : "Deprecated Library", + "from" : "2025-09-30", + "exempt" : false + }], + "lastUpdated" : "2025-01-03T17:37:47.338Z", + "latest" : true, + "production" : true, + "qa" : true, + "staging" : true, + "development" : true, + "externaltest": false, + "integration" : false + }] + """)) + + serviceDependenciesConnector + .bobbyReports( + teamName = Some(TeamName("some-team")) + , digitalService = Some(DigitalService("some-digital-service")) + , repoType = Some(RepoType.Service) + , flag = SlugInfoFlag.Latest) + .futureValue shouldBe Seq( + BobbyReport( + repoName = "some-repo" + , repoVersion = Version("1.165.0") + , repoType = RepoType.Service + , violations = Seq(BobbyReport.Violation( + depGroup = "uk.gov.hmrc" + , depArtefact = "some-library" + , depVersion = Version("4.0.0") + , depScopes = Set(DependencyScope.Compile, DependencyScope.Test) + , range = VersionRange("[0.0.0,)") + , reason = "Deprecated Library" + , from = LocalDate.parse("2025-09-30") + , exempt = false + )) + , lastUpdated = Instant.parse("2025-01-03T17:37:47.338Z") + ) + ) } From ac90baf48f738e53ebb09be5c0c0113d528831b6 Mon Sep 17 00:00:00 2001 From: Shnick <1132919+Shnick@users.noreply.github.com> Date: Tue, 7 Jan 2025 09:42:24 +0000 Subject: [PATCH 2/2] BDOG-3337 change bobby reports page name to bobby violations + remove exempt toggle --- .../bobby/BobbyExplorerController.scala | 24 +++++++++---------- ...la.html => BobbyViolationsPage.scala.html} | 21 ++++++---------- conf/app.routes | 2 +- 3 files changed, 19 insertions(+), 28 deletions(-) rename app/uk/gov/hmrc/cataloguefrontend/bobby/view/{BobbyReportsPage.scala.html => BobbyViolationsPage.scala.html} (92%) diff --git a/app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyExplorerController.scala b/app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyExplorerController.scala index da22ec504..999037f01 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyExplorerController.scala +++ b/app/uk/gov/hmrc/cataloguefrontend/bobby/BobbyExplorerController.scala @@ -22,7 +22,7 @@ import play.api.mvc.{Action, AnyContent, MessagesControllerComponents, MessagesR import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendController import uk.gov.hmrc.internalauth.client.FrontendAuthComponents import uk.gov.hmrc.cataloguefrontend.auth.CatalogueAuthBuilders -import uk.gov.hmrc.cataloguefrontend.bobby.view.html.{BobbyExplorerPage, BobbyReportsPage} +import uk.gov.hmrc.cataloguefrontend.bobby.view.html.{BobbyExplorerPage, BobbyViolationsPage} import uk.gov.hmrc.cataloguefrontend.connector.{RepoType, ServiceDependenciesConnector, TeamsAndRepositoriesConnector} import uk.gov.hmrc.cataloguefrontend.model.{DigitalService, SlugInfoFlag, TeamName} @@ -30,19 +30,19 @@ import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} class BobbyExplorerController @Inject() ( - teamsAndReposConnector : TeamsAndRepositoriesConnector -, serviceDeps : ServiceDependenciesConnector -, bobbyService : BobbyService -, bobbyReportsPage : BobbyReportsPage -, bobbyExplorerPage : BobbyExplorerPage -, override val mcc : MessagesControllerComponents -, override val auth : FrontendAuthComponents + teamsAndReposConnector: TeamsAndRepositoriesConnector +, serviceDeps : ServiceDependenciesConnector +, bobbyService : BobbyService +, bobbyViolationsPage : BobbyViolationsPage +, bobbyExplorerPage : BobbyExplorerPage +, override val mcc : MessagesControllerComponents +, override val auth : FrontendAuthComponents )(using override val ec: ExecutionContext ) extends FrontendController(mcc) with CatalogueAuthBuilders: - def bobbyReports(team: Option[TeamName], digitalService: Option[DigitalService], flag: Option[String]): Action[AnyContent] = + def bobbyViolations(team: Option[TeamName], digitalService: Option[DigitalService], flag: Option[String]): Action[AnyContent] = BasicAuthAction.async: request => given MessagesRequest[AnyContent] = request ( for @@ -50,13 +50,13 @@ class BobbyExplorerController @Inject() ( digitalServices <- EitherT.right[Result](teamsAndReposConnector.allDigitalServices()) form = BobbyReportFilter.form.bindFromRequest() filter <- EitherT.fromEither[Future](form.fold( - formErrors => Left(BadRequest(bobbyReportsPage(form, teams, digitalServices, results = None))) + formErrors => Left(BadRequest(bobbyViolationsPage(form, teams, digitalServices, results = None))) , formObject => Right(formObject) )) results <- EitherT.right[Result]: serviceDeps.bobbyReports(filter.team, filter.digitalService, filter.repoType, filter.flag) yield - Ok(bobbyReportsPage(form, teams, digitalServices, results = Some(results))) + Ok(bobbyViolationsPage(form, teams, digitalServices, results = Some(results))) ).merge def list(selector: Option[String]): Action[AnyContent] = @@ -73,7 +73,6 @@ case class BobbyReportFilter( , repoType : Option[RepoType] = None , flag : SlugInfoFlag = SlugInfoFlag.Latest , isActive : Option[Boolean] = None -, exempt : Boolean = false ) object BobbyReportFilter: @@ -85,6 +84,5 @@ object BobbyReportFilter: , "repoType" -> Forms.optional(Forms.of[RepoType]) , "flag" -> Forms.optional(Forms.of[SlugInfoFlag]).transform(_.getOrElse(SlugInfoFlag.Latest), Some.apply) , "isActive" -> Forms.optional(Forms.boolean) - , "exempt" -> Forms.boolean )(BobbyReportFilter.apply)(f => Some(Tuple.fromProductTyped(f))) ) diff --git a/app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyReportsPage.scala.html b/app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyViolationsPage.scala.html similarity index 92% rename from app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyReportsPage.scala.html rename to app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyViolationsPage.scala.html index 7e3ff745c..4877113c2 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyReportsPage.scala.html +++ b/app/uk/gov/hmrc/cataloguefrontend/bobby/view/BobbyViolationsPage.scala.html @@ -38,10 +38,10 @@ @implicitField: FieldConstructor = @{ FieldConstructor(catalogueFieldConstructor.f) } -@standard_layout("Bobby Reports", active = "health") { -

    Bobby Reports

    +@standard_layout("Bobby violations", active = "health") { +

    Bobby Violations

    -
    +
    @@ -93,7 +93,7 @@

    Bobby Reports

    , Symbol("class") -> "form-select" )
    -
    +
    @select( field = form("isActive") , options = Seq("true" -> "Active", "false" -> "Upcoming") @@ -104,12 +104,6 @@

    Bobby Reports

    , Symbol("class") -> "form-select" )
    -
    -
    - - -
    -
    @@ -119,7 +113,7 @@

    Bobby Reports

    .flatMap: x => x.violations.collect: case v - if v.exempt == form.get.exempt + if !v.exempt && ( form.get.isActive.fold(true): case true => now.isAfter(v.from) case false => now.isBefore(v.from) || now.isEqual(v.from) @@ -130,7 +124,6 @@

    Bobby Reports

      } case items => { -

      Found @items.size results

      @@ -192,7 +185,7 @@

      Bobby Reports

      }