diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/KPIDto.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/KPIDto.kt new file mode 100644 index 0000000000000000000000000000000000000000..8c5e774e87afba0c951960e93069925b33a4d162 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/KPIDto.kt @@ -0,0 +1,31 @@ +package de.fraunhofer.iem.dataprovider.kpi + +import de.fraunhofer.iem.dataprovider.kpi.model.KPI +import de.fraunhofer.iem.dataprovider.kpi.strategy.KPICalculationStrategy +import java.sql.Timestamp +import java.time.Instant + + +class KPIDto(val name: String, val description: String, val isRoot: Boolean, private val calculationStrategy: KPICalculationStrategy) { + val hierarchyEdges: MutableList<KPIHierarchyEdgeDto> = mutableListOf<KPIHierarchyEdgeDto>() + var value: Int? = null + + fun calculateKPI() { + this.value = calculationStrategy.calculateKPI(this.hierarchyEdges); + } + + fun addChildKPI(kpiDto: KPIDto, weight: Double) { + val hierarchyEdge = KPIHierarchyEdgeDto(this, kpiDto, weight) + this.hierarchyEdges.add(hierarchyEdge) + } +} + +fun KPIDto.toDbObject(): KPI { + val kpi = KPI() + kpi.name = this.name + kpi.value = this.value + kpi.description = this.description + kpi.isRoot = this.isRoot + kpi.timestamp = Timestamp.from(Instant.now()) + return kpi +} \ No newline at end of file diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/KPIHierarchyEdgeDto.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/KPIHierarchyEdgeDto.kt new file mode 100644 index 0000000000000000000000000000000000000000..f0bd29077bd1658325702cc79c7a609ab60fee8c --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/KPIHierarchyEdgeDto.kt @@ -0,0 +1,3 @@ +package de.fraunhofer.iem.dataprovider.kpi + +data class KPIHierarchyEdgeDto (val from: KPIDto, val to: KPIDto, val weight: Double) \ No newline at end of file diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/model/KPI.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/model/KPI.kt new file mode 100644 index 0000000000000000000000000000000000000000..fc1f2b3702022c9f40aec712f59fc2bb95f78d88 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/model/KPI.kt @@ -0,0 +1,51 @@ +package de.fraunhofer.iem.dataprovider.kpi.model + +import de.fraunhofer.iem.dataprovider.toolRun.model.Repository +import jakarta.persistence.* +import org.hibernate.annotations.JdbcTypeCode +import org.hibernate.type.SqlTypes +import java.sql.Timestamp +import java.util.* + +@Entity +@Table(name = "kpi") +class KPI { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id", nullable = false) + var id: UUID? = null + + @ManyToOne + @JoinColumn(name = "repository_id") + var repository: Repository? = null + + @OneToMany(mappedBy = "from", cascade = [CascadeType.MERGE, CascadeType.REMOVE], orphanRemoval = true) + var hierarchyEdges: MutableSet<KPIHierarchyEdge> = mutableSetOf() + + @Column(name = "value") + var value: Int? = null + + @Column(name = "isRoot") + var isRoot: Boolean? = null + + @Column(name = "name") + var name: String? = null + + @Column(name = "description") + var description: String? = null + + @JdbcTypeCode(SqlTypes.TIMESTAMP) + @Column(name = "timestamp") + var timestamp: Timestamp? = null + + fun addHierarchyEdges(kpiHierarchyEdges: Collection<KPIHierarchyEdge>) { + kpiHierarchyEdges.forEach { + this.addHierarchyEdge(it) + } + } + + fun addHierarchyEdge(hierarchyEdge: KPIHierarchyEdge) { + hierarchyEdge.from = this + this.hierarchyEdges.add(hierarchyEdge) + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/model/KPIHierarchyEdge.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/model/KPIHierarchyEdge.kt new file mode 100644 index 0000000000000000000000000000000000000000..6994560f93a2b26b438c77aec9e3aa5c1ea88bd9 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/model/KPIHierarchyEdge.kt @@ -0,0 +1,24 @@ +package de.fraunhofer.iem.dataprovider.kpi.model + +import jakarta.persistence.* +import java.util.* + +@Entity +@Table(name = "kpi_hierarchy_edge") +class KPIHierarchyEdge { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id", nullable = false) + var id: UUID? = null + + @ManyToOne(cascade = [CascadeType.MERGE]) + @JoinColumn(name = "from_id") + var from: KPI? = null + + @ManyToOne(cascade = [CascadeType.MERGE]) + @JoinColumn(name = "to_id") + var to: KPI? = null + + @Column(name = "weight") + var weight: Double? = null +} \ No newline at end of file diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/repository/KPIHierarchyEdgeRepository.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/repository/KPIHierarchyEdgeRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..5d455ef31b09b1b26aa62cf7baf8da69ecd64166 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/repository/KPIHierarchyEdgeRepository.kt @@ -0,0 +1,7 @@ +package de.fraunhofer.iem.dataprovider.kpi.repository + +import de.fraunhofer.iem.dataprovider.kpi.model.KPIHierarchyEdge +import org.springframework.data.jpa.repository.JpaRepository +import java.util.* + +interface KPIHierarchyEdgeRepository: JpaRepository<KPIHierarchyEdge, UUID> {} diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/repository/KPIRepository.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/repository/KPIRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..6a9ddd84aef58704d55282a3cf1c2ec4e11c955d --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/repository/KPIRepository.kt @@ -0,0 +1,9 @@ +package de.fraunhofer.iem.dataprovider.kpi.repository + +import de.fraunhofer.iem.dataprovider.kpi.model.KPI +import org.springframework.data.jpa.repository.JpaRepository +import java.util.* + +interface KPIRepository : JpaRepository<KPI, UUID> { + fun findByRepository_Id(id: UUID): List<KPI> +} \ No newline at end of file diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/service/KPIService.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/service/KPIService.kt new file mode 100644 index 0000000000000000000000000000000000000000..46863bf5dc9c8dcf8540cae28c72b667c2f85c09 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/service/KPIService.kt @@ -0,0 +1,93 @@ +package de.fraunhofer.iem.dataprovider.kpi.service + +import de.fraunhofer.iem.dataprovider.kpi.KPIDto +import de.fraunhofer.iem.dataprovider.kpi.model.KPI +import de.fraunhofer.iem.dataprovider.kpi.model.KPIHierarchyEdge +import de.fraunhofer.iem.dataprovider.kpi.repository.KPIHierarchyEdgeRepository +import de.fraunhofer.iem.dataprovider.kpi.repository.KPIRepository +import de.fraunhofer.iem.dataprovider.kpi.toDbObject +import de.fraunhofer.iem.dataprovider.logger.getLogger +import de.fraunhofer.iem.dataprovider.toolRun.model.Repository +import org.springframework.stereotype.Service + +@Service +class KPIService(private val kpiRepository: KPIRepository, private val kpiHierarchyEdgeRepository: KPIHierarchyEdgeRepository) { + + private val logger = getLogger(javaClass) + + private fun calculateKPIsRecursively(kpi: KPIDto, visited: MutableSet<KPIDto>) { + // Check if the KPI has already been visited + if (visited.contains(kpi)) { + return + } + + // Check if the KPI has child KPIs + if (kpi.hierarchyEdges.isEmpty()) { + // Leaf node, calculate the KPI value + kpi.calculateKPI() + visited.add(kpi) + return + } + + // Recursively calculate child KPIs first + for (childEdge in kpi.hierarchyEdges) { + calculateKPIsRecursively(childEdge.to, visited) + } + + // Calculate the KPI value after processing child KPIs + kpi.calculateKPI() + visited.add(kpi) + } + + fun calculateKPIs(rootKPI: KPIDto) { + val visited: MutableSet<KPIDto> = mutableSetOf() + calculateKPIsRecursively(rootKPI, visited) + } + + fun storeAndPurgeOld(repository: Repository, rootKPI: KPIDto) { + purgeAllKPIs(repository) + val kpiDtoToEntityMapping: MutableMap<KPIDto, KPI> = mutableMapOf() + storeKPI(repository, rootKPI, kpiDtoToEntityMapping) + } + + private fun storeKPI(repository: Repository, kpi: KPIDto, kpiDtoToEntityMapping: MutableMap<KPIDto, KPI>) { + // already visited + if (kpiDtoToEntityMapping.contains(kpi)) { + return; + } + + if (kpi.hierarchyEdges.isEmpty()) { + // leaf node + val kpiEntity = kpi.toDbObject() + kpiEntity.repository = repository + + kpiDtoToEntityMapping[kpi] = kpiEntity + kpiRepository.save(kpiEntity) + logger.info("Storing leaf node ${kpi.name}") + return; + } + + for (childEdge in kpi.hierarchyEdges) { + storeKPI(repository, childEdge.to, kpiDtoToEntityMapping) + } + + val kpiEntity = kpi.toDbObject() + kpiEntity.repository = repository + kpiDtoToEntityMapping[kpi] = kpiEntity + kpiRepository.save(kpiEntity) + logger.info("Storing node ${kpi.name}") + for (hierarchyEdge in kpi.hierarchyEdges) { + val hierarchyEdgeEntity = KPIHierarchyEdge() + hierarchyEdgeEntity.from = kpiEntity + hierarchyEdgeEntity.to = kpiDtoToEntityMapping[hierarchyEdge.to] + hierarchyEdgeEntity.weight = hierarchyEdge.weight + kpiHierarchyEdgeRepository.save(hierarchyEdgeEntity) + logger.info("Storing edge ${hierarchyEdge.weight}: From ${hierarchyEdge.from.name} to ${hierarchyEdge.to.name}") + } + } + + private fun purgeAllKPIs(repository: Repository) { + val kpis: List<KPI> = kpiRepository.findByRepository_Id(repository.id!!) + kpiRepository.deleteAll(kpis) + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/strategy/AggregationKPICalculationStrategy.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/strategy/AggregationKPICalculationStrategy.kt new file mode 100644 index 0000000000000000000000000000000000000000..292617cfb6e48855643285e1787c7222456f690e --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/strategy/AggregationKPICalculationStrategy.kt @@ -0,0 +1,16 @@ +package de.fraunhofer.iem.dataprovider.kpi.strategy + +import de.fraunhofer.iem.dataprovider.kpi.KPIHierarchyEdgeDto + +class AggregationKPICalculationStrategy: KPICalculationStrategy { + override fun calculateKPI(children: List<KPIHierarchyEdgeDto>): Int { + var aggregate = 0; + for (child in children) { + if (child.to.value != null) { + val childValue = child.to.value!!; + aggregate += (childValue.toFloat() * child.weight).toInt(); + } + } + return aggregate; + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/strategy/KPICalculationStrategy.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/strategy/KPICalculationStrategy.kt new file mode 100644 index 0000000000000000000000000000000000000000..2519be7d76dfedd65973f48c43fc0a8b03f13ed1 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/strategy/KPICalculationStrategy.kt @@ -0,0 +1,8 @@ +package de.fraunhofer.iem.dataprovider.kpi.strategy + +import de.fraunhofer.iem.dataprovider.kpi.KPIHierarchyEdgeDto + +interface KPICalculationStrategy { + fun calculateKPI(children: List<KPIHierarchyEdgeDto>): Int; +} + diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/strategy/RatioKPICalculationStrategy.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/strategy/RatioKPICalculationStrategy.kt new file mode 100644 index 0000000000000000000000000000000000000000..668167891155434e1b5e51542811695f66d4590d --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/strategy/RatioKPICalculationStrategy.kt @@ -0,0 +1,17 @@ +package de.fraunhofer.iem.dataprovider.kpi.strategy + +import de.fraunhofer.iem.dataprovider.kpi.KPIHierarchyEdgeDto + +class RatioKPICalculationStrategy(): KPICalculationStrategy { + override fun calculateKPI(children: List<KPIHierarchyEdgeDto>): Int { + if (children.size != 2) { + throw Exception("Requires exactly two children") + } + val firstValue = children[0].to.value + val secondValue = children[1].to.value + if (firstValue!! >= secondValue!!) { + return ((secondValue / firstValue) * 100).toInt(); + } + return ((firstValue / secondValue) * 100).toInt(); + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/strategy/RawValueKPICalculationStrategy.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/strategy/RawValueKPICalculationStrategy.kt new file mode 100644 index 0000000000000000000000000000000000000000..73d6a9b4011129df8cd591243dc37bd1a786e322 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/kpi/strategy/RawValueKPICalculationStrategy.kt @@ -0,0 +1,9 @@ +package de.fraunhofer.iem.dataprovider.kpi.strategy + +import de.fraunhofer.iem.dataprovider.kpi.KPIHierarchyEdgeDto + +class RawValueKPICalculationStrategy(val value: Int): KPICalculationStrategy { + override fun calculateKPI(children: List<KPIHierarchyEdgeDto>): Int { + return this.value; + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/TaskManager.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/TaskManager.kt index d69796995469a866489b062e000c96853ceee3e7..fedefcae593637865ddbcf05a318c4995cd45335 100644 --- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/TaskManager.kt +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/TaskManager.kt @@ -1,6 +1,7 @@ package de.fraunhofer.iem.dataprovider.taskManager import de.fraunhofer.iem.dataprovider.gitlab.OpenCodeGitlabConfiguration +import de.fraunhofer.iem.dataprovider.kpi.service.KPIService import de.fraunhofer.iem.dataprovider.logger.getLogger import de.fraunhofer.iem.dataprovider.taskManager.model.* import de.fraunhofer.iem.dataprovider.taskManager.tasks.* @@ -27,7 +28,8 @@ import java.util.* class TaskManager( private val config: Config, private val openCodeGitlabConfiguration: OpenCodeGitlabConfiguration, - private val toolRunService: ToolRunService + private val toolRunService: ToolRunService, + private val kpiService: KPIService ) { // The used default dispatcher is ok for CPU-bound workloads. However, @@ -126,35 +128,30 @@ class TaskManager( toolRunService ) - val gitlabTask = GetRepositoryDetailsTask( - event.repoId, - openCodeGitlabConfiguration, - toolRunService, - ::addEvent - ) - taskIds.add(odcTask.taskID) taskIds.add(detektTask.taskID) - taskIds.add(gitlabTask.taskID) dependentEvents[groupId] = taskIds worker.addTask(odcTask) worker.addTask(detektTask) - worker.addTask(gitlabTask) } is SarifProcessGroupDone -> { + logger.info("Done with Sarif Task") val eventGroup = dependentEvents[event.groupId] - if (eventGroup != null) { eventGroup.remove(event.taskId) if (eventGroup.isEmpty()) { - worker.addTask(MetricsTask(event.repoId, toolRunService, ::addEvent)) + logger.info("Adding repository details task") + worker.addTask(GetRepositoryDetailsTask(event.repoId, openCodeGitlabConfiguration, toolRunService, ::addEvent)) } } } + is GetRepositoryDetailsDone -> { + worker.addTask(MetricsTask(event.repoId, toolRunService, kpiService, ::addEvent)) + } else -> { logger.info("Received event without special handling associated $event") diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/model/Event.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/model/Event.kt index 83952b82c059090a2cdb055887ef0339b45d7fc3..c05208f79850642cc03a79bbe60aac0b2d4642ae 100644 --- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/model/Event.kt +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/model/Event.kt @@ -19,4 +19,4 @@ class SarifProcessDone(override val taskId: UUID, val repoId: UUID, val sarif: S class SarifProcessGroupDone(override val taskId: UUID, val repoId: UUID, val groupId: UUID, val sarif: Sarif) : TaskDone() -class GetRepositoryDetailsDone(override val taskId: UUID, val repositoryDetailsEntity: RepositoryDetails) : TaskDone() +class GetRepositoryDetailsDone(override val taskId: UUID, val repoId: UUID, val repositoryDetailsEntity: RepositoryDetails) : TaskDone() diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/GetRepositoryDetailsTask.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/GetRepositoryDetailsTask.kt index d1bb20eebb8044727cba711449f8141d957e51c1..e85c940c84fa49339dff39671f65865e7f539e57 100644 --- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/GetRepositoryDetailsTask.kt +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/GetRepositoryDetailsTask.kt @@ -33,7 +33,7 @@ class GetRepositoryDetailsTask( logger.info("Collected repository details from $repoId (${repositoryDetailsDto}) successfully") val repositoryDetailsEntity = toolRunService.createRepositoryDetails(repositoryDetailsDto); logger.info("Stored repository details for $repoId (${repositoryDetailsEntity}) successfully") - responseChannel(GetRepositoryDetailsDone(taskID, repositoryDetailsEntity)) + responseChannel(GetRepositoryDetailsDone(taskID, repoId, repositoryDetailsEntity)) } else { logger.error("Repository $repoId can not be found in the database") } diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/MetricsTask.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/MetricsTask.kt index 9b583dd61c0524d562daa948e62796aaaaf32c82..42549ccc3f31b9b78af36d3759468ba6c5885d40 100644 --- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/MetricsTask.kt +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/MetricsTask.kt @@ -1,17 +1,52 @@ package de.fraunhofer.iem.dataprovider.taskManager.tasks +import de.fraunhofer.iem.dataprovider.kpi.KPIDto +import de.fraunhofer.iem.dataprovider.kpi.service.KPIService import de.fraunhofer.iem.dataprovider.taskManager.model.Event +import de.fraunhofer.iem.dataprovider.kpi.strategy.AggregationKPICalculationStrategy +import de.fraunhofer.iem.dataprovider.kpi.strategy.RatioKPICalculationStrategy +import de.fraunhofer.iem.dataprovider.kpi.strategy.RawValueKPICalculationStrategy import de.fraunhofer.iem.dataprovider.toolRun.ToolRunService +import de.fraunhofer.iem.dataprovider.toolRun.model.RepositoryDetails import java.util.* class MetricsTask( val repoId: UUID, private val toolRunService: ToolRunService, - + private val kpiService: KPIService, override val responseChannel: suspend (task: Event) -> Unit ) : Task() { override suspend fun execute() { logger.info("Starting metrics task for repoId $repoId") - toolRunService.getLatestToolRunsForRepo(repoId) + val repository = toolRunService.findRepoByID(repoId); + val repositoryDetails = toolRunService.getLatestRepositoryDetailsByRepositoryId(repoId); + if (repositoryDetails != null && repository != null) { + val rootKPI = generateKPITree(repositoryDetails) + kpiService.calculateKPIs(rootKPI) + kpiService.storeAndPurgeOld(repository, rootKPI) + } + } + + private fun generateKPITree(repositoryDetails: RepositoryDetails): KPIDto { + // lowest level leaves + val numberOfCommitsKPI = KPIDto( "Number of commits", "Overall transparency score between 0-100 :-)", false, RawValueKPICalculationStrategy(repositoryDetails.numberOfCommits!!)) + val numberOfSignedCommitsKPI = KPIDto("Number of signed commits", "Overall transparency score between 0-100 :-)", false, RawValueKPICalculationStrategy(repositoryDetails.numberOfSignedCommits!!)) + val isDefaultBranchProtectedKPI = KPIDto( "Is Default Branch Protected", "Is the default branch protected?", false, RawValueKPICalculationStrategy(if (repositoryDetails.isDefaultBranchProtected == true) 100 else 0)) + + val signedCommitsRatioKPI = KPIDto( "Signed Commit Ratio", "Ratio between signed and all commits", false, RatioKPICalculationStrategy()) + signedCommitsRatioKPI.addChildKPI(numberOfCommitsKPI, 1.0) + signedCommitsRatioKPI.addChildKPI(numberOfSignedCommitsKPI, 1.0) + + // second level + val securityKPI = KPIDto( "Security score", "Overall project security score between 0-100 :-)", false, AggregationKPICalculationStrategy()) + val transparencyKPI = KPIDto( "Transparency score", "Overall transparency score between 0-100 :-)", false, AggregationKPICalculationStrategy()) + securityKPI.addChildKPI(isDefaultBranchProtectedKPI, 0.5) + securityKPI.addChildKPI(signedCommitsRatioKPI, 0.5) + transparencyKPI.addChildKPI(signedCommitsRatioKPI, 1.0) + + val rootKPI = KPIDto( "Project score", "Overall project score between 0-100 :-)", true, AggregationKPICalculationStrategy()) + rootKPI.addChildKPI(securityKPI, 0.5) + rootKPI.addChildKPI(transparencyKPI, 0.5) + return rootKPI } } diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolRun/RepositoryDetailsRepository.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolRun/RepositoryDetailsRepository.kt index 3d51a9d055f9fce679c2ee6dfc38d92ae1e23732..a13b4997cbe95c8202fb0487aeaf505544167548 100644 --- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolRun/RepositoryDetailsRepository.kt +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolRun/RepositoryDetailsRepository.kt @@ -1,8 +1,11 @@ package de.fraunhofer.iem.dataprovider.toolRun import de.fraunhofer.iem.dataprovider.toolRun.model.RepositoryDetails +import org.springframework.data.domain.Sort import org.springframework.data.jpa.repository.JpaRepository import java.util.* -interface RepositoryDetailsRepository : JpaRepository<RepositoryDetails, UUID> {} +interface RepositoryDetailsRepository : JpaRepository<RepositoryDetails, UUID> { + fun findFirstByRepository_IdOrderByTimestampDesc(id: UUID): Optional<RepositoryDetails> +} diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolRun/ToolRunService.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolRun/ToolRunService.kt index c62e2cbcb4b31df425d9d98c1bf9058f63b64232..7985c50ad71ca9e012ec137fa77ac4451946fe02 100644 --- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolRun/ToolRunService.kt +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolRun/ToolRunService.kt @@ -137,4 +137,20 @@ class ToolRunService( fun createRepositoryDetails(repositoryDetailsDto: RepositoryDetailsDto): RepositoryDetails { return repositoryDetailsRepository.save(repositoryDetailsDto.toDbObject()) } + + fun getLatestRepositoryDetailsByRepositoryId(repositoryId: UUID): RepositoryDetails? { + val repositoryDetails = repositoryDetailsRepository.findFirstByRepository_IdOrderByTimestampDesc(repositoryId); + if (repositoryDetails.isEmpty) { + return null + } + return repositoryDetails.get(); + } + + fun getRepositoryDetailsById(id: UUID): RepositoryDetails? { + val repositoryDetails = repositoryDetailsRepository.findById(id); + if (repositoryDetails.isEmpty) { + return null + } + return repositoryDetails.get() + } }