diff --git a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/entity/KPIEntity.kt b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/entity/KPIEntity.kt index 7f9af6821337c7128faa47ca9757ac5be7535769..423e37641b6dfadb9895f75316bef99b16c3db9a 100644 --- a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/entity/KPIEntity.kt +++ b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/entity/KPIEntity.kt @@ -1,9 +1,8 @@ package de.fraunhofer.iem.app.kpi.entity import de.fraunhofer.iem.app.toolRun.entity.ToolRunEntity -import de.fraunhofer.iem.kpiCalculator.core.hierarchy.KpiCalculationNode -import de.fraunhofer.iem.kpiCalculator.core.hierarchy.RawKpiCalculationNode import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiId +import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpi import jakarta.persistence.* import org.hibernate.annotations.CurrentTimestamp import org.hibernate.generator.EventType @@ -34,9 +33,9 @@ class KPIEntity( @Column(name = "timestamp") var createdAt: Timestamp? = null ) { - fun toCalculationDto(): KpiCalculationNode { + fun toCalculationDto(): RawValueKpi { // TODO: fixme - return RawKpiCalculationNode( + return RawValueKpi( kind = this.kind, score = this.score, ) diff --git a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/service/KPIService.kt b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/service/KPIService.kt index 2611f5b49ab52db91e8c69a27373af776629765e..3c39cc7afb57f858221e1872a3d117476b6ba291 100644 --- a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/service/KPIService.kt +++ b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/service/KPIService.kt @@ -8,7 +8,7 @@ import de.fraunhofer.iem.app.tools.occmd.enumeration.Checks import de.fraunhofer.iem.app.tools.occmd.json.RawResultJson import de.fraunhofer.iem.kpiCalculator.core.strategy.AggregationKPICalculationStrategy import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiId -import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpiDto +import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpi import org.springframework.stereotype.Service @Service @@ -49,14 +49,14 @@ class KPIService { /** * This method calculates the OccmdCreateKpiDtos based upon the given tool results. */ - fun calculateOccmdKpis(rawOccmdResults: List<RawResultJson>): List<RawValueKpiDto> { - val kpis = mutableListOf<RawValueKpiDto>() + fun calculateOccmdKpis(rawOccmdResults: List<RawResultJson>): List<RawValueKpi> { + val kpis = mutableListOf<RawValueKpi>() rawOccmdResults.forEach { when (val check = Checks.fromString(it.check)) { Checks.CheckedInBinaries -> kpis.add( - RawValueKpiDto( + RawValueKpi( kind = KpiId.CHECKED_IN_BINARIES, score = (it.score * 100).toInt() ) @@ -64,7 +64,7 @@ class KPIService { Checks.SastUsageBasic -> kpis.add( - RawValueKpiDto( + RawValueKpi( kind = KpiId.SAST_USAGE, score = (it.score * 100).toInt() ) @@ -72,7 +72,7 @@ class KPIService { Checks.Secrets -> kpis.add( - RawValueKpiDto( + RawValueKpi( kind = KpiId.SECRETS, score = (it.score * 100).toInt() ) @@ -80,7 +80,7 @@ class KPIService { Checks.CommentsInCode -> kpis.add( - RawValueKpiDto( + RawValueKpi( kind = KpiId.COMMENTS_IN_CODE, score = (it.score * 100).toInt() ) @@ -88,7 +88,7 @@ class KPIService { Checks.DocumentationInfrastructure -> kpis.add( - RawValueKpiDto( + RawValueKpi( kind = KpiId.DOCUMENTATION_INFRASTRUCTURE, score = (it.score * 100).toInt() ) @@ -106,17 +106,17 @@ class KPIService { * Creates a named map of RepositoryCreateDtos, based upon the provided repository details. * This method only returns raw KPIs. */ - fun calculateRepositoryDetailsKpis(repoDetailsDto: RepositoryDetailsDto): List<RawValueKpiDto> { + fun calculateRepositoryDetailsKpis(repoDetailsDto: RepositoryDetailsDto): List<RawValueKpi> { return listOf( - RawValueKpiDto( + RawValueKpi( kind = KpiId.NUMBER_OF_COMMITS, score = repoDetailsDto.numberOfCommits ), - RawValueKpiDto( + RawValueKpi( kind = KpiId.NUMBER_OF_SIGNED_COMMITS, score = repoDetailsDto.numberOfSignedCommits ), - RawValueKpiDto( + RawValueKpi( kind = KpiId.IS_DEFAULT_BRANCH_PROTECTED, score = if (repoDetailsDto.isDefaultBranchProtected) 100 else 0 ) diff --git a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/repository/dto/RawValueKpiCreateDtoExtension.kt b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/repository/dto/RawValueKpiCreateDtoExtension.kt index c9c41ab897f7b034ee4987ed2bf6c820ef7c2685..cb061351c53f47054aa46e80d2cae103dc018aeb 100644 --- a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/repository/dto/RawValueKpiCreateDtoExtension.kt +++ b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/repository/dto/RawValueKpiCreateDtoExtension.kt @@ -1,11 +1,11 @@ package de.fraunhofer.iem.app.repository.dto import de.fraunhofer.iem.app.toolRun.entity.ToolRunEntity -import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpiDto +import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpi import java.sql.Timestamp import java.time.Instant -fun RawValueKpiDto.toDbObject(toolRun: ToolRunEntity): de.fraunhofer.iem.app.kpi.entity.KPIEntity { +fun RawValueKpi.toDbObject(toolRun: ToolRunEntity): de.fraunhofer.iem.app.kpi.entity.KPIEntity { return de.fraunhofer.iem.app.kpi.entity.KPIEntity( kind = this.kind, score = this.score, diff --git a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/repository/service/RepositoryService.kt b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/repository/service/RepositoryService.kt index 177a7a2babb5e7892bf567d083f7e7ceaf3ff6a1..359ccc9924f4534c200724102e42dd70d4496227 100644 --- a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/repository/service/RepositoryService.kt +++ b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/repository/service/RepositoryService.kt @@ -13,7 +13,7 @@ import de.fraunhofer.iem.app.toolRun.dto.ToolRunDto import de.fraunhofer.iem.app.toolRun.entity.LanguageEntity import de.fraunhofer.iem.app.toolRun.entity.ToolRunEntity import de.fraunhofer.iem.app.toolRun.repository.ToolRunRepository -import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpiDto +import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpi import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Propagation import org.springframework.transaction.annotation.Transactional @@ -30,7 +30,7 @@ class RepositoryService( @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) fun createToolRun( repoDto: RepositoryCreateDto, - kpiToolList: List<Pair<CreateToolDto, List<RawValueKpiDto>>>, + kpiToolList: List<Pair<CreateToolDto, List<RawValueKpi>>>, languageMap: Map<String, Float> ) { val repo = getOrCreate(repoDto) diff --git a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/toolRun/service/ToolRunService.kt b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/toolRun/service/ToolRunService.kt index 8b9c3b7b990378ee4aea44f8dbde4c66c02729c5..5c003c1c1a43a9cd1f7f7e9c94d1c027ccb3ae21 100644 --- a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/toolRun/service/ToolRunService.kt +++ b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/toolRun/service/ToolRunService.kt @@ -73,7 +73,7 @@ class ToolRunService( // goal is to match the behavior of the old implementation. val rawValueKpiCreateDtos = CveAdapter.transformDataToKpi(vulnerabilityDtos).filterIsInstance<AdapterResult.Success>() - .map { it.rawValueKpiDto } + .map { it.rawValueKpi } Pair(ortService.getToolDto(), rawValueKpiCreateDtos) }, diff --git a/kpi-calculator/adapter/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/adapter/cve/CveAdapter.kt b/kpi-calculator/adapter/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/adapter/cve/CveAdapter.kt index e71fdb38c5813820e216a5916938dd782bc84256..931c3a3db6bf4ddf9dc716dbd34dd694361b2fa4 100644 --- a/kpi-calculator/adapter/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/adapter/cve/CveAdapter.kt +++ b/kpi-calculator/adapter/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/adapter/cve/CveAdapter.kt @@ -5,7 +5,7 @@ import de.fraunhofer.iem.kpiCalculator.model.adapter.AdapterResult import de.fraunhofer.iem.kpiCalculator.model.adapter.ErrorType import de.fraunhofer.iem.kpiCalculator.model.adapter.VulnerabilityDto import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiId -import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpiDto +import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpi object CveAdapter : KpiAdapter<VulnerabilityDto> { override val kpiId: KpiId @@ -16,7 +16,7 @@ object CveAdapter : KpiAdapter<VulnerabilityDto> { .map { return@map if (isValid(it)) { AdapterResult.Success( - RawValueKpiDto( + RawValueKpi( kind = kpiId, score = (it.severity * 10).toInt() ) diff --git a/kpi-calculator/adapter/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/adapter/cve/CveAdapterTest.kt b/kpi-calculator/adapter/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/adapter/cve/CveAdapterTest.kt index 333ea6a0dac01539e25b78544add821637412840..7e8b07aba998fbc1a8b87ece0addd0757e010b60 100644 --- a/kpi-calculator/adapter/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/adapter/cve/CveAdapterTest.kt +++ b/kpi-calculator/adapter/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/adapter/cve/CveAdapterTest.kt @@ -21,7 +21,7 @@ class CveAdapterTest { ) when (validKpi) { is AdapterResult.Success -> { - assert(validKpi.rawValueKpiDto.score in (0..100)) + assert(validKpi.rawValueKpi.score in (0..100)) } is AdapterResult.Error -> { diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculator.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculator.kt index bf9fa0c885607452e542739b245cb3ee700629e8..9e9dc5fb6595ed7a51370a373a9a33c71deb7bfb 100644 --- a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculator.kt +++ b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculator.kt @@ -3,7 +3,7 @@ package de.fraunhofer.iem.kpiCalculator.core import de.fraunhofer.iem.kpiCalculator.core.hierarchy.KpiCalculationNode import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiId import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiStrategyId -import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpiDto +import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpi import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiHierarchy import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiNode import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiResultNode @@ -13,9 +13,9 @@ sealed class KpiResult {} object KpiCalculator { //XXX: Setup Logger - fun calculateKpis(hierarchy: KpiHierarchy, rawValueKpiDtos: List<RawValueKpiDto>): KpiResultNode { + fun calculateKpis(hierarchy: KpiHierarchy, rawValueKpis: List<RawValueKpi>): KpiResultNode { val root = hierarchy.rootNode - val connectedHierarchyRoot = connectKpiHierarchyToRawValues(root, rawValueKpiDtos) + val connectedHierarchyRoot = connectKpiHierarchyToRawValues(root, rawValueKpis) depthFirstTraversal(connectedHierarchyRoot) { it.calculateKpi() } return KpiCalculationNode.to(connectedHierarchyRoot) } @@ -31,7 +31,7 @@ object KpiCalculator { node.hierarchyEdges .forEach { child -> - depthFirstTraversal(node = child.target, seen = seen, action) + depthFirstTraversal(node = child.to, seen = seen, action) } action(node) @@ -40,57 +40,59 @@ object KpiCalculator { private fun connectKpiHierarchyToRawValues( node: KpiNode, - rawValueKpiDtos: List<RawValueKpiDto> + rawValueKpis: List<RawValueKpi> ): KpiCalculationNode { - val kindToValues = mutableMapOf<KpiId, MutableList<RawValueKpiDto>>() + val kindToValues = mutableMapOf<KpiId, MutableList<RawValueKpi>>() KpiId.entries.forEach { kindToValues[it] = mutableListOf() } - rawValueKpiDtos.forEach { + rawValueKpis.forEach { kindToValues[it.kind]!!.add(it) } val calculationRoot = KpiCalculationNode.from(node) + val updates: MutableList<Triple<KpiCalculationNode, KpiCalculationNode, List<RawValueKpi>>> = mutableListOf() + depthFirstTraversal( node = calculationRoot ) { currentNode -> - // TODO: this is complicated as we don't know how many children we will have. - // this scenario might reappear again so we should come up with a better solution - if ( - currentNode.kind == KpiId.MAXIMAL_VULNERABILITY && - currentNode.hierarchyEdges.size == 1 && - currentNode.hierarchyEdges.first().target.kind == KpiId.VULNERABILITY_SCORE && - !kindToValues[KpiId.VULNERABILITY_SCORE].isNullOrEmpty() - ) { - val vulnerabilityCalcNodes = kindToValues[KpiId.VULNERABILITY_SCORE]?.map { - val newNode = KpiCalculationNode( - kind = KpiId.VULNERABILITY_SCORE, - calculationStrategy = KpiStrategyId.RAW_VALUE_STRATEGY - ) - newNode.setScore(it.score) - newNode - } + val correspondingRawValue = kindToValues[currentNode.kind] + val parent = currentNode.parent - if (!vulnerabilityCalcNodes.isNullOrEmpty()) { - currentNode.clearChildren() - val weights = 1.0 / vulnerabilityCalcNodes.size - vulnerabilityCalcNodes.forEach { - currentNode.addChild(it, weights) - } + if (!correspondingRawValue.isNullOrEmpty() && parent != null) { + updates.add(Triple(parent, currentNode, correspondingRawValue)) + } + } - } - } else { - val correspondingRawValue = kindToValues[currentNode.kind] - if (!correspondingRawValue.isNullOrEmpty()) { - if (correspondingRawValue.size > 1) { - println("We only expect one RAW value for every KPI") - } - currentNode.setScore(correspondingRawValue.first().score) - } + updates.forEach { + val parent = it.first + val currentNode = it.second + val correspondingRawValue = it.third + + parent.removeChild(currentNode) + + val rawValueNodes = correspondingRawValue.map { rawValue -> + val newNode = KpiCalculationNode( + kind = rawValue.kind, + calculationStrategy = KpiStrategyId.RAW_VALUE_STRATEGY, + parent = parent + ) + newNode.setScore(rawValue.score) + newNode } + if (rawValueNodes.isNotEmpty()) { + // TODO: this is currently incorrect and also breaks the remaining nodes weights + val weights = 1.0 / rawValueNodes.size + parent.hierarchyEdges.size + rawValueNodes.forEach { rawValueNode -> + parent.addChild(rawValueNode, weights) + } + + } } + + return calculationRoot } } diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiCalculationNode.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiCalculationNode.kt index 0d76f23043c4de2a5bfe5264e3eba335445e8327..afdd7c66e582ba5dd6268f64001ab82146ad9c98 100644 --- a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiCalculationNode.kt +++ b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiCalculationNode.kt @@ -11,28 +11,33 @@ import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiResultEdge import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiResultNode -internal class KpiCalculationNode(val kind: KpiId, val calculationStrategy: KpiStrategyId) { +internal class KpiCalculationNode( + val kind: KpiId, + val calculationStrategy: KpiStrategyId, + val parent: KpiCalculationNode? +) { private var score: KpiCalculationResult = KpiCalculationResult.Empty fun setScore(score: Int) { this.score = KpiCalculationResult.Success(score) } + private val _hierarchyEdges: MutableList<KpiHierarchyEdge> = mutableListOf() val hierarchyEdges: List<KpiHierarchyEdge> get() = _hierarchyEdges fun addChild(node: KpiCalculationNode, weight: Double) { - _hierarchyEdges.add(KpiHierarchyEdge(node, weight)) + _hierarchyEdges.add(KpiHierarchyEdge(to = node, from = this, weight = weight)) } - fun clearChildren() { - _hierarchyEdges.clear() + fun removeChild(node: KpiCalculationNode) { + _hierarchyEdges.removeIf { it.to == node } } fun calculateKpi(): KpiCalculationResult { val strategyData = hierarchyEdges.map { - Pair(it.target.score, it.weight) + Pair(it.to.score, it.weight) } score = when (calculationStrategy) { KpiStrategyId.RAW_VALUE_STRATEGY -> @@ -59,7 +64,7 @@ internal class KpiCalculationNode(val kind: KpiId, val calculationStrategy: KpiS kpiResult = node.score, children = node.hierarchyEdges.map { KpiResultEdge( - target = to(it.target), + target = to(it.to), weight = it.weight ) } @@ -67,10 +72,17 @@ internal class KpiCalculationNode(val kind: KpiId, val calculationStrategy: KpiS } fun from(node: KpiNode): KpiCalculationNode { - val calcNode = KpiCalculationNode(node.kpiId, calculationStrategy = node.strategyType) + return from(node, parent = null) + } + + private fun from(node: KpiNode, parent: KpiCalculationNode? = null): KpiCalculationNode { + + val calcNode = + KpiCalculationNode(kind = node.kpiId, parent = parent, calculationStrategy = node.strategyType) val children = node.children.map { child -> KpiHierarchyEdge( - target = from(child.target), + to = from(child.target, calcNode), + from = calcNode, weight = child.weight ) } diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiHierarchyEdge.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiHierarchyEdge.kt index ddb5b74b8acf3c957762d16df1b480ee70edd2f5..03c1a0df664aa42b3c5c32edd90ea2a1205a33d1 100644 --- a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiHierarchyEdge.kt +++ b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiHierarchyEdge.kt @@ -1,6 +1,7 @@ package de.fraunhofer.iem.kpiCalculator.core.hierarchy internal data class KpiHierarchyEdge( - val target: KpiCalculationNode, + val from: KpiCalculationNode, + val to: KpiCalculationNode, val weight: Double, ) diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/AggregationKPICalculationStrategy.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/AggregationKPICalculationStrategy.kt index c4fefe356f6f346cafa964d61c5c6571ceb0007e..fd7538a8106906c6b5ee9a35e1f9afd3d553af65 100644 --- a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/AggregationKPICalculationStrategy.kt +++ b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/AggregationKPICalculationStrategy.kt @@ -12,15 +12,36 @@ object AggregationKPICalculationStrategy { * The method returns the KPIs value as well as the updated * KPIHierarchyEdgeDtos with the actual used weight. */ - fun calculateKPI(childScores: List<Pair<KpiCalculationResult, Double>>): KpiCalculationResult { + fun calculateKPI( + childScores: List<Pair<KpiCalculationResult, Double>>, + considerIncomplete: Boolean = true + ): KpiCalculationResult { if (childScores.isEmpty()) { return KpiCalculationResult.Empty } - val successScores = childScores.filter { it.first is KpiCalculationResult.Success } + val incompleteResults = if (considerIncomplete) { + childScores.mapNotNull { + val res = it.first + if (res is KpiCalculationResult.Incomplete) { + Pair(KpiCalculationResult.Success(score = res.score), it.second) + } else { + null + } + } + } else { + emptyList() + } + + val successScores = listOf( + childScores.filter { it.first is KpiCalculationResult.Success }, + incompleteResults + ).flatten() + val failed = childScores - .filter { it.first !is KpiCalculationResult.Success } + .filter { it.first is KpiCalculationResult.Error || (it.first is KpiCalculationResult.Empty) } + val missingEdgeWeights = failed.sumOf { it.second } val additionalWeight = @@ -38,7 +59,7 @@ object AggregationKPICalculationStrategy { }.toInt() - if (failed.isNotEmpty()) { + if (incompleteResults.isNotEmpty()) { return KpiCalculationResult.Incomplete( score = aggregation, reason = "There were ${failed.size} elements missing during aggregation.", diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/RatioKPICalculationStrategy.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/RatioKPICalculationStrategy.kt index b08c3b1b108a338c43d70073d4cfa27a04a0cd4d..21ef54887989dfce393019fe365668debf6da2e4 100644 --- a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/RatioKPICalculationStrategy.kt +++ b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/RatioKPICalculationStrategy.kt @@ -6,10 +6,28 @@ object RatioKPICalculationStrategy { /** * Returns smallerValue / biggerValue, regardless in which order the values are given. */ - fun calculateKPI(childScores: List<Pair<KpiCalculationResult, Double>>): KpiCalculationResult { + fun calculateKPI( + childScores: List<Pair<KpiCalculationResult, Double>>, + considerIncomplete: Boolean = true + ): KpiCalculationResult { - val successScores = - childScores.map { it.first }.filterIsInstance<KpiCalculationResult.Success>() + val incompleteResults = if (considerIncomplete) { + childScores.mapNotNull { + val res = it.first + if (res is KpiCalculationResult.Incomplete) { + KpiCalculationResult.Success(res.score) + } else { + null + } + } + } else { + emptyList() + } + + val successScores = listOf( + childScores.map { it.first }.filterIsInstance<KpiCalculationResult.Success>(), + incompleteResults + ).flatten() if (successScores.size != 2) { return KpiCalculationResult.Error( @@ -18,23 +36,36 @@ object RatioKPICalculationStrategy { ) } - val firstValue = successScores.first().score - val secondValue = successScores[1].score + val biggerValue = if (successScores.first().score > successScores[1].score) { + successScores.first().score + } else { + successScores.last().score + } - if (firstValue == secondValue && secondValue == 0) { + val smallerValue = if (successScores.first().score < successScores[1].score) { + successScores.first().score + } else { + successScores[1].score + } + + if (biggerValue == smallerValue && smallerValue == 0) { return KpiCalculationResult.Success(0) } - try { - if (firstValue >= secondValue) { - return KpiCalculationResult.Success( - score = ((secondValue.toDouble() / firstValue.toDouble()) * 100).toInt() + + return try { + if (incompleteResults.isEmpty()) { + KpiCalculationResult.Success( + score = ((smallerValue.toDouble() / biggerValue.toDouble()) * 100).toInt() + ) + } else { + KpiCalculationResult.Incomplete( + score = ((smallerValue.toDouble() / biggerValue.toDouble()) * 100).toInt(), + reason = "Incomplete results.", + missingWeights = 0.0 ) } - return KpiCalculationResult.Success( - score = ((firstValue.toDouble() / secondValue.toDouble()) * 100).toInt() - ) } catch (e: Exception) { - return KpiCalculationResult.Error(e.message ?: e.toString()) + KpiCalculationResult.Error(e.message ?: e.toString()) } } } diff --git a/kpi-calculator/core/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculatorTest.kt b/kpi-calculator/core/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculatorTest.kt index dfffba34ccd4e5491dc73d43dc9743027852a397..a5f27f67bb46c39ea778113ffaf9396e76922929 100644 --- a/kpi-calculator/core/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculatorTest.kt +++ b/kpi-calculator/core/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculatorTest.kt @@ -2,7 +2,7 @@ package de.fraunhofer.iem.kpiCalculator.core import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiId import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiStrategyId -import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpiDto +import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpi import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.* import org.junit.jupiter.api.Test import kotlin.test.assertEquals @@ -12,28 +12,28 @@ class KpiCalculatorTest { @Test fun calculateDefaultHierarchyKpis() { - val rawValueKpiDtos = listOf( - RawValueKpiDto(kind = KpiId.VULNERABILITY_SCORE, score = 8), - RawValueKpiDto(kind = KpiId.VULNERABILITY_SCORE, score = 9), - RawValueKpiDto(kind = KpiId.CHECKED_IN_BINARIES, score = 100), - RawValueKpiDto(kind = KpiId.COMMENTS_IN_CODE, score = 80), - RawValueKpiDto(kind = KpiId.NUMBER_OF_COMMITS, score = 90), - RawValueKpiDto(kind = KpiId.IS_DEFAULT_BRANCH_PROTECTED, score = 100), - RawValueKpiDto(kind = KpiId.NUMBER_OF_SIGNED_COMMITS, score = 80), - RawValueKpiDto(kind = KpiId.SECRETS, score = 80), - RawValueKpiDto(kind = KpiId.SAST_USAGE, score = 80), - RawValueKpiDto(kind = KpiId.DOCUMENTATION_INFRASTRUCTURE, score = 80), + val rawValueKpis = listOf( + RawValueKpi(kind = KpiId.VULNERABILITY_SCORE, score = 8), + RawValueKpi(kind = KpiId.VULNERABILITY_SCORE, score = 9), + RawValueKpi(kind = KpiId.CHECKED_IN_BINARIES, score = 100), + RawValueKpi(kind = KpiId.COMMENTS_IN_CODE, score = 80), + RawValueKpi(kind = KpiId.NUMBER_OF_COMMITS, score = 90), + RawValueKpi(kind = KpiId.IS_DEFAULT_BRANCH_PROTECTED, score = 100), + RawValueKpi(kind = KpiId.NUMBER_OF_SIGNED_COMMITS, score = 80), + RawValueKpi(kind = KpiId.SECRETS, score = 80), + RawValueKpi(kind = KpiId.SAST_USAGE, score = 80), + RawValueKpi(kind = KpiId.DOCUMENTATION_INFRASTRUCTURE, score = 80), ) - val res = KpiCalculator.calculateKpis(DefaultHierarchy.get(), rawValueKpiDtos) + val res = KpiCalculator.calculateKpis(DefaultHierarchy.get(), rawValueKpis) println(res) } @Test fun calculateMaxKpis() { - val rawValueKpiDtos = listOf( - RawValueKpiDto(kind = KpiId.VULNERABILITY_SCORE, score = 82), - RawValueKpiDto(kind = KpiId.VULNERABILITY_SCORE, score = 90), - RawValueKpiDto(kind = KpiId.VULNERABILITY_SCORE, score = 65), + val rawValueKpis = listOf( + RawValueKpi(kind = KpiId.VULNERABILITY_SCORE, score = 82), + RawValueKpi(kind = KpiId.VULNERABILITY_SCORE, score = 90), + RawValueKpi(kind = KpiId.VULNERABILITY_SCORE, score = 65), ) val root = KpiNode( @@ -58,7 +58,7 @@ class KpiCalculatorTest { ) val hierarchy = KpiHierarchy.create(root) - val res = KpiCalculator.calculateKpis(hierarchy, rawValueKpiDtos) + val res = KpiCalculator.calculateKpis(hierarchy, rawValueKpis) val result = res.kpiResult if (result is KpiCalculationResult.Success) { @@ -70,10 +70,10 @@ class KpiCalculatorTest { @Test fun calculateMaxKpisIncomplete() { - val rawValueKpiDtos = listOf( - RawValueKpiDto(kind = KpiId.VULNERABILITY_SCORE, score = 82), - RawValueKpiDto(kind = KpiId.VULNERABILITY_SCORE, score = 90), - RawValueKpiDto(kind = KpiId.VULNERABILITY_SCORE, score = 65), + val rawValueKpis = listOf( + RawValueKpi(kind = KpiId.VULNERABILITY_SCORE, score = 82), + RawValueKpi(kind = KpiId.VULNERABILITY_SCORE, score = 90), + RawValueKpi(kind = KpiId.VULNERABILITY_SCORE, score = 65), ) val root = KpiNode( @@ -106,9 +106,10 @@ class KpiCalculatorTest { ) val hierarchy = KpiHierarchy.create(root) - val res = KpiCalculator.calculateKpis(hierarchy, rawValueKpiDtos) + val res = KpiCalculator.calculateKpis(hierarchy, rawValueKpis) val result = res.kpiResult + println(res) if (result is KpiCalculationResult.Incomplete) { assertEquals(90, result.score) } else { diff --git a/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/adapter/AdapterResult.kt b/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/adapter/AdapterResult.kt index bdb5acf4be959ea80ecaaa0466098707a0ab3020..02d35a75ccc9f84ebe9c370a0fca80d538a32562 100644 --- a/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/adapter/AdapterResult.kt +++ b/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/adapter/AdapterResult.kt @@ -1,10 +1,10 @@ package de.fraunhofer.iem.kpiCalculator.model.adapter -import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpiDto +import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpi enum class ErrorType { DATA_VALIDATION_ERROR } sealed class AdapterResult { - data class Success(val rawValueKpiDto: RawValueKpiDto) : AdapterResult() + data class Success(val rawValueKpi: RawValueKpi) : AdapterResult() data class Error(val type: ErrorType) : AdapterResult() } diff --git a/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/RawValueKpi.kt b/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/RawValueKpi.kt new file mode 100644 index 0000000000000000000000000000000000000000..916cf1538fe402c4d309ca7de26a0a7560815f17 --- /dev/null +++ b/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/RawValueKpi.kt @@ -0,0 +1,3 @@ +package de.fraunhofer.iem.kpiCalculator.model.kpi + +data class RawValueKpi(val kind: KpiId, val score: Int) diff --git a/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/RawValueKpiDto.kt b/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/RawValueKpiDto.kt deleted file mode 100644 index ef3e322c56eecc88e57ad5b447c297753ae3c5a3..0000000000000000000000000000000000000000 --- a/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/RawValueKpiDto.kt +++ /dev/null @@ -1,3 +0,0 @@ -package de.fraunhofer.iem.kpiCalculator.model.kpi - -data class RawValueKpiDto(val kind: KpiId, val score: Int)