From 6e2f39ef4da184ee9158bcd9bca835569acf2b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Membr=C3=A9?= Date: Thu, 2 May 2024 23:23:49 +0200 Subject: [PATCH] Fixes #24829: Replace compliance chart with Score chart and and new details score charts --- .../src/main/javascript/rudder/homePage.js | 14 ++ .../rudder/web/snippet/HomePage.scala | 137 ++++++++---------- .../src/main/webapp/secure/index.html | 12 +- 3 files changed, 83 insertions(+), 80 deletions(-) diff --git a/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/homePage.js b/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/homePage.js index 6599e2078f9..f408a50a8b4 100644 --- a/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/homePage.js +++ b/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/homePage.js @@ -116,6 +116,7 @@ function homePage ( , nodeCompliance , nodeComplianceColors , nodeCount + , scoreDetails ) { var opts = { lines: 12, // The number of lines to draw @@ -185,6 +186,19 @@ function homePage ( var complianceHColors = nodeCompliance.colors.map(x => complianceHoverColors[x]); doughnutChart('nodeCompliance', nodeCompliance, allNodes, nodeCompliance.colors, complianceHColors); } + + + scoreDetails.forEach(function(score) { + $("#scoreBreakdown .node-charts").append( + `
+

${score.name}

+ +
+
`) + var complianceHColors = score.data.colors.map(x => complianceHoverColors[x]); + doughnutChart('score'+ score.scoreId, score.data, score.count, score.data.colors, complianceHColors); + }) + initBsTooltips(); } diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/HomePage.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/HomePage.scala index 59d9b1e6ba8..4a88bf1ce1f 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/HomePage.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/HomePage.scala @@ -59,6 +59,7 @@ import com.normation.rudder.domain.logger.TimingDebugLogger import com.normation.rudder.domain.reports.ComplianceLevel import com.normation.rudder.facts.nodes.CoreNodeFact import com.normation.rudder.facts.nodes.QueryContext +import com.normation.rudder.score.ScoreValue import com.normation.rudder.users.CurrentUser import com.normation.zio.* import net.liftweb.common.* @@ -69,10 +70,21 @@ import net.liftweb.http.js.JsCmds.* import scala.collection.MapView import scala.xml.* -sealed trait ComplianceLevelPieChart { - def color: String - def label: String - def value: Int +case class ScoreChart(scoreValue: ScoreValue, value: Int) { + def color: String = { + + import com.normation.rudder.score.ScoreValue.* + scoreValue match { + case A => "#13beb7" + case B => "#68c96a" + case C => "#b3d337" + case D => "#fedc04" + case E => "#f0940e" + case F => "#da291c" + case NoScore => "" + } + } + def label: String = scoreValue.value def jsValue: JsArray = { JsArray(label, value) @@ -83,36 +95,6 @@ sealed trait ComplianceLevelPieChart { } } -final case class DisabledChart(value: Int) extends ComplianceLevelPieChart { - val label = "Reports Disabled" - val color = "#B1BBCB" -} - -final case class GreenChart(value: Int) extends ComplianceLevelPieChart { - val label = "Perfect (100%)" - val color = "#13BEB7" -} - -final case class BlueChart(value: Int) extends ComplianceLevelPieChart { - val label = "Good (> 75%)" - val color = "#B1EDA4" -} - -final case class OrangeChart(value: Int) extends ComplianceLevelPieChart { - val label = "Average (> 50%)" - val color = "#EF9600" -} - -final case class RedChart(value: Int) extends ComplianceLevelPieChart { - val label = "Poor (< 50%)" - val color = "#DA291C" -} - -final case class PendingChart(value: Int) extends ComplianceLevelPieChart { - val label = "Applying" - val color = "#5bc0de" -} - object HomePageUtils { // Format different version naming type into one def formatAgentVersion(version: String): String = { @@ -161,6 +143,7 @@ class HomePage extends StatefulSnippet { private[this] val rudderDit = RudderConfig.rudderDit private[this] val reportingService = RudderConfig.reportingService private[this] val roRuleRepo = RudderConfig.roRuleRepository + private[this] val scoreService = RudderConfig.rci.scoreService override val dispatch: DispatchIt = { case "pendingNodes" => pendingNodes @@ -200,11 +183,6 @@ class HomePage extends StatefulSnippet { def getAllCompliance(): NodeSeq = { - sealed trait ChartType - case object PendingChartType extends ChartType - case object DisabledChartType extends ChartType - final case class ColoredChartType(value: Double) extends ChartType - (for { n2 <- currentTimeMillis userRules <- roRuleRepo.getIds() @@ -235,6 +213,8 @@ class HomePage extends StatefulSnippet { n5 <- currentTimeMillis _ <- TimingDebugLoggerPure.trace(s"Compute global compliance in: ${n5 - n4}ms") _ <- TimingDebugLoggerPure.debug(s"Compute compliance: ${n5 - n2}ms") + scores <- scoreService.getAll() + existingScore <- scoreService.getAvailableScore() } yield { // log global compliance info (useful for metrics on number of components and log data analysis) @@ -252,52 +232,25 @@ class HomePage extends StatefulSnippet { * Note: node without reports are also put in "pending". */ - val complianceByNode: List[ChartType] = compliancePerNodes.values.map { r => - if (r.pending == r.total) { PendingChartType } - else if (r.reportsDisabled == r.total) { DisabledChartType } - else { ColoredChartType(r.withoutPending.computePercent().compliance) } - }.toList - - val complianceDiagram: List[ComplianceLevelPieChart] = (complianceByNode.groupBy { compliance => - compliance match { - case PendingChartType => PendingChart - case DisabledChartType => DisabledChart - case ColoredChartType(100) => GreenChart - case ColoredChartType(x) if x >= 75 => BlueChart - case ColoredChartType(x) if x >= 50 => OrangeChart - case ColoredChartType(_) => RedChart - } - }.map { - case (PendingChart, compliance) => PendingChart(compliance.size) - case (DisabledChart, compliance) => DisabledChart(compliance.size) - case (GreenChart, compliance) => GreenChart(compliance.size) - case (BlueChart, compliance) => BlueChart(compliance.size) - case (OrangeChart, compliance) => OrangeChart(compliance.size) - case (RedChart, compliance) => RedChart(compliance.size) - case (_, compliance) => RedChart(compliance.size) - }).toList - - val sorted = complianceDiagram.sortWith { - case (_: PendingChart, _) => false - case (_: DisabledChart, _) => false - case (_: GreenChart, _) => false - case (_: BlueChart, _: GreenChart) => true - case (_: BlueChart, _) => false - case (_: OrangeChart, (_: GreenChart | _: BlueChart)) => true - case (_: OrangeChart, _) => false - case (_: RedChart, _) => true + val numberOfPendingNodes = compliancePerNodes.values.filter(r => r.pending == r.total).size + + val complianceDiagram: List[ScoreChart] = + scores.values.groupBy(_.value).map(v => ScoreChart(v._1, v._2.size)).toList.sortBy(_.scoreValue.value).reverse + + val detailsScore = scores.values.flatMap(_.details).toList.groupBy(_.scoreId).map { c => + (c._1, c._2.groupBy(_.value).map(v => ScoreChart(v._1, v._2.size)).toList.sortBy(_.scoreValue.value).reverse) } - val numberOfNodes = complianceByNode.size - val pendingNodes = complianceDiagram.collectFirst { case p: PendingChart => p.value } match { + val numberOfNodes = compliancePerNodes.values.size + val pendingNodes = numberOfPendingNodes match { - case None => + case 0 => JsObj( "pending" -> JsNull, "active" -> numberOfNodes ) - case Some(pending) => + case pending => JsObj( "pending" -> JsObj( @@ -308,14 +261,39 @@ class HomePage extends StatefulSnippet { ) } - val diagramData = sorted.foldLeft((Nil: List[JsExp], Nil: List[JsExp], Nil: List[JsExp])) { + val diagramData = complianceDiagram.foldLeft((Nil: List[JsExp], Nil: List[JsExp], Nil: List[JsExp])) { case ((labels, values, colors), diag) => (diag.label :: labels, diag.value :: values, diag.color :: colors) } val data = JsObj("labels" -> JsArray(diagramData._1), "values" -> JsArray(diagramData._2), "colors" -> JsArray(diagramData._3)) - val diagramColor = JsObj(sorted.map(_.jsColor)*) + val diagramColor = JsObj(complianceDiagram.map(_.jsColor)*) + val scoreDetailsData = JsArray(for { + (scoreId, detailChart) <- detailsScore.toList.sortBy(_._1) + } yield { + + val diagramDetailData = detailChart.foldLeft((Nil: List[JsExp], Nil: List[JsExp], Nil: List[JsExp])) { + case ((labels, values, colors), diag) => (diag.label :: labels, diag.value :: values, diag.color :: colors) + } + val detailData = { + JsObj( + "labels" -> JsArray(diagramDetailData._1), + "values" -> JsArray(diagramDetailData._2), + "colors" -> JsArray(diagramDetailData._3) + ) + } + + val detailDiagramColor = JsObj(complianceDiagram.map(_.jsColor)*) + val name: String = existingScore.find(_._1 == scoreId).map(_._2).getOrElse(scoreId) + JsObj( + "scoreId" -> scoreId, + "data" -> detailData, + "colors" -> detailDiagramColor, + "count" -> detailChart.map(_.value).sum, + "name" -> name + ) + }) // Data used for compliance bar, compliance without pending val (complianceBar, globalCompliance) = global match { @@ -336,6 +314,7 @@ class HomePage extends StatefulSnippet { , ${data.toJsCmd} , ${diagramColor.toJsCmd} , ${pendingNodes.toJsCmd} + , ${scoreDetailsData.toJsCmd} )"""))) }).either.runNow match { case Right(homePageCompliance) => homePageCompliance diff --git a/webapp/sources/rudder/rudder-web/src/main/webapp/secure/index.html b/webapp/sources/rudder/rudder-web/src/main/webapp/secure/index.html index 829922e647f..3083442f41f 100644 --- a/webapp/sources/rudder/rudder-web/src/main/webapp/secure/index.html +++ b/webapp/sources/rudder/rudder-web/src/main/webapp/secure/index.html @@ -80,7 +80,7 @@
-
Nodes by overall compliance
+
Global score breakdown
@@ -117,6 +117,16 @@

By agent version

+ +
+
+
Score breakdown
+
+
+
+
+
+