diff --git a/webapp/sources/pom.xml b/webapp/sources/pom.xml
index 71715e4565c..ea1dab12468 100644
--- a/webapp/sources/pom.xml
+++ b/webapp/sources/pom.xml
@@ -467,7 +467,7 @@ limitations under the License.
1.5.2
0.10.2
24.1.1
- 1.5.0
+ 1.6.0
0.7.0
5.5.1
2.3
diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/db/Doobie.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/db/Doobie.scala
index 1640f9f1eee..78a8eb270d8 100644
--- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/db/Doobie.scala
+++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/db/Doobie.scala
@@ -57,6 +57,7 @@ import doobie.util.log.ExecFailure
import doobie.util.log.LogEvent
import doobie.util.log.ProcessingFailure
import doobie.util.transactor
+import io.scalaland.chimney.syntax.*
import java.sql.SQLXML
import javax.sql.DataSource
import net.liftweb.common.*
@@ -279,15 +280,16 @@ object Doobie {
}
}
+ /*
+ * The 4 following ones are used in udder-core/src/main/scala/com/normation/rudder/repository/jdbc/ComplianceRepository.scala
+ */
implicit val CompliancePercentWrite: Write[CompliancePercent] = {
- import ComplianceLevelSerialisation.*
- import net.liftweb.json.*
- Write[String].contramap(x => compactRender(x.toJson))
+ Write[String].contramap(x => x.transformInto[ComplianceSerializable].toJson)
}
implicit val ComplianceRunInfoComposite: Write[(RunAnalysis, RunComplianceInfo)] = {
import NodeStatusReportSerialization.*
- Write[String].contramap(_.toCompactJson)
+ Write[String].contramap(runToJson)
}
implicit val AggregatedStatusReportComposite: Write[AggregatedStatusReport] = {
@@ -297,7 +299,7 @@ object Doobie {
implicit val SetRuleNodeStatusReportComposite: Write[Set[RuleNodeStatusReport]] = {
import NodeStatusReportSerialization.*
- Write[String].contramap(_.toCompactJson)
+ Write[String].contramap(ruleNodeStatusReportToJson)
}
import doobie.enumerated.JdbcType.SqlXml
diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/policies/Tags.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/policies/Tags.scala
index 2acd8612c68..3596db8d705 100644
--- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/policies/Tags.scala
+++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/policies/Tags.scala
@@ -36,9 +36,11 @@
*/
package com.normation.rudder.domain.policies
-import com.normation.rudder.repository.json.DataExtractor.CompleteJson
-import com.normation.rudder.repository.json.JsonExtractorUtils
-import net.liftweb.common.*
+import com.normation.errors.PureResult
+import com.normation.errors.Unexpected
+import io.scalaland.chimney.*
+import io.scalaland.chimney.syntax.*
+import zio.json.*
/**
* Tags that apply on Rules and Directives
@@ -64,52 +66,38 @@ final case class Tags(tags: Set[Tag]) extends AnyVal {
}
object Tags {
- // get tags from a list of key/value embodied by a Map with one elements (but
- // also works with several elements in map)
- def fromMaps(tags: List[Map[String, String]]): Tags = {
- Tags(tags.flatMap(_.map { case (k, v) => Tag(TagName(k), TagValue(v)) }).toSet)
+ private case class JsonTag(key: String, value: String)
+ implicit private val transformTag: Transformer[Tag, JsonTag] = { case Tag(name, value) => JsonTag(name.value, value.value) }
+ implicit private val transformJsonTag: Transformer[JsonTag, Tag] = {
+ case JsonTag(name, value) => Tag(TagName(name), TagValue(value))
}
-}
-object JsonTagSerialisation {
-
- import net.liftweb.json.*
- import net.liftweb.json.JsonDSL.*
-
- def serializeTags(tags: Tags): JValue = {
-
- // sort all the tags by name
- val m: JValue = JArray(
- tags.tags.toList.sortBy(_.name.value).map(t => ("key" -> t.name.value) ~ ("value" -> t.value.value): JObject)
- )
-
- m
+ implicit private val transformTags: Transformer[Tags, List[JsonTag]] = {
+ case Tags(tags) => tags.toList.sortBy(_.name.value).map(_.transformInto[JsonTag])
+ }
+ implicit private val transformListJsonTag: Transformer[List[JsonTag], Tags] = {
+ case list => Tags(list.map(_.transformInto[Tag]).toSet)
}
-}
-
-trait JsonTagExtractor[M[_]] extends JsonExtractorUtils[M] {
- import net.liftweb.json.*
+ implicit private val codecJsonTag: JsonCodec[JsonTag] = DeriveJsonCodec.gen
+ implicit val encoderTags: JsonEncoder[Tags] = JsonEncoder.list[JsonTag].contramap(_.transformInto[List[JsonTag]])
+ implicit val decoderTags: JsonDecoder[Tags] = JsonDecoder.list[JsonTag].map(_.transformInto[Tags])
- def unserializeTags(value: String): Box[M[Tags]] = {
- parseOpt(value) match {
- case Some(json) => extractTags(json)
- case _ => Failure(s"Invalid JSON serialization for Tags ${value}")
- }
+ // get tags from a list of key/value embodied by a Map with one elements (but
+ // also works with several elements in map)
+ def fromMaps(tags: List[Map[String, String]]): Tags = {
+ Tags(tags.flatMap(_.map { case (k, v) => Tag(TagName(k), TagValue(v)) }).toSet)
}
- def convertToTag(jsonTag: JValue): Box[Tag] = {
- for {
- tagName <- CompleteJson.extractJsonString(jsonTag, "key", s => Full(TagName(s)))
- tagValue <- CompleteJson.extractJsonString(jsonTag, "value", s => Full(TagValue(s)))
- } yield {
- Tag(tagName, tagValue)
- }
- }
+ val empty: Tags = Tags(Set())
- def extractTags(value: JValue): Box[M[Tags]] = {
- extractJsonArray(value, "")(convertToTag).map(k =>
- monad.map(k)(tags => Tags(tags.toSet))
- ) ?~! s"Invalid JSON serialization for Tags ${value}"
+ // we have a lot of cases where we parse an `Option[String]` into a `PureResult[Tags]` with
+ // default value `Tags.empty`. The string format is:
+ // [{"key":"k1","value":"v1"},{"key":"k2","value":"v2"}]
+ def parse(opt: Option[String]): PureResult[Tags] = {
+ opt match {
+ case Some(v) => v.fromJson[Tags].left.map(Unexpected.apply)
+ case None => Right(Tags.empty)
+ }
}
}
diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/reports/ComplianceLevel.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/reports/ComplianceLevel.scala
index 4106e4ed389..aeab484f90e 100644
--- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/reports/ComplianceLevel.scala
+++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/reports/ComplianceLevel.scala
@@ -40,24 +40,22 @@ package com.normation.rudder.domain.reports
import com.normation.rudder.domain.reports.ComplianceLevel.PERCENT_PRECISION
import com.normation.rudder.domain.reports.CompliancePrecision.Level0
import com.normation.rudder.domain.reports.CompliancePrecision.Level2
+import io.scalaland.chimney.*
import net.liftweb.common.*
-import net.liftweb.http.js.JE
-import net.liftweb.http.js.JE.JsArray
-import net.liftweb.json.JsonAST.JInt
-import net.liftweb.json.JsonAST.JObject
-import net.liftweb.json.JsonAST.JValue
import zio.Chunk
import zio.json.*
+import zio.json.ast.*
+import zio.json.ast.Json.*
/**
- * That file define a "compliance level" object, which store all the kind of reports we can get and
+ * That file defines a "compliance level" object, which store all the kind of reports we can get and
* compute percentage on them.
*
* Percent are stored a double from 0 to 100 with two relevant digits so 12.34% is actually stored
* as Double(12.34) (not 0.1234)
*
* Since use only two digits in percent, we only have a 10e-4 precision, but we nonetheless NEVER EVER
- * want to make 1 error among 20000 success reports disapear by being rounded to 0.
+ * want to make 1 error among 20000 success reports disappear by being rounded to 0.
* So we accept that we have a minimum for all percent, `MIN_PC`, and whatever the real number, it
* will be returned as that minimum.
*
@@ -65,7 +63,7 @@ import zio.json.*
* It also mean that any transformation or computation on compliance must always be done on level, never
* compliance percent, which are just a model for human convenience, but is false.
*
- * This class should always be instanciated with CompliancePercent.fromLevels` to ensure sum is 100%
+ * This class should always be instantiated with CompliancePercent.fromLevels` to ensure sum is 100%
* The rounding algorithm is:
* - sort levels by number of reports, less first, sum them to get total number of reports
* - for each level except the last one (most number):
@@ -95,47 +93,24 @@ final case class CompliancePercent(
val compliance: Double = success + repaired + notApplicable + compliant + auditNotApplicable
}
-final case class ComplianceSerializable(
- applying: Option[Double],
- successNotApplicable: Option[Double],
- successAlreadyOK: Option[Double],
- successRepaired: Option[Double],
- error: Option[Double],
- auditCompliant: Option[Double],
- auditNonCompliant: Option[Double],
- auditError: Option[Double],
- auditNotApplicable: Option[Double],
- unexpectedUnknownComponent: Option[Double],
- unexpectedMissingComponent: Option[Double],
- noReport: Option[Double],
- reportsDisabled: Option[Double],
- badPolicyMode: Option[Double]
-)
+object CompliancePercent {
-object ComplianceSerializable {
- def fromPercent(compliancePercent: CompliancePercent): ComplianceSerializable = {
- ComplianceSerializable(
- if (compliancePercent.pending == 0) None else Some(compliancePercent.pending),
- if (compliancePercent.notApplicable == 0) None else Some(compliancePercent.notApplicable),
- if (compliancePercent.success == 0) None else Some(compliancePercent.success),
- if (compliancePercent.repaired == 0) None else Some(compliancePercent.repaired),
- if (compliancePercent.error == 0) None else Some(compliancePercent.error),
- if (compliancePercent.compliant == 0) None else Some(compliancePercent.compliant),
- if (compliancePercent.nonCompliant == 0) None else Some(compliancePercent.nonCompliant),
- if (compliancePercent.auditError == 0) None else Some(compliancePercent.auditError),
- if (compliancePercent.auditNotApplicable == 0) None else Some(compliancePercent.auditNotApplicable),
- if (compliancePercent.unexpected == 0) None else Some(compliancePercent.unexpected),
- if (compliancePercent.missing == 0) None else Some(compliancePercent.missing),
- if (compliancePercent.noAnswer == 0) None else Some(compliancePercent.noAnswer),
- if (compliancePercent.reportsDisabled == 0) None else Some(compliancePercent.reportsDisabled),
- if (compliancePercent.badPolicyMode == 0) None else Some(compliancePercent.badPolicyMode)
- )
+ implicit val transformComplianceSerializable: Transformer[CompliancePercent, ComplianceSerializable] = {
+ Transformer
+ .define[CompliancePercent, ComplianceSerializable]
+ .withFieldRenamed(_.pending, _.applying)
+ .withFieldRenamed(_.notApplicable, _.successNotApplicable)
+ .withFieldRenamed(_.success, _.successAlreadyOK)
+ .withFieldRenamed(_.repaired, _.successRepaired)
+ .withFieldRenamed(_.compliant, _.auditCompliant)
+ .withFieldRenamed(_.nonCompliant, _.auditNonCompliant)
+ .withFieldRenamed(_.unexpected, _.unexpectedUnknownComponent)
+ .withFieldRenamed(_.missing, _.unexpectedMissingComponent)
+ .withFieldRenamed(_.noAnswer, _.noReport)
+ .buildTransformer
}
-}
-object CompliancePercent {
-
- // a correspondance array between worse order in `ReportType` and the order of fields in `ComplianceLevel`
+ // a mapping array between worse order in `ReportType` and the order of fields in `ComplianceLevel`
val WORSE_ORDER: Array[Int] = {
import ReportType.*
Array(
@@ -188,7 +163,7 @@ object CompliancePercent {
if (total == 0) { // special case: let it be 0
CompliancePercent()(precision)
} else {
- // these depends on the precision
+ // these depend on the precision
val diviser = divisers(precision.precision)
val hundred = hundreds(precision.precision)
@@ -236,7 +211,7 @@ object CompliancePercent {
if (total == 0) { // special case: let it be 0
0
} else {
- // these depends on the precision
+ // these depend on the precision
val diviser = divisers(precision.precision)
val hundred = hundreds(precision.precision)
@@ -314,7 +289,7 @@ object CompliancePercent {
}
def sortLevelsWithoutPending(c: ComplianceLevel): List[(Int, Int)] = {
- // we want to compare accordingly to `ReportType.getWorsteType` but I don't see any
+ // we want to compare accordingly to `ReportType.getWorstType` but I don't see any
// way to do it directly since we don't use the same order in compliance.
// So we map index of a compliance element to it's worse type order and compare by index
@@ -391,7 +366,8 @@ final case class ComplianceLevel(
pending + success + repaired + error + unexpected + missing + noAnswer + notApplicable + reportsDisabled + compliant + auditNotApplicable + nonCompliant + auditError + badPolicyMode
lazy val total_ok: Int = success + repaired + notApplicable + compliant + auditNotApplicable
- def withoutPending: ComplianceLevel = this.copy(pending = 0, reportsDisabled = 0)
+ def withoutPending: ComplianceLevel = this.copy(pending = 0, reportsDisabled = 0)
+
def computePercent(precision: CompliancePrecision = PERCENT_PRECISION): CompliancePercent =
CompliancePercent.fromLevels(this, precision)
@@ -446,6 +422,21 @@ object CompliancePrecision {
}
object ComplianceLevel {
+ implicit val transformComplianceSerializable: Transformer[ComplianceLevel, ComplianceLevelSerialisation] = {
+ Transformer
+ .define[ComplianceLevel, ComplianceLevelSerialisation]
+ .withFieldRenamed(_.pending, _.applying)
+ .withFieldRenamed(_.notApplicable, _.successNotApplicable)
+ .withFieldRenamed(_.success, _.successAlreadyOK)
+ .withFieldRenamed(_.repaired, _.successRepaired)
+ .withFieldRenamed(_.compliant, _.auditCompliant)
+ .withFieldRenamed(_.nonCompliant, _.auditNonCompliant)
+ .withFieldRenamed(_.unexpected, _.unexpectedUnknownComponent)
+ .withFieldRenamed(_.missing, _.unexpectedMissingComponent)
+ .withFieldRenamed(_.noAnswer, _.noReport)
+ .buildTransformer
+ }
+
def PERCENT_PRECISION = Level2
def compute(reports: Iterable[ReportType]): ComplianceLevel = {
@@ -468,23 +459,21 @@ object ComplianceLevel {
var auditError = 0
var badPolicyMode = 0
- reports.foreach { report =>
- report match {
- case EnforceNotApplicable => notApplicable += 1
- case EnforceSuccess => success += 1
- case EnforceRepaired => repaired += 1
- case EnforceError => error += 1
- case Unexpected => unexpected += 1
- case Missing => missing += 1
- case NoAnswer => noAnswer += 1
- case Pending => pending += 1
- case Disabled => reportsDisabled += 1
- case AuditCompliant => compliant += 1
- case AuditNotApplicable => auditNotApplicable += 1
- case AuditNonCompliant => nonCompliant += 1
- case AuditError => auditError += 1
- case BadPolicyMode => badPolicyMode += 1
- }
+ reports.foreach {
+ case EnforceNotApplicable => notApplicable += 1
+ case EnforceSuccess => success += 1
+ case EnforceRepaired => repaired += 1
+ case EnforceError => error += 1
+ case Unexpected => unexpected += 1
+ case Missing => missing += 1
+ case NoAnswer => noAnswer += 1
+ case Pending => pending += 1
+ case Disabled => reportsDisabled += 1
+ case AuditCompliant => compliant += 1
+ case AuditNotApplicable => auditNotApplicable += 1
+ case AuditNonCompliant => nonCompliant += 1
+ case AuditError => auditError += 1
+ case BadPolicyMode => badPolicyMode += 1
}
ComplianceLevel(
pending = pending,
@@ -560,79 +549,127 @@ object ComplianceLevel {
}
}
-object ComplianceLevelSerialisation {
- import net.liftweb.json.JsonDSL.*
-
- // utility class to alway have the same names in JSON,
- // even if we are refactoring ComplianceLevel at some point
- // also remove 0
- private def toJObject(
- pending: Number,
- success: Number,
- repaired: Number,
- error: Number,
- unexpected: Number,
- missing: Number,
- noAnswer: Number,
- notApplicable: Number,
- reportsDisabled: Number,
- compliant: Number,
- auditNotApplicable: Number,
- nonCompliant: Number,
- auditError: Number,
- badPolicyMode: Number
- ) = {
- def POS(n: Number) = if (n.doubleValue <= 0) None else Some(JE.Num(n))
-
- (
- ("pending" -> POS(pending))
- ~ ("success" -> POS(success))
- ~ ("repaired" -> POS(repaired))
- ~ ("error" -> POS(error))
- ~ ("unexpected" -> POS(unexpected))
- ~ ("missing" -> POS(missing))
- ~ ("noAnswer" -> POS(noAnswer))
- ~ ("notApplicable" -> POS(notApplicable))
- ~ ("reportsDisabled" -> POS(reportsDisabled))
- ~ ("compliant" -> POS(compliant))
- ~ ("auditNotApplicable" -> POS(auditNotApplicable))
- ~ ("nonCompliant" -> POS(nonCompliant))
- ~ ("auditError" -> POS(auditError))
- ~ ("badPolicyMode" -> POS(badPolicyMode))
- )
+// for serialization
+
+// utility class to always have the same names in JSON,
+// even if we are refactoring ComplianceLevel at some point
+
+// Remove 0 (and neg values) by changing them into None
+// Ensure that only some are written. All intermediary objects are removed (ie it's the same as mapping int/double)
+final class OptPosNum(val value: Option[Num])
+object OptPosNum {
+ val none = new OptPosNum(None)
+
+ def apply(i: Int): OptPosNum = if (i <= 0) none else new OptPosNum(Some(Num(i)))
+ def apply(i: Double): OptPosNum = if (i <= 0) none else new OptPosNum(Some(Num(i)))
+ def apply(i: java.math.BigDecimal): OptPosNum = if (i.signum() <= 0) none else new OptPosNum(Some(Num(i)))
+
+ implicit def encoderOptPosNum: JsonEncoder[OptPosNum] = JsonEncoder.option[Num].contramap(_.value)
+ implicit def decoderOptPosNum: JsonDecoder[OptPosNum] =
+ JsonDecoder.option[Num].map(x => OptPosNum.apply(x.map(_.value).getOrElse(java.math.BigDecimal.ZERO)))
+
+ implicit val transformDouble: Iso[Double, OptPosNum] = Iso[Double, OptPosNum](
+ (src: Double) => OptPosNum(src),
+ (src: OptPosNum) => src.value.map(_.value.doubleValue()).getOrElse(0)
+ )
+
+ implicit val transformInt: Iso[Int, OptPosNum] = Iso[Int, OptPosNum](
+ (src: Int) => OptPosNum(src),
+ (src: OptPosNum) => src.value.map(_.value.intValue()).getOrElse(0)
+ )
+
+}
+
+/*
+ * This one is the one that is serialized to JSON when we don't use the Array[Array[Int]].
+ * The field names must be the one expected by API/client side. Order matters. Name matters.
+ */
+final case class ComplianceSerializable(
+ applying: OptPosNum,
+ successNotApplicable: OptPosNum,
+ successAlreadyOK: OptPosNum,
+ successRepaired: OptPosNum,
+ error: OptPosNum,
+ auditCompliant: OptPosNum,
+ auditNonCompliant: OptPosNum,
+ auditError: OptPosNum,
+ auditNotApplicable: OptPosNum,
+ unexpectedUnknownComponent: OptPosNum,
+ unexpectedMissingComponent: OptPosNum,
+ noReport: OptPosNum,
+ reportsDisabled: OptPosNum,
+ badPolicyMode: OptPosNum
+)
+
+object ComplianceSerializable {
+
+ // A ComplianceSerializable with all field set to OptPosNum.none
+ def empty = {
+ import shapeless.syntax.sized.*
+ val nbFields = 14
+ val x = Array.fill(nbFields)(OptPosNum.none).toList
+ ComplianceSerializable.apply.tupled(x.sized(nbFields).map(_.tupled).get)
}
- private def parse[T](json: JValue, convert: BigInt => T) = {
- def N(n: JValue): T = convert(n match {
- case JInt(i) => i
- case _ => 0
- })
-
- (
- N(json \ "pending"),
- N(json \ "success"),
- N(json \ "repaired"),
- N(json \ "error"),
- N(json \ "unexpected"),
- N(json \ "missing"),
- N(json \ "noAnswer"),
- N(json \ "notApplicable"),
- N(json \ "reportsDisabled"),
- N(json \ "compliant"),
- N(json \ "auditNotApplicable"),
- N(json \ "nonCompliant"),
- N(json \ "auditError"),
- N(json \ "badPolicyMode")
- )
+ implicit val codecComplianceSerializable: JsonCodec[ComplianceSerializable] = DeriveJsonCodec.gen
+ implicit val transformComplianceSerializable: Transformer[ComplianceSerializable, CompliancePercent] = {
+ Transformer
+ .define[ComplianceSerializable, CompliancePercent]
+ .withFieldConst(_.precision, Level0)
+ .withFieldRenamed(_.applying, _.pending)
+ .withFieldRenamed(_.successNotApplicable, _.notApplicable)
+ .withFieldRenamed(_.successAlreadyOK, _.success)
+ .withFieldRenamed(_.successRepaired, _.repaired)
+ .withFieldRenamed(_.auditCompliant, _.compliant)
+ .withFieldRenamed(_.auditNonCompliant, _.nonCompliant)
+ .withFieldRenamed(_.unexpectedUnknownComponent, _.unexpected)
+ .withFieldRenamed(_.unexpectedMissingComponent, _.missing)
+ .withFieldRenamed(_.noReport, _.noAnswer)
+ .buildTransformer
}
+}
- def parseLevel(json: JValue): ComplianceLevel = {
- (ComplianceLevel.apply _).tupled(parse(json, (i: BigInt) => i.intValue))
+final case class ComplianceLevelSerialisation(
+ applying: OptPosNum,
+ successNotApplicable: OptPosNum,
+ successAlreadyOK: OptPosNum,
+ successRepaired: OptPosNum,
+ error: OptPosNum,
+ auditCompliant: OptPosNum,
+ auditNonCompliant: OptPosNum,
+ auditError: OptPosNum,
+ auditNotApplicable: OptPosNum,
+ unexpectedUnknownComponent: OptPosNum,
+ unexpectedMissingComponent: OptPosNum,
+ noReport: OptPosNum,
+ reportsDisabled: OptPosNum,
+ badPolicyMode: OptPosNum
+)
+
+object ComplianceLevelSerialisation {
+
+ implicit val codecComplianceLevelSerialisation: JsonCodec[ComplianceLevelSerialisation] = DeriveJsonCodec.gen
+
+ implicit val transformComplianceLevelSerialisation: Transformer[ComplianceLevelSerialisation, ComplianceLevel] = {
+ Transformer
+ .define[ComplianceLevelSerialisation, ComplianceLevel]
+ .withFieldRenamed(_.applying, _.pending)
+ .withFieldRenamed(_.successNotApplicable, _.notApplicable)
+ .withFieldRenamed(_.successAlreadyOK, _.success)
+ .withFieldRenamed(_.successRepaired, _.repaired)
+ .withFieldRenamed(_.auditCompliant, _.compliant)
+ .withFieldRenamed(_.auditNonCompliant, _.nonCompliant)
+ .withFieldRenamed(_.unexpectedUnknownComponent, _.unexpected)
+ .withFieldRenamed(_.unexpectedMissingComponent, _.missing)
+ .withFieldRenamed(_.noReport, _.noAnswer)
+ .buildTransformer
}
- // transform the compliance percent to a list with a given order:
- // pc_reportDisabled, pc_notapplicable, pc_success, pc_repaired,
- // pc_error, pc_pending, pc_noAnswer, pc_missing, pc_unknown
+ // transform the compliance percent to an array with a given order:
+ // reportDisabled, notapplicable, success, repaired,
+ // error, pending, noAnswer, missing, unexpected,
+ // auditNotApplicable, compliant, nonCompliant, auditError,
+ // badPolicyMode
object array {
implicit val complianceLevelArrayEncoder: JsonEncoder[ComplianceLevel] = {
JsonEncoder[Chunk[(Int, Double)]].contramap(compliance => {
@@ -673,83 +710,36 @@ object ComplianceLevelSerialisation {
// same as in "array" but in old lift-json AST, should be removed soon
implicit class ComplianceLevelToJs(val compliance: ComplianceLevel) extends AnyVal {
- def toJsArray: JsArray = {
+ def toJsArray: Json.Arr = {
val pc = compliance.computePercent()
- JsArray(
- JsArray(compliance.reportsDisabled, JE.Num(pc.reportsDisabled)), // 0
-
- JsArray(compliance.notApplicable, JE.Num(pc.notApplicable)), // 1
+ Arr(
+ Arr(Num(compliance.reportsDisabled), Num(pc.reportsDisabled)), // 0
- JsArray(compliance.success, JE.Num(pc.success)), // 2
+ Arr(Num(compliance.notApplicable), Num(pc.notApplicable)), // 1
- JsArray(compliance.repaired, JE.Num(pc.repaired)), // 3
+ Arr(Num(compliance.success), Num(pc.success)), // 2
- JsArray(compliance.error, JE.Num(pc.error)), // 4
+ Arr(Num(compliance.repaired), Num(pc.repaired)), // 3
- JsArray(compliance.pending, JE.Num(pc.pending)), // 5
+ Arr(Num(compliance.error), Num(pc.error)), // 4
- JsArray(compliance.noAnswer, JE.Num(pc.noAnswer)), // 6
+ Arr(Num(compliance.pending), Num(pc.pending)), // 5
- JsArray(compliance.missing, JE.Num(pc.missing)), // 7
+ Arr(Num(compliance.noAnswer), Num(pc.noAnswer)), // 6
- JsArray(compliance.unexpected, JE.Num(pc.unexpected)), // 8
+ Arr(Num(compliance.missing), Num(pc.missing)), // 7
- JsArray(compliance.auditNotApplicable, JE.Num(pc.auditNotApplicable)), // 9
+ Arr(Num(compliance.unexpected), Num(pc.unexpected)), // 8
- JsArray(compliance.compliant, JE.Num(pc.compliant)), // 10
+ Arr(Num(compliance.auditNotApplicable), Num(pc.auditNotApplicable)), // 9
- JsArray(compliance.nonCompliant, JE.Num(pc.nonCompliant)), // 11
+ Arr(Num(compliance.compliant), Num(pc.compliant)), // 10
- JsArray(compliance.auditError, JE.Num(pc.auditError)), // 12
+ Arr(Num(compliance.nonCompliant), Num(pc.nonCompliant)), // 11
- JsArray(compliance.badPolicyMode, JE.Num(pc.badPolicyMode)) // 13
- )
- }
-
- def toJson: JObject = {
- import compliance.*
- toJObject(
- pending,
- success,
- repaired,
- error,
- unexpected,
- missing,
- noAnswer,
- notApplicable,
- reportsDisabled,
- compliant,
- auditNotApplicable,
- nonCompliant,
- auditError,
- badPolicyMode
- )
- }
- }
+ Arr(Num(compliance.auditError), Num(pc.auditError)), // 12
- // transform a compliace percent to JSON.
- // here, we are using attributes contrary to compliance level,
- // and we only keep the one > 0 (we want the result to be
- // human-readable and to aknolewdge the fact that there may be
- // new fields.
- implicit class CompliancePercentToJs(val c: CompliancePercent) extends AnyVal {
- def toJson: JObject = {
- import c.*
- toJObject(
- pending,
- success,
- repaired,
- error,
- unexpected,
- missing,
- noAnswer,
- notApplicable,
- reportsDisabled,
- compliant,
- auditNotApplicable,
- nonCompliant,
- auditError,
- badPolicyMode
+ Arr(Num(compliance.badPolicyMode), Num(pc.badPolicyMode)) // 13
)
}
}
diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/reports/StatusReports.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/reports/StatusReports.scala
index b9a812258f7..ab4717f9e20 100644
--- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/reports/StatusReports.scala
+++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/reports/StatusReports.scala
@@ -44,10 +44,15 @@ import com.normation.rudder.domain.policies.*
import com.softwaremill.quicklens.*
import enumeratum.Enum
import enumeratum.EnumEntry
+import io.scalaland.chimney.*
+import io.scalaland.chimney.syntax.*
import net.liftweb.common.Loggable
import org.joda.time.DateTime
+import scala.annotation.nowarn
import scala.collection.MapView
-import zio.Chunk
+import zio.*
+import zio.json.*
+import zio.json.ast.Json.*
/**
* That file contains all the kind of status reports for:
@@ -574,105 +579,208 @@ object MessageStatusReport {
}
+/*
+ * This is the serialization for API.
+ * We are again redefining roughly the same objects.
+ */
+// not sure why it doesn't see that they are used
+@nowarn("msg=private val .* in object NodeStatusReportSerialization is never used")
object NodeStatusReportSerialization {
- import net.liftweb.json.*
- import net.liftweb.json.JsonDSL.*
-
- def jsonRunInfo(runInfo: RunAnalysis): JValue = {
- (("type" -> runInfo.kind.entryName)
- ~ ("expectedConfigId" -> runInfo.expectedConfigId.map(_.value))
- ~ ("runConfigId" -> runInfo.lastRunConfigId.map(_.value)))
+ private case class ApiRunInfo(
+ `type`: String,
+ expectedConfigId: Option[String],
+ runConfigId: Option[String]
+ )
+ implicit private lazy val encoderApiRunInfo: JsonEncoder[ApiRunInfo] = DeriveJsonEncoder.gen
+ implicit private lazy val transformRunAnalysis: Transformer[RunAnalysis, ApiRunInfo] = {
+ Transformer
+ .define[RunAnalysis, ApiRunInfo]
+ .withFieldComputed(_.`type`, _.kind.entryName)
+ .withFieldComputed(_.expectedConfigId, _.expectedConfigId.map(_.value))
+ .withFieldComputed(_.runConfigId, _.lastRunConfigId.map(_.value))
+ .buildTransformer
}
- def jsonStatusInfo(statusInfo: RunComplianceInfo): JValue = {
- (
- ("status" -> (statusInfo match {
- case RunComplianceInfo.OK => "success"
- case _ => "error"
- })) ~ (
- "errors" -> (statusInfo match {
+ private case class ApiStatusInfo(
+ status: String,
+ errors: Option[List[Obj]]
+ )
+ implicit private lazy val encoderApiStatusInfo: JsonEncoder[ApiStatusInfo] = DeriveJsonEncoder.gen
+ implicit private lazy val transformRunComplianceInfo: Transformer[RunComplianceInfo, ApiStatusInfo] = {
+ Transformer
+ .define[RunComplianceInfo, ApiStatusInfo]
+ .withFieldComputed(
+ _.status,
+ {
+ case RunComplianceInfo.OK => "success"
+ case _ => "error"
+ }
+ )
+ .withFieldComputed(
+ _.errors,
+ {
case RunComplianceInfo.OK => None
case RunComplianceInfo.PolicyModeInconsistency(errors) =>
Some(errors.map {
case RunComplianceInfo.PolicyModeError.TechniqueMixedMode(msg) =>
- ("policyModeError" -> ("message" -> msg)): JObject
+ Obj(("policyModeError", Obj("message" -> Str(msg))))
case RunComplianceInfo.PolicyModeError.AgentAbortMessage(cause, msg) =>
- ("policyModeInconsistency" -> (("cause" -> cause) ~ ("message" -> msg))): JObject
+ Obj(("policyModeInconsistency", Obj(("cause", Str(cause)), ("message", Str(msg)))))
})
- })
+ }
)
- )
+ .buildTransformer
}
- implicit class RunComplianceInfoToJs(val x: (RunAnalysis, RunComplianceInfo)) extends AnyVal {
- def toJValue: JObject = {
- (
- ("run" -> jsonRunInfo(x._1))
- ~ ("status" -> jsonStatusInfo(x._2))
- )
- }
+ private case class ApiInfo(
+ run: ApiRunInfo,
+ status: ApiStatusInfo
+ )
+
+ implicit private lazy val encoderApiInfo: JsonEncoder[ApiInfo] = DeriveJsonEncoder.gen
+ implicit private lazy val transformApiInfo: Transformer[(RunAnalysis, RunComplianceInfo), ApiInfo] = {
+ Transformer
+ .define[(RunAnalysis, RunComplianceInfo), ApiInfo]
+ .withFieldComputed(_.run, _._1.transformInto[ApiRunInfo])
+ .withFieldComputed(_.status, _._2.transformInto[ApiStatusInfo])
+ .buildTransformer
+ }
+
+ // entry point for Doobie
+ def runToJson(p: (RunAnalysis, RunComplianceInfo)): String = p.transformInto[ApiInfo].toJson
+
+ private case class ApiComponentValue(
+ componentName: String,
+ compliance: ComplianceSerializable,
+ numberReports: Int,
+ value: List[ApiValue]
+ )
+
+ // here, I'm not sure that we want compliance or
+ // compliance percents. Having a normalized value
+ // seems far better for queries in the future.
+ // but in that case, we should also keep the total
+ // number of events to be able to rebuild raw data
+
+ // always map compliance field from ComplianceLevel to ComplianceSerializable automatically
+ implicit private lazy val transformComplianceLevel: Transformer[ComplianceLevel, ComplianceSerializable] = {
+ case c: ComplianceLevel => c.computePercent().transformInto[ComplianceSerializable]
+ }
+
+ implicit private lazy val encoderApiComponentValue: JsonEncoder[ApiComponentValue] = DeriveJsonEncoder.gen
+ implicit private lazy val transformValueStatusReport: Transformer[ValueStatusReport, ApiComponentValue] = {
+ Transformer
+ .define[ValueStatusReport, ApiComponentValue]
+ .withFieldComputed(_.numberReports, _.compliance.total)
+ .withFieldComputed(_.value, _.componentValues.map(_.transformInto[ApiValue]))
+ .buildTransformer
+ }
+
+ private case class ApiValue(
+ value: String,
+ compliance: ComplianceSerializable,
+ numberReports: Int,
+ unexpanded: String,
+ messages: List[ApiMessage]
+ )
- def toJson: String = prettyRender(toJValue)
- def toCompactJson: String = compactRender(toJValue)
+ implicit private lazy val encoderApiValue: JsonEncoder[ApiValue] = DeriveJsonEncoder.gen
+
+ implicit private lazy val transformValue: Transformer[ComponentValueStatusReport, ApiValue] = {
+ Transformer
+ .define[ComponentValueStatusReport, ApiValue]
+ .withFieldComputed(_.value, _.componentValue)
+ .withFieldComputed(_.numberReports, _.compliance.total)
+ .withFieldComputed(_.unexpanded, _.expectedComponentValue)
+ .buildTransformer
+ }
+
+ private case class ApiMessage(
+ message: Option[String],
+ `type`: String
+ )
+
+ implicit private lazy val encoderApiMessage: JsonEncoder[ApiMessage] = DeriveJsonEncoder.gen
+ implicit private lazy val transformMessageStatusReport: Transformer[MessageStatusReport, ApiMessage] = {
+ Transformer
+ .define[MessageStatusReport, ApiMessage]
+ .withFieldComputed(_.`type`, _.reportType.severity)
+ .buildTransformer
+ }
+
+ private case class ApiComponentBlock(
+ value: String,
+ compliance: ComplianceSerializable,
+ numberReports: Int,
+ subComponents: List[Either[ApiComponentValue, ApiComponentBlock]],
+ reportingLogic: String
+ )
+
+ implicit private lazy val encoderApiComponentBlock: JsonEncoder[ApiComponentBlock] = DeriveJsonEncoder.gen
+ implicit private lazy val transformComponent
+ : Transformer[ComponentStatusReport, Either[ApiComponentValue, ApiComponentBlock]] = {
+ case b: BlockStatusReport => Right(b.transformInto[ApiComponentBlock])
+ case v: ValueStatusReport => Left(v.transformInto[ApiComponentValue])
+ }
+
+ implicit private lazy val transformBlockStatusReport: Transformer[BlockStatusReport, ApiComponentBlock] = {
+ Transformer
+ .define[BlockStatusReport, ApiComponentBlock]
+ .withFieldComputed(_.value, _.componentName)
+ .withFieldComputed(_.numberReports, _.compliance.total)
+ .withFieldComputed(_.reportingLogic, _.reportingLogic.value)
+ .enableMethodAccessors
+ .buildTransformer
}
+ private case class ApiDirective(
+ directiveId: String,
+ compliance: ComplianceSerializable,
+ numberReports: Int,
+ components: List[Either[ApiComponentValue, ApiComponentBlock]]
+ )
+
+ implicit private lazy val encoderApiDirective: JsonEncoder[ApiDirective] = DeriveJsonEncoder.gen
+ implicit private lazy val transformDirectiveStatusReport: Transformer[DirectiveStatusReport, ApiDirective] = {
+ Transformer
+ .define[DirectiveStatusReport, ApiDirective]
+ .withFieldComputed(_.directiveId, _.directiveId.serialize)
+ .withFieldComputed(_.numberReports, _.compliance.total)
+ .buildTransformer
+ }
+
+ private case class ApiRule(
+ ruleId: String,
+ compliance: ComplianceSerializable,
+ numberReports: Int,
+ directives: List[ApiDirective]
+ )
+
+ implicit private lazy val encoderApiRule: JsonEncoder[ApiRule] = DeriveJsonEncoder.gen
+ implicit private lazy val transformRule: Transformer[RuleNodeStatusReport, ApiRule] = {
+ Transformer
+ .define[RuleNodeStatusReport, ApiRule]
+ .withFieldComputed(_.ruleId, _.ruleId.serialize)
+ .withFieldComputed(_.numberReports, _.compliance.total)
+ .withFieldComputed(_.directives, _.directives.toList.sortBy(_._1.serialize).map(_._2.transformInto[ApiDirective]))
+ .buildTransformer
+ }
+
+ private case class ApiAggregated(rules: List[ApiRule])
+
+ implicit private lazy val encoderApiAggregated: JsonEncoder[ApiAggregated] = DeriveJsonEncoder.gen
+ implicit private lazy val transformAggregatedStatusReport: Transformer[AggregatedStatusReport, ApiAggregated] = {
+ case a: AggregatedStatusReport => ApiAggregated(a.reports.toList.map(_.transformInto[ApiRule]))
+ }
+
+ // entry point for Doobie
+ def ruleNodeStatusReportToJson(r: Set[RuleNodeStatusReport]) = r.toList.map(_.transformInto[ApiRule]).toJson
+
+ // main external entry point
implicit class AggregatedStatusReportToJs(val x: AggregatedStatusReport) extends AnyVal {
- def toJValue: JValue = x.reports.toJValue
- def toJson: String = prettyRender(toJValue)
- def toCompactJson: String = compactRender(toJValue)
- }
-
- implicit class SetRuleNodeStatusReportToJs(reports: Set[RuleNodeStatusReport]) {
- import ComplianceLevelSerialisation.*
-
- def componentValueToJson(c: ComponentStatusReport): JValue = {
- c match {
- case c: ValueStatusReport =>
- (("componentName" -> c.componentName)
- ~ ("compliance" -> c.compliance.computePercent().toJson)
- ~ ("numberReports" -> c.compliance.total)
- ~ ("values" -> c.componentValues.map { v =>
- (("value" -> v.componentValue)
- ~ ("compliance" -> v.compliance.computePercent().toJson)
- ~ ("numberReports" -> v.compliance.total)
- ~ ("unexpanded" -> v.expectedComponentValue)
- ~ ("messages" -> v.messages.map { m =>
- (("message" -> m.message)
- ~ ("type" -> m.reportType.severity))
- }))
- }))
- case c: BlockStatusReport =>
- (("componentName" -> c.componentName)
- ~ ("compliance" -> c.compliance.computePercent().toJson)
- ~ ("numberReports" -> c.compliance.total)
- ~ ("subComponents" -> c.subComponents.map(componentValueToJson))
- ~ ("reportingLogic" -> c.reportingLogic.toString))
- }
- }
- def toJValue: JValue = {
-
- // here, I'm not sure that we want compliance or
- // compliance percents. Having a normalized value
- // seems far better for queries in the futur.
- // but in that case, we should also keep the total
- // number of events to be able to rebuild raw data
-
- "rules" -> reports.map { r =>
- (("ruleId" -> r.ruleId.serialize)
- ~ ("compliance" -> r.compliance.computePercent().toJson)
- ~ ("numberReports" -> r.compliance.total)
- ~ ("directives" -> r.directives.values.map { d =>
- (("directiveId" -> d.directiveId.serialize)
- ~ ("compliance" -> d.compliance.computePercent().toJson)
- ~ ("numberReports" -> d.compliance.total)
- ~ ("components" -> d.components.map(componentValueToJson)))
- }))
- }
- }
-
- def toJson: String = prettyRender(toJValue)
- def toCompactJson: String = compactRender(toJValue)
+ def toPrettyJson: String = x.transformInto[ApiAggregated].toJsonPretty
+ def toCompactJson: String = x.transformInto[ApiAggregated].toJson
}
}
diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/json/JsonExctractorUtils.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/json/JsonExctractorUtils.scala
index e251f24bc38..bac4e9197cb 100644
--- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/json/JsonExctractorUtils.scala
+++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/json/JsonExctractorUtils.scala
@@ -39,7 +39,6 @@ package com.normation.rudder.repository.json
import cats.*
import cats.implicits.*
-import com.normation.rudder.domain.policies.JsonTagExtractor
import com.normation.utils.Control.*
import net.liftweb.common.*
import net.liftweb.json.*
@@ -133,7 +132,7 @@ trait JsonExtractorUtils[A[_]] {
}
}
-trait DataExtractor[T[_]] extends JsonTagExtractor[T]
+trait DataExtractor[T[_]] extends JsonExtractorUtils[T]
object DataExtractor {
object OptionnalJson extends DataExtractor[Option] {
diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPDiffMapper.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPDiffMapper.scala
index 6acbb444bc0..22312b76121 100644
--- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPDiffMapper.scala
+++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPDiffMapper.scala
@@ -58,7 +58,6 @@ import com.normation.rudder.domain.properties.GroupProperty
import com.normation.rudder.domain.properties.InheritMode
import com.normation.rudder.domain.properties.ModifyGlobalParameterDiff
import com.normation.rudder.domain.properties.PropertyProvider
-import com.normation.rudder.repository.json.DataExtractor
import com.normation.rudder.rule.category.RuleCategoryId
import com.normation.rudder.services.queries.*
import com.unboundid.ldap.sdk.DN
@@ -174,12 +173,9 @@ class LDAPDiffMapper(
case A_SERIALIZED_TAGS =>
for {
d <- diff
- tags <- mod.getOptValue() match {
- case Some(v) => DataExtractor.CompleteJson.unserializeTags(v).map(_.tags).toPureResult
- case None => Right(Set[Tag]())
- }
+ tags <- Tags.parse(mod.getOptValue())
} yield {
- d.copy(modTags = Some(SimpleDiff(oldCr.tags.tags, tags)))
+ d.copy(modTags = Some(SimpleDiff(oldCr.tags.tags, tags.tags)))
}
case x => Left(Err.UnexpectedObject("Unknown diff attribute: " + x))
}
@@ -336,10 +332,7 @@ class LDAPDiffMapper(
case A_SERIALIZED_TAGS =>
for {
d <- diff
- tags <- mod.getOptValue() match {
- case Some(v) => DataExtractor.CompleteJson.unserializeTags(v).toPureResult
- case None => Right(Tags(Set()))
- }
+ tags <- Tags.parse(mod.getOptValue())
} yield {
d.copy(modTags = Some(SimpleDiff(oldPi.tags, tags)))
}
diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPEntityMapper.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPEntityMapper.scala
index dcdb396bf82..05286ecc64e 100644
--- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPEntityMapper.scala
+++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPEntityMapper.scala
@@ -75,7 +75,6 @@ import com.normation.rudder.domain.properties.PropertyProvider
import com.normation.rudder.facts.nodes.NodeSecurityContext
import com.normation.rudder.facts.nodes.SecurityTag
import com.normation.rudder.reports.*
-import com.normation.rudder.repository.json.DataExtractor.CompleteJson
import com.normation.rudder.rule.category.RuleCategory
import com.normation.rudder.rule.category.RuleCategoryId
import com.normation.rudder.services.queries.*
@@ -766,14 +765,7 @@ class LDAPEntityMapper(
priority = e.getAsInt(A_PRIORITY).getOrElse(0)
isEnabled = e.getAsBoolean(A_IS_ENABLED).getOrElse(false)
isSystem = e.getAsBoolean(A_IS_SYSTEM).getOrElse(false)
- tags <- e(A_SERIALIZED_TAGS) match {
- case None => Right(Tags(Set()))
- case Some(tags) =>
- CompleteJson
- .unserializeTags(tags)
- .toPureResult
- .chainError(s"Invalid attribute value for tags ${A_SERIALIZED_TAGS}: ${tags}")
- }
+ tags <- Tags.parse(e(A_SERIALIZED_TAGS)).chainError(s"Invalid attribute value for tags ${A_SERIALIZED_TAGS}")
} yield {
Directive(
DirectiveId(DirectiveUid(id), ParseRev(e(A_REV_ID))),
@@ -812,7 +804,7 @@ class LDAPEntityMapper(
entry.resetValuesTo(A_IS_ENABLED, directive.isEnabled.toLDAPString)
entry.resetValuesTo(A_IS_SYSTEM, directive.isSystem.toLDAPString)
directive.policyMode.foreach(mode => entry.resetValuesTo(A_POLICY_MODE, mode.name))
- entry.resetValuesTo(A_SERIALIZED_TAGS, net.liftweb.json.compactRender(JsonTagSerialisation.serializeTags(directive.tags)))
+ entry.resetValuesTo(A_SERIALIZED_TAGS, directive.tags.toJson)
entry
}
@@ -858,14 +850,7 @@ class LDAPEntityMapper(
if (e.isA(OC_RULE)) {
for {
id <- e.required(A_RULE_UUID)
- tags <- e(A_SERIALIZED_TAGS) match {
- case None => Right(Tags(Set()))
- case Some(tags) =>
- CompleteJson
- .unserializeTags(tags)
- .toPureResult
- .chainError(s"Invalid attribute value for tags ${A_SERIALIZED_TAGS}: ${tags}")
- }
+ tags <- Tags.parse(e(A_SERIALIZED_TAGS)).chainError(s"Invalid attribute value for tags ${A_SERIALIZED_TAGS}")
} yield {
val targets = for {
target <- e.valuesFor(A_RULE_TARGET)
@@ -925,7 +910,7 @@ class LDAPEntityMapper(
entry.resetValuesTo(A_DIRECTIVE_UUID, rule.directiveIds.map(_.serialize).toSeq*)
entry.resetValuesTo(A_DESCRIPTION, rule.shortDescription)
entry.resetValuesTo(A_LONG_DESCRIPTION, rule.longDescription.toString)
- entry.resetValuesTo(A_SERIALIZED_TAGS, net.liftweb.json.compactRender(JsonTagSerialisation.serializeTags(rule.tags)))
+ entry.resetValuesTo(A_SERIALIZED_TAGS, rule.tags.toJson)
entry
}
diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/score/ComplianceScore.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/score/ComplianceScore.scala
index b21ec79800b..e9083cd146b 100644
--- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/score/ComplianceScore.scala
+++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/score/ComplianceScore.scala
@@ -43,6 +43,7 @@ import com.normation.inventory.domain.NodeId
import com.normation.rudder.domain.reports.ComplianceSerializable
import com.normation.rudder.score.ComplianceScore.scoreId
import com.normation.rudder.score.ScoreValue.*
+import io.scalaland.chimney.syntax.TransformerOps
import zio.*
import zio.json.*
import zio.syntax.*
@@ -52,20 +53,19 @@ object ComplianceScore {
}
object ComplianceScoreEventHandler extends ScoreEventHandler {
- implicit val compliancePercentEncoder: JsonEncoder[ComplianceSerializable] = DeriveJsonEncoder.gen
- def handle(event: ScoreEvent): PureResult[List[(NodeId, List[Score])]] = {
+ def handle(event: ScoreEvent): PureResult[List[(NodeId, List[Score])]] = {
event match {
case ComplianceScoreEvent(n, percent) =>
- val p = ComplianceSerializable.fromPercent(percent)
+ val p = percent.transformInto[ComplianceSerializable]
(for {
json <- p.toJsonAST
} yield {
val score = {
- if (p == ComplianceSerializable(None, None, None, None, None, None, None, None, None, None, None, None, None, None)) {
+ if (p == ComplianceSerializable.empty) {
Score(scoreId, NoScore, "No rules applied on this node", json)
} else {
- import ComplianceScore.scoreId
+ import com.normation.rudder.score.ComplianceScore.scoreId
if (percent.compliance >= 95) {
Score(scoreId, A, "Compliance is over 95%", json)
} else if (percent.compliance >= 80) {
diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/quicksearch/QuickSearchBackendImpl.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/quicksearch/QuickSearchBackendImpl.scala
index d29d6511aca..628e9ef47a7 100644
--- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/quicksearch/QuickSearchBackendImpl.scala
+++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/quicksearch/QuickSearchBackendImpl.scala
@@ -49,6 +49,7 @@ import com.normation.rudder.domain.RudderDit
import com.normation.rudder.domain.RudderLDAPConstants.*
import com.normation.rudder.domain.policies.Tag
import com.normation.rudder.domain.policies.TagName
+import com.normation.rudder.domain.policies.Tags as TAGS
import com.normation.rudder.domain.policies.TagValue
import com.normation.rudder.domain.properties.NodeProperty
import com.normation.rudder.facts.nodes.CoreNodeFact
@@ -61,13 +62,12 @@ import com.normation.rudder.ncf.MethodBlock
import com.normation.rudder.ncf.MethodCall
import com.normation.rudder.ncf.MethodElem
import com.normation.rudder.repository.RoDirectiveRepository
-import com.normation.rudder.repository.json.DataExtractor.CompleteJson
import com.unboundid.ldap.sdk.Attribute
import com.unboundid.ldap.sdk.Filter
import java.util.regex.Pattern
-import net.liftweb.common.Full
import net.liftweb.common.Loggable
import scala.util.control.NonFatal
+import zio.json.*
import zio.syntax.*
/**
@@ -617,12 +617,10 @@ object QSLdapBackend {
def transform(pattern: Pattern, value: String): Option[String] = {
def parseTag(value: String, matcher: Tag => Boolean, transform: Tag => String) = {
- import net.liftweb.json.parse
try {
- val json = parse(value)
- CompleteJson.extractTags(json) match {
- case Full(tags) => tags.tags.collectFirst { case t if matcher(t) => transform(t) }
- case _ => None
+ value.fromJson[TAGS] match {
+ case Right(tags) => tags.tags.collectFirst { case t if matcher(t) => transform(t) }
+ case _ => None
}
} catch {
case NonFatal(ex) => None
diff --git a/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/domain/policies/TagsTest.scala b/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/domain/policies/TagsTest.scala
index 995b5529195..93e21430474 100644
--- a/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/domain/policies/TagsTest.scala
+++ b/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/domain/policies/TagsTest.scala
@@ -36,15 +36,11 @@
*/
package com.normation.rudder.domain.policies
-import com.normation.rudder.repository.json.DataExtractor.CompleteJson
import net.liftweb.common.*
-import net.liftweb.json.JsonAST.JArray
-import net.liftweb.json.JsonAST.JField
-import net.liftweb.json.JsonAST.JObject
-import net.liftweb.json.JsonAST.JString
import org.junit.runner.RunWith
import org.specs2.mutable.*
import org.specs2.runner.*
+import zio.json.*
@RunWith(classOf[JUnitRunner])
class TagsTest extends Specification with Loggable {
@@ -60,14 +56,7 @@ class TagsTest extends Specification with Loggable {
val simpleTags: Tags = Tags(Set[Tag](tag1, tag2, tag3))
val simpleSerialization =
"""[{"key":"tag1","value":"tag1-value"},{"key":"tag2","value":"tag2-value"},{"key":"tag3","value":"tag3-value"}]"""
- val jsonSimple: JArray = {
- JArray(
- JObject(JField("key", JString("tag1")), JField("value", JString("tag1-value"))) ::
- JObject(JField("key", JString("tag2")), JField("value", JString("tag2-value"))) ::
- JObject(JField("key", JString("tag3")), JField("value", JString("tag3-value"))) ::
- Nil
- )
- }
+
val invalidSerialization = """[{"key" :"tag1"},{"key" :"tag2", "value":"tag2-value"},{"key" :"tag3", "value":"tag3-value"}]"""
val duplicatedSerialization =
@@ -75,22 +64,19 @@ class TagsTest extends Specification with Loggable {
"Serializing and unserializing" should {
"serialize in correct JSON" in {
- JsonTagSerialisation.serializeTags(simpleTags) must
- equalTo(jsonSimple)
+ simpleTags.toJson must equalTo(simpleSerialization)
}
"unserialize correct JSON" in {
- CompleteJson.unserializeTags(simpleSerialization) must
- equalTo(simpleTags)
+ simpleSerialization.fromJson[Tags] must beRight(simpleTags)
}
"fail to unserialize incorrect JSON" in {
- CompleteJson.unserializeTags(invalidSerialization) must haveClass[Failure]
+ invalidSerialization.fromJson[Tags] must beLeft
}
"unserialize duplicated entries in JSON into uniques" in {
- CompleteJson.unserializeTags(duplicatedSerialization) must
- equalTo(simpleTags)
+ duplicatedSerialization.fromJson[Tags] must beRight(simpleTags)
}
}
diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/NodeGroupForm.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/NodeGroupForm.scala
index c61fefb12f8..9d504200594 100644
--- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/NodeGroupForm.scala
+++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/NodeGroupForm.scala
@@ -72,6 +72,7 @@ import org.apache.commons.text.StringEscapeUtils
import scala.xml.*
import zio.ZIO
import zio.json.*
+import zio.json.ast.*
import zio.syntax.*
object NodeGroupForm {
@@ -276,8 +277,8 @@ class NodeGroupForm(
_.name
)
- private def showComplianceForGroup(progressBarSelector: String, optComplianceArray: Option[JsArray]) = {
- val complianceHtml = optComplianceArray.map(js => s"buildComplianceBar(${js.toJsCmd})").getOrElse("\"No report\"")
+ private def showComplianceForGroup(progressBarSelector: String, optComplianceArray: Option[Json.Arr]) = {
+ val complianceHtml = optComplianceArray.map(js => s"buildComplianceBar(${js.toJson})").getOrElse("\"No report\"")
Script(JsRaw(s"""$$("${progressBarSelector}").html(${complianceHtml});"""))
}
@@ -489,7 +490,7 @@ class NodeGroupForm(
intro ++ tabProperties
}
- private def loadComplianceBar(isGlobalCompliance: Boolean): Option[JsArray] = {
+ private def loadComplianceBar(isGlobalCompliance: Boolean): Option[Json.Arr] = {
val target = nodeGroup match {
case Left(value) => value
case Right(value) => GroupTarget(value.id)
diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/RuleGrid.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/RuleGrid.scala
index 6e0e16eb6bd..455e7b93820 100644
--- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/RuleGrid.scala
+++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/RuleGrid.scala
@@ -61,14 +61,8 @@ import net.liftweb.common.*
import net.liftweb.http.*
import net.liftweb.http.js.*
import net.liftweb.http.js.JE.*
-import net.liftweb.http.js.JE.AnonFunc
import net.liftweb.http.js.JsCmds.*
-import net.liftweb.json.JArray
-import net.liftweb.json.JField
-import net.liftweb.json.JObject
-import net.liftweb.json.JsonAST.JValue
-import net.liftweb.json.JsonParser
-import net.liftweb.json.JString
+import net.liftweb.json.*
import net.liftweb.util.Helpers.*
import org.apache.commons.text.StringEscapeUtils
import org.joda.time.Interval
@@ -741,8 +735,7 @@ object RuleGrid {
val t5 = System.currentTimeMillis
TimingDebugLogger.trace(s"Rule grid: transforming into data: get rule data: callback: ${t5 - t4}ms")
- val tags = JsObj(line.rule.tags.map(tag => (tag.name.value, Str(tag.value.value))).toList*).toJsCmd
- val tagsDisplayed = JsonTagSerialisation.serializeTags(line.rule.tags)
+ val tags = JsObj(line.rule.tags.map(tag => (tag.name.value, Str(tag.value.value))).toList*).toJsCmd
RuleLine(
line.rule.name,
line.rule.id,
@@ -757,7 +750,7 @@ object RuleGrid {
line.policyMode,
line.policyModeExplanation,
tags,
- tagsDisplayed
+ line.rule.tags
)
}
@@ -793,11 +786,20 @@ final case class RuleLine(
policyMode: String,
explanation: String,
tags: String,
- tagsDisplayed: JValue
+ tagsDisplayed: Tags
) extends JsTableLine {
+ private def serializeTags(tags: Tags): JValue = {
+ // sort all the tags by name
+ import net.liftweb.json.JsonDSL.*
+ val m: JValue = JArray(
+ tags.tags.toList.sortBy(_.name.value).map(t => ("key", t.name.value) ~ ("value", t.value.value))
+ )
+ m
+ }
+
/* Would love to have a reflexive way to generate that map ... */
- override def json(freshName: () => String): js.JsObj = {
+ override def json(freshName: () => String): JsObj = {
val reasonField = reasons.map(r => ("reasons" -> escapeHTML(r)))
@@ -818,7 +820,7 @@ final case class RuleLine(
("policyMode", policyMode),
("explanation", explanation),
("tags", tags),
- ("tagsDisplayed", tagsDisplayed)
+ ("tagsDisplayed", serializeTags(tagsDisplayed))
)
base +* JsObj(optFields*)
diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/TagsEditForm.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/TagsEditForm.scala
index 2d86ffbfd97..2ac19bbd255 100644
--- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/TagsEditForm.scala
+++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/TagsEditForm.scala
@@ -1,8 +1,7 @@
package com.normation.rudder.web.components
-import com.normation.rudder.domain.policies.JsonTagSerialisation
+import com.normation.box.*
import com.normation.rudder.domain.policies.Tags
-import com.normation.rudder.repository.json.DataExtractor.CompleteJson
import com.normation.rudder.web.ChooseTemplate
import net.liftweb.common.*
import net.liftweb.http.SHtml
@@ -12,15 +11,16 @@ import net.liftweb.util.CssSel
import net.liftweb.util.Helpers.*
import org.apache.commons.text.StringEscapeUtils
import scala.xml.NodeSeq
+import zio.json.*
class TagsEditForm(tags: Tags, objectId: String) extends Loggable {
val templatePath: List[String] = List("templates-hidden", "components", "ComponentTags")
def tagsTemplate: NodeSeq = ChooseTemplate(templatePath, "tag-form")
- val jsTags: String = net.liftweb.json.compactRender(JsonTagSerialisation.serializeTags(tags))
+ val jsTags: String = tags.toJson
- def parseResult(s: String): Box[Tags] = CompleteJson.unserializeTags(s)
+ def parseResult(s: String): Box[Tags] = s.fromJson[Tags].toBox
def tagsForm(controllerId: String, appId: String, update: Box[Tags] => Unit, isRule: Boolean): NodeSeq = {
diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/AsyncComplianceService.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/AsyncComplianceService.scala
index 73b9fdfafa1..6258fb021db 100644
--- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/AsyncComplianceService.scala
+++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/AsyncComplianceService.scala
@@ -159,7 +159,7 @@ class AsyncComplianceService(
for { (key, optCompliance) <- compliances } yield {
val value = kind.value(key)
val displayCompliance = optCompliance
- .map(_.toJsArray.toJsCmd)
+ .map(_.toJsArray.toJson)
.getOrElse("""'
no data available
'""")
s"${kind.jsContainer}['${value}'] = ${displayCompliance};"
}
diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/ComplianceData.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/ComplianceData.scala
index f9dfa5b0d40..f9188f88a67 100644
--- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/ComplianceData.scala
+++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/ComplianceData.scala
@@ -45,10 +45,47 @@ import com.normation.rudder.domain.policies.*
import com.normation.rudder.domain.reports.*
import com.normation.rudder.facts.nodes.CoreNodeFact
import com.normation.rudder.repository.FullActiveTechniqueCategory
+import com.normation.rudder.web.services.EscapeHtml.*
+import io.scalaland.chimney.*
+import io.scalaland.chimney.syntax.*
import net.liftweb.common.*
import net.liftweb.http.*
-import net.liftweb.http.js.JE.*
-import net.liftweb.json.JsonAST.JValue
+import org.apache.commons.text.StringEscapeUtils
+import zio.json.*
+import zio.json.internal.Write
+
+object EscapeHtml {
+ implicit class DoEscapeHtml(s: String) {
+ // this is needed because DataTable doesn't escape HTML element when using table.rows.add
+ def escapeHTML: String = StringEscapeUtils.escapeHtml4(s)
+ }
+}
+
+/*
+ * This is used to provide the jsid: nextFuncName in main code, a decidable pseudo-random in tests.
+ */
+trait ProvideNextName {
+ def nextName: String
+}
+
+object LiftProvideNextName extends ProvideNextName {
+ override def nextName: String = net.liftweb.util.Helpers.nextFuncName
+}
+
+object ProvideNextName {
+ implicit val encoderProvideNextName: JsonEncoder[ProvideNextName] = JsonEncoder.string.contramap(_.nextName)
+}
+
+final case class RuleComplianceLines(rules: List[RuleComplianceLine])
+
+object RuleComplianceLines {
+ /*
+ * This is the main encoder that under the hood will transform to the JsonXXXLine corresponding object to encode them.
+ * It will need a `ProvideNextName` implicit in context to be used.
+ */
+ implicit def encoderRuleComplianceLines(implicit next: ProvideNextName): JsonEncoder[RuleComplianceLines] =
+ JsonEncoder.list[JsonRuleComplianceLine].contramap[RuleComplianceLines](_.rules.map(_.transformInto[JsonRuleComplianceLine]))
+}
/*
* That file contains all the datastructures related to
@@ -73,29 +110,48 @@ final case class RuleComplianceLine(
rule: Rule,
id: RuleId,
compliance: ComplianceLevel,
- details: JsTableData[DirectiveComplianceLine],
+ details: List[DirectiveComplianceLine],
policyMode: String,
modeExplanation: String,
- tags: JValue
-) extends JsTableLine {
- def json(freshName: () => String): js.JsObj = {
- JsObj(
- ("rule" -> escapeHTML(rule.name)),
- ("compliance" -> jsCompliance(compliance)),
- ("compliancePercent" -> compliance.computePercent().compliance),
- ("id" -> escapeHTML(rule.id.serialize)),
- ("details" -> details.json(freshName)), // unique id, usable as DOM id - rules, directives, etc can
- // appear several time in a page
-
- ("jsid" -> freshName()),
- ("isSystem" -> rule.isSystem),
- ("policyMode" -> policyMode),
- ("explanation" -> modeExplanation),
- ("tags" -> tags)
- )
+ tags: Tags
+)
+
+object RuleComplianceLine {
+
+ implicit def transformRuleComplianceLine(implicit
+ next: ProvideNextName
+ ): Transformer[RuleComplianceLine, JsonRuleComplianceLine] = {
+ Transformer
+ .define[RuleComplianceLine, JsonRuleComplianceLine]
+ .withFieldComputed(_.rule, _.rule.name.escapeHTML)
+ .withFieldComputed(_.compliancePercent, _.compliance.computePercent().compliance)
+ .withFieldComputed(_.id, _.rule.id.serialize.escapeHTML)
+ .withFieldComputed(_.details, _.details.map(_.transformInto[JsonDirectiveComplianceLine]))
+ .withFieldConst(_.jsid, next)
+ .withFieldComputed(_.isSystem, _.rule.isSystem)
+ .withFieldRenamed(_.modeExplanation, _.explanation)
+ .buildTransformer
}
}
+final case class JsonRuleComplianceLine(
+ rule: String,
+ compliance: ComplianceLevel,
+ compliancePercent: Double,
+ id: String,
+ details: List[JsonDirectiveComplianceLine],
+ jsid: ProvideNextName,
+ isSystem: Boolean,
+ policyMode: String,
+ explanation: String,
+ tags: Tags
+)
+
+object JsonRuleComplianceLine {
+ import com.normation.rudder.domain.reports.ComplianceLevelSerialisation.array.*
+ implicit val encoderJsonRuleComplianceLine: JsonEncoder[JsonRuleComplianceLine] = DeriveJsonEncoder.gen
+}
+
/*
* Javascript object containing all data to create a line in the DataTable
* { "directive" : Directive name [String]
@@ -116,31 +172,50 @@ final case class DirectiveComplianceLine(
techniqueName: String,
techniqueVersion: TechniqueVersion,
compliance: ComplianceLevel,
- details: JsTableData[ComponentComplianceLine],
+ details: List[ComponentComplianceLine],
policyMode: String,
- modeExplanation: String,
- tags: JValue
-) extends JsTableLine {
- override def json(freshName: () => String): js.JsObj = {
- JsObj(
- ("directive" -> escapeHTML(directive.name)),
- ("id" -> escapeHTML(directive.id.uid.value)),
- ("techniqueName" -> escapeHTML(techniqueName)),
- ("techniqueVersion" -> escapeHTML(techniqueVersion.serialize)),
- ("compliance" -> jsCompliance(compliance)),
- ("compliancePercent" -> compliance.computePercent().compliance),
- ("details" -> details.json(freshName)), // unique id, usable as DOM id - rules, directives, etc can
- // appear several time in a page
-
- ("jsid" -> freshName()),
- ("isSystem" -> directive.isSystem),
- ("policyMode" -> policyMode),
- ("explanation" -> modeExplanation),
- ("tags" -> tags)
- )
+ explanation: String,
+ tags: Tags
+)
+
+object DirectiveComplianceLine {
+ implicit def transformDirectiveComplianceLine(implicit
+ next: ProvideNextName
+ ): Transformer[DirectiveComplianceLine, JsonDirectiveComplianceLine] = {
+ Transformer
+ .define[DirectiveComplianceLine, JsonDirectiveComplianceLine]
+ .withFieldComputed(_.directive, _.directive.name.escapeHTML)
+ .withFieldComputed(_.id, _.directive.id.uid.value.escapeHTML)
+ .withFieldComputed(_.techniqueName, _.techniqueName.escapeHTML)
+ .withFieldComputed(_.techniqueVersion, _.techniqueVersion.serialize.escapeHTML)
+ .withFieldComputed(_.compliancePercent, _.compliance.computePercent().compliance)
+ .withFieldComputed(_.details, _.details.map(_.transformInto[JsonComponentComplianceLine]))
+ .withFieldConst(_.jsid, next)
+ .withFieldComputed(_.isSystem, _.directive.isSystem)
+ .buildTransformer
}
}
+final case class JsonDirectiveComplianceLine(
+ directive: String,
+ id: String,
+ techniqueName: String,
+ techniqueVersion: String,
+ compliance: ComplianceLevel,
+ compliancePercent: Double,
+ details: List[JsonComponentComplianceLine],
+ jsid: ProvideNextName,
+ isSystem: Boolean,
+ policyMode: String,
+ explanation: String,
+ tags: Tags
+)
+
+object JsonDirectiveComplianceLine {
+ import com.normation.rudder.domain.reports.ComplianceLevelSerialisation.array.*
+ implicit val encoderJsonDirectiveComplianceLine: JsonEncoder[JsonDirectiveComplianceLine] = DeriveJsonEncoder.gen
+}
+
/*
* Javascript object containing all data to create a line in the DataTable
* { "component" : component name [String]
@@ -153,51 +228,111 @@ final case class DirectiveComplianceLine(
* }
*/
-sealed trait ComponentComplianceLine extends JsTableLine {
+sealed trait ComponentComplianceLine {
def component: String
def compliance: ComplianceLevel
}
+object ComponentComplianceLine {
+ implicit def transformComponentComplianceLine(implicit
+ next: ProvideNextName
+ ): Transformer[ComponentComplianceLine, JsonComponentComplianceLine] = {
+ case x: BlockComplianceLine => x.transformInto[JsonBlockComplianceLine]
+ case x: ValueComplianceLine => x.transformInto[JsonValueComplianceLine]
+ }
+}
+
final case class BlockComplianceLine(
component: String,
compliance: ComplianceLevel,
- details: JsTableData[ComponentComplianceLine],
+ details: List[ComponentComplianceLine],
reportingLogic: ReportingLogic
-) extends ComponentComplianceLine {
- def json(freshName: () => String): js.JsObj = {
- JsObj(
- ("component" -> escapeHTML(component)),
- ("compliance" -> jsCompliance(compliance)),
- ("compliancePercent" -> compliance.computePercent().compliance),
- ("details" -> details.json(freshName)),
- ("jsid" -> freshName()),
- ("composition" -> reportingLogic.toString)
- )
+) extends ComponentComplianceLine
+
+object BlockComplianceLine {
+ implicit def transformBlockComplianceLine(implicit
+ next: ProvideNextName
+ ): Transformer[BlockComplianceLine, JsonBlockComplianceLine] = {
+ Transformer
+ .define[BlockComplianceLine, JsonBlockComplianceLine]
+ .withFieldComputed(_.component, _.component.escapeHTML)
+ .withFieldComputed(_.compliancePercent, _.compliance.computePercent().compliance)
+ .withFieldComputed(_.details, _.details.map(_.transformInto[JsonComponentComplianceLine]))
+ .withFieldConst(_.jsid, next)
+ .withFieldComputed(_.composition, _.reportingLogic.toString)
+ .buildTransformer
}
+
}
final case class ValueComplianceLine(
component: String,
unexpanded: String,
compliance: ComplianceLevel,
- details: JsTableData[ComponentValueComplianceLine],
+ details: List[ComponentValueComplianceLine],
noExpand: Boolean
-) extends ComponentComplianceLine {
-
- def json(freshName: () => String): js.JsObj = {
- JsObj(
- ("component" -> escapeHTML(component)),
- ("unexpanded" -> escapeHTML(unexpanded)),
- ("compliance" -> jsCompliance(compliance)),
- ("compliancePercent" -> compliance.computePercent().compliance),
- ("details" -> details.json(freshName)),
- ("noExpand" -> noExpand),
- ("jsid" -> freshName())
- )
+) extends ComponentComplianceLine
+
+object ValueComplianceLine {
+ implicit def transformValueComplianceLine(implicit
+ next: ProvideNextName
+ ): Transformer[ValueComplianceLine, JsonValueComplianceLine] = {
+ Transformer
+ .define[ValueComplianceLine, JsonValueComplianceLine]
+ .withFieldComputed(_.component, _.component.escapeHTML)
+ .withFieldComputed(_.unexpanded, _.unexpanded.escapeHTML)
+ .withFieldComputed(_.compliancePercent, _.compliance.computePercent().compliance)
+ .withFieldComputed(_.details, _.details.map(_.transformInto[JsonComponentValueComplianceLine]))
+ .withFieldConst(_.jsid, next)
+ .buildTransformer
}
}
+sealed trait JsonComponentComplianceLine
+
+object JsonComponentComplianceLine {
+ implicit val encoderJsonComponentComplianceLine: JsonEncoder[JsonComponentComplianceLine] = {
+ new JsonEncoder[JsonComponentComplianceLine] {
+ override def unsafeEncode(a: JsonComponentComplianceLine, indent: Option[Int], out: Write): Unit = {
+ a match {
+ case x: JsonBlockComplianceLine => JsonEncoder[JsonBlockComplianceLine].unsafeEncode(x, indent, out)
+ case x: JsonValueComplianceLine => JsonEncoder[JsonValueComplianceLine].unsafeEncode(x, indent, out)
+ }
+ }
+ }
+ }
+}
+
+final case class JsonBlockComplianceLine(
+ component: String,
+ compliance: ComplianceLevel,
+ compliancePercent: Double,
+ details: List[JsonComponentComplianceLine],
+ jsid: ProvideNextName,
+ composition: String
+) extends JsonComponentComplianceLine
+
+object JsonBlockComplianceLine {
+ import com.normation.rudder.domain.reports.ComplianceLevelSerialisation.array.*
+ implicit val encoderJsonBlockComplianceLine: JsonEncoder[JsonBlockComplianceLine] = DeriveJsonEncoder.gen
+}
+
+final case class JsonValueComplianceLine(
+ component: String,
+ unexpanded: String,
+ compliance: ComplianceLevel,
+ compliancePercent: Double,
+ details: List[JsonComponentValueComplianceLine],
+ noExpand: Boolean,
+ jsid: ProvideNextName
+) extends JsonComponentComplianceLine
+
+object JsonValueComplianceLine {
+ import com.normation.rudder.domain.reports.ComplianceLevelSerialisation.array.*
+ implicit val encoderJsonValueComplianceLine: JsonEncoder[JsonValueComplianceLine] = DeriveJsonEncoder.gen
+}
+
/*
* Javascript object containing all data to create a line in the DataTable
* { "value" : value of the key [String]
@@ -216,25 +351,40 @@ final case class ComponentValueComplianceLine(
compliance: ComplianceLevel,
status: String,
statusClass: String
-) extends JsTableLine {
-
- def json(freshName: () => String): js.JsObj = {
- JsObj(
- ("value" -> escapeHTML(value)),
- ("unexpanded" -> escapeHTML(unexpandedValue)),
- ("status" -> status),
- ("statusClass" -> statusClass),
- ("messages" -> JsArray(messages.map { case (s, m) => JsObj(("status" -> s), ("value" -> escapeHTML(m))) })),
- ("compliance" -> jsCompliance(compliance)),
- ("compliancePercent" -> compliance.computePercent().compliance), // unique id, usable as DOM id - rules, directives, etc can
- // appear several time in a page
-
- ("jsid" -> freshName())
- )
+)
+
+object ComponentValueComplianceLine {
+ implicit def transformComponentValueComplianceLine(implicit
+ next: ProvideNextName
+ ): Transformer[ComponentValueComplianceLine, JsonComponentValueComplianceLine] = {
+ Transformer
+ .define[ComponentValueComplianceLine, JsonComponentValueComplianceLine]
+ .withFieldComputed(_.value, _.value.escapeHTML)
+ .withFieldComputed(_.unexpanded, _.unexpandedValue.escapeHTML)
+ .withFieldComputed(_.messages, _.messages.map { case (s, v) => Map("status" -> s, "value" -> v.escapeHTML) })
+ .withFieldComputed(_.compliancePercent, _.compliance.computePercent().compliance)
+ .withFieldConst(_.jsid, next)
+ .buildTransformer
}
}
+final case class JsonComponentValueComplianceLine(
+ value: String,
+ unexpanded: String,
+ status: String,
+ statusClass: String,
+ messages: List[Map[String, String]],
+ compliance: ComplianceLevel,
+ compliancePercent: Double,
+ jsid: ProvideNextName
+)
+
+object JsonComponentValueComplianceLine {
+ import com.normation.rudder.domain.reports.ComplianceLevelSerialisation.array.*
+ implicit val encoderJsonComponentValueComplianceLine: JsonEncoder[JsonComponentValueComplianceLine] = DeriveJsonEncoder.gen
+}
+
object ComplianceData extends Loggable {
/*
@@ -252,7 +402,7 @@ object ComplianceData extends Loggable {
rules: Seq[Rule],
globalMode: GlobalPolicyMode,
addOverridden: Boolean
- ): JsTableData[RuleComplianceLine] = {
+ ): RuleComplianceLines = {
// add overridden directive in the list under there rule
val overridesByRules = if (addOverridden) {
@@ -287,13 +437,13 @@ object ComplianceData extends Loggable {
rule,
rule.id,
aggregate.compliance,
- JsTableData(details),
+ details,
policyMode,
explanation,
- JsonTagSerialisation.serializeTags(rule.tags)
+ rule.tags
)
}
- JsTableData(ruleComplianceLine.toList.sortBy(_.id.serialize))
+ RuleComplianceLines(ruleComplianceLine.toList.sortBy(_.id.serialize))
}
private def getOverriddenDirectiveDetails(
@@ -330,10 +480,10 @@ object ComplianceData extends Loggable {
overriddenTechName,
overriddenTechVersion,
ComplianceLevel(),
- JsTableData(Nil),
+ Nil,
policyMode,
explanation,
- JsonTagSerialisation.serializeTags(overriddenDir.tags)
+ overriddenDir.tags
)
}
@@ -357,7 +507,6 @@ object ComplianceData extends Loggable {
val techniqueVersion = directive.techniqueVersion
val components = getComponentsComplianceDetails(directiveStatus.components, includeMessage = true)
val (policyMode, explanation) = computeMode(directive.policyMode)
- val directiveTags = JsonTagSerialisation.serializeTags(directive.tags)
DirectiveComplianceLine(
directive,
techniqueName,
@@ -366,7 +515,7 @@ object ComplianceData extends Loggable {
components,
policyMode,
explanation,
- directiveTags
+ directive.tags
)
}
@@ -378,7 +527,7 @@ object ComplianceData extends Loggable {
private def getComponentsComplianceDetails(
components: List[ComponentStatusReport],
includeMessage: Boolean
- ): JsTableData[ComponentComplianceLine] = {
+ ): List[ComponentComplianceLine] = {
val componentsComplianceData = components.map {
case component: BlockStatusReport =>
BlockComplianceLine(
@@ -405,7 +554,7 @@ object ComplianceData extends Loggable {
)
}
- JsTableData(componentsComplianceData)
+ componentsComplianceData
}
//////////////// Value Report ///////////////
@@ -413,7 +562,7 @@ object ComplianceData extends Loggable {
// From Node Point of view
private def getValuesComplianceDetails(
values: List[ComponentValueStatusReport]
- ): JsTableData[ComponentValueComplianceLine] = {
+ ): List[ComponentValueComplianceLine] = {
val valuesComplianceData = for {
value <- values
} yield {
@@ -431,7 +580,7 @@ object ComplianceData extends Loggable {
severity
)
}
- JsTableData(valuesComplianceData)
+ valuesComplianceData
}
private def getDisplayStatusFromSeverity(severity: String): String = {
diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/ReportDisplayer.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/ReportDisplayer.scala
index 4504870fdca..a55d47689b2 100644
--- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/ReportDisplayer.scala
+++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/ReportDisplayer.scala
@@ -66,6 +66,7 @@ import net.liftweb.util.Helpers.*
import org.joda.time.DateTime
import scala.xml.NodeSeq
import scala.xml.NodeSeq.seqToNodeSeq
+import zio.json.*
/**
* Display the last reports of a server
@@ -136,7 +137,8 @@ class ReportDisplayer(
addOverridden: Boolean,
defaultRunInterval: Int
): AnonFunc = {
- def refreshData: Box[JsCmd] = {
+ implicit val next: ProvideNextName = LiftProvideNextName
+ def refreshData: Box[JsCmd] = {
for {
report <- getReports(node.id)
data <- getComplianceData(node.id, report, addOverridden)
@@ -145,7 +147,7 @@ class ReportDisplayer(
import net.liftweb.util.Helpers.encJs
val intro = encJs(displayIntro(report, node.rudderSettings, defaultRunInterval).toString)
JsRaw(
- s"""refreshTable("${tableId}",${data}); $$("#node-compliance-intro").replaceWith(${intro})"""
+ s"""refreshTable("${tableId}",${data.toJson}); $$("#node-compliance-intro").replaceWith(${intro})"""
) // JsRaw ok, escaped
}
}
@@ -577,7 +579,7 @@ class ReportDisplayer(
nodeId: NodeId,
reportStatus: NodeStatusReport,
addOverridden: Boolean
- ): Box[JsTableData[RuleComplianceLine]] = {
+ ): Box[RuleComplianceLines] = {
for {
directiveLib <- directiveRepository.getFullDirectiveLibrary().toBox
allNodeInfos <- nodeFactRepo.getAll()(CurrentUser.queryContext).toBox
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 6a82e678068..f9c6e553698 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
@@ -68,6 +68,7 @@ import net.liftweb.http.js.JE.*
import net.liftweb.http.js.JsCmds.*
import scala.collection.MapView
import scala.xml.*
+import zio.json.*
case class ScoreChart(scoreValue: ScoreValue, value: Int, noScoreLegend: Option[String]) {
import com.normation.rudder.score.ScoreValue.*
@@ -322,7 +323,7 @@ class HomePage extends StatefulSnippet {
import com.normation.rudder.domain.reports.ComplianceLevelSerialisation.*
(bar.copy(pending = 0).toJsArray, value)
case None =>
- (JsArray(Nil), -1L)
+ (ast.Json.Arr(), -1L)
}
val n4 = System.currentTimeMillis
@@ -330,7 +331,7 @@ class HomePage extends StatefulSnippet {
Script(OnLoad(JsRaw(s"""
homePage(
- ${complianceBar.toJsCmd}
+ ${complianceBar.toJson}
, ${globalCompliance}
, ${data.toJsCmd}
, ${pendingNodes.toJsCmd}
diff --git a/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/rudder/web/services/ComplianceLineTest.scala b/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/rudder/web/services/ComplianceLineTest.scala
index c8c87e8ef05..f65e91e9dd6 100644
--- a/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/rudder/web/services/ComplianceLineTest.scala
+++ b/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/rudder/web/services/ComplianceLineTest.scala
@@ -56,6 +56,7 @@ import org.specs2.mutable.*
import org.specs2.runner.JUnitRunner
import scala.collection.MapView
import scala.util.Random
+import zio.json.*
@RunWith(classOf[JUnitRunner])
class ComplianceLineTest extends Specification with JsonSpecMatcher {
@@ -66,8 +67,10 @@ class ComplianceLineTest extends Specification with JsonSpecMatcher {
val nodes: MapView[NodeId, CoreNodeFact] = mockCompliance.nodeFactRepo.getAll().runNow
val directives: FullActiveTechniqueCategory = mockDirectives.directiveRepo.getFullDirectiveLibrary().runNow
- val stableRandom = new Random(42)
- def freshName(): String = stableRandom.nextLong().toString
+ implicit object StableRandom extends ProvideNextName {
+ val stableRandom = new Random(42)
+ override def nextName: String = stableRandom.nextLong().toString
+ }
"compliance lines serialisation" >> {
val nodeId = NodeId("n1")
@@ -83,8 +86,7 @@ class ComplianceLineTest extends Specification with JsonSpecMatcher {
GlobalPolicyMode(Enforce, PolicyModeOverrides.Always),
true
)
- .json(freshName _)
- .toJsCmd
+ .toJson
lines must equalsJsonSemantic(
"""[
@@ -125,14 +127,14 @@ class ComplianceLineTest extends Specification with JsonSpecMatcher {
| "compliance":[[0,0.0],[0,0.0], [1, 100.0], [0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0]
| ],
| "compliancePercent":100.0,
- | "jsid":"-5025562857975149833"
+ | "jsid":"-5843495416241995736"
| }
| ],
| "noExpand":false,
- | "jsid":"-5843495416241995736"
+ | "jsid":"5111195811822994797"
| }
| ],
- | "jsid":"5694868678511409995",
+ | "jsid":"-1782466964123969572",
| "isSystem":false,
| "policyMode":"audit",
| "explanation":"The Node is configured to enforce but is overridden to audit by this Directive. ",
@@ -144,7 +146,7 @@ class ComplianceLineTest extends Specification with JsonSpecMatcher {
| ]
| }
| ],
- | "jsid":"5111195811822994797",
+ | "jsid":"5086654115216342560",
| "isSystem":false,
| "policyMode":"audit",
| "explanation":"The Node is configured to enforce but is overridden to audit by all Directives. ",
@@ -187,21 +189,21 @@ class ComplianceLineTest extends Specification with JsonSpecMatcher {
| "compliance":[[0,0.0],[0,0.0],[0,0.0], [1, 100.0], [0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0]
| ],
| "compliancePercent":100.0,
- | "jsid":"-6169532649852302182"
+ | "jsid":"-4004755535478349341"
| }
| ],
| "noExpand":false,
- | "jsid":"-1782466964123969572"
+ | "jsid":"8051837266862454915"
| }
| ],
- | "jsid":"6802844026563419272",
+ | "jsid":"7130900098642117381",
| "isSystem":false,
| "policyMode":"enforce",
| "explanation":"Enforce is forced by this Node mode",
| "tags":[]
| }
| ],
- | "jsid":"5086654115216342560",
+ | "jsid":"-7482923245497525943",
| "isSystem":false,
| "policyMode":"enforce",
| "explanation":"enforce mode is forced by this Node",
@@ -244,21 +246,21 @@ class ComplianceLineTest extends Specification with JsonSpecMatcher {
| "compliance":[[0,0.0],[0,0.0],[0,0.0],[0,0.0], [1, 100.0], [0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0],[0,0.0]
| ],
| "compliancePercent":0.0,
- | "jsid":"8552898714322622292"
+ | "jsid":"-3210362905434573697"
| }
| ],
| "noExpand":false,
- | "jsid":"-4004755535478349341"
+ | "jsid":"-7610621359446545191"
| }
| ],
- | "jsid":"-1488139573943419793",
+ | "jsid":"-7912908803613548926",
| "isSystem":false,
| "policyMode":"enforce",
| "explanation":"Enforce is forced by this Node mode",
| "tags":[]
| }
| ],
- | "jsid":"8051837266862454915",
+ | "jsid":"-4565385657661118002",
| "isSystem":false,
| "policyMode":"enforce",
| "explanation":"enforce mode is forced by this Node",