Skip to content

Commit

Permalink
feat: Enhance packages endpoint with shortest dependency path data
Browse files Browse the repository at this point in the history
Add information about the shortest dependency path data to the
response of the packages endpoint. This will later be added to
be shown in the UI in order to get a sense of the importance
to address issues found in the packages.

Signed-off-by: Johanna Lamppu <[email protected]>
  • Loading branch information
lamppu committed Jan 30, 2025
1 parent ad0f0ab commit cb89d6a
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 63 deletions.
27 changes: 27 additions & 0 deletions api/v1/mapping/src/commonMain/kotlin/Mappings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import org.eclipse.apoapsis.ortserver.api.v1.model.ScannerJob as ApiScannerJob
import org.eclipse.apoapsis.ortserver.api.v1.model.ScannerJobConfiguration as ApiScannerJobConfiguration
import org.eclipse.apoapsis.ortserver.api.v1.model.Secret as ApiSecret
import org.eclipse.apoapsis.ortserver.api.v1.model.Severity as ApiSeverity
import org.eclipse.apoapsis.ortserver.api.v1.model.ShortestDependencyPath as ApiShortestDependencyPath
import org.eclipse.apoapsis.ortserver.api.v1.model.SortDirection as ApiSortDirection
import org.eclipse.apoapsis.ortserver.api.v1.model.SortProperty as ApiSortProperty
import org.eclipse.apoapsis.ortserver.api.v1.model.SourceCodeOrigin as ApiSourceCodeOrigin
Expand Down Expand Up @@ -133,8 +134,10 @@ import org.eclipse.apoapsis.ortserver.model.runs.Issue
import org.eclipse.apoapsis.ortserver.model.runs.OrtRuleViolation
import org.eclipse.apoapsis.ortserver.model.runs.Package
import org.eclipse.apoapsis.ortserver.model.runs.PackageManagerConfiguration
import org.eclipse.apoapsis.ortserver.model.runs.PackageWithShortestDependencyPath
import org.eclipse.apoapsis.ortserver.model.runs.ProcessedDeclaredLicense
import org.eclipse.apoapsis.ortserver.model.runs.RemoteArtifact
import org.eclipse.apoapsis.ortserver.model.runs.ShortestDependencyPath
import org.eclipse.apoapsis.ortserver.model.runs.VcsInfo
import org.eclipse.apoapsis.ortserver.model.runs.advisor.Vulnerability
import org.eclipse.apoapsis.ortserver.model.runs.advisor.VulnerabilityReference
Expand Down Expand Up @@ -791,3 +794,27 @@ fun ApiSubmoduleFetchStrategy.mapToModel() = when (this) {
ApiSubmoduleFetchStrategy.TOP_LEVEL_ONLY -> SubmoduleFetchStrategy.TOP_LEVEL_ONLY
ApiSubmoduleFetchStrategy.FULLY_RECURSIVE -> SubmoduleFetchStrategy.FULLY_RECURSIVE
}

fun ShortestDependencyPath.mapToApi() = ApiShortestDependencyPath(
scope = scope,
projectIdentifier = project.identifier.mapToApi(),
path = path.map { it.mapToApi() }
)

fun PackageWithShortestDependencyPath.mapToApi() = ApiPackage(
pkg.identifier.mapToApi(),
pkg.purl,
pkg.cpe,
pkg.authors,
pkg.declaredLicenses,
pkg.processedDeclaredLicense.mapToApi(),
pkg.description,
pkg.homepageUrl,
pkg.binaryArtifact.mapToApi(),
pkg.sourceArtifact.mapToApi(),
pkg.vcs.mapToApi(),
pkg.vcsProcessed.mapToApi(),
pkg.isMetadataOnly,
pkg.isModified,
shortestDependencyPath?.mapToApi()
)
3 changes: 2 additions & 1 deletion api/v1/model/src/commonMain/kotlin/Package.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ data class Package(
val vcs: VcsInfo,
val vcsProcessed: VcsInfo,
val isMetadataOnly: Boolean = false,
val isModified: Boolean = false
val isModified: Boolean = false,
val shortestDependencyPath: ShortestDependencyPath? = null
)
35 changes: 35 additions & 0 deletions api/v1/model/src/commonMain/kotlin/ShortestDependencyPath.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2025 The ORT Server Authors (See <https://github.com/eclipse-apoapsis/ort-server/blob/main/NOTICE>)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/

package org.eclipse.apoapsis.ortserver.api.v1.model

import kotlinx.serialization.Serializable

/** The shortest dependency path for a Package. */
@Serializable
data class ShortestDependencyPath(
/** The identifier of the root project of this path. */
val projectIdentifier: Identifier,

/** The scope in which this shortest path of the dependency is found in. */
val scope: String,

/** Path of dependency identifiers to the dependency. */
val path: List<Identifier>
)
8 changes: 4 additions & 4 deletions core/src/main/kotlin/api/RunsRoute.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import io.ktor.http.ContentDisposition
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
import io.ktor.server.response.header
import io.ktor.server.response.respond
import io.ktor.server.response.respondFile
Expand Down Expand Up @@ -73,7 +72,7 @@ import org.eclipse.apoapsis.ortserver.model.authorization.RepositoryPermission
import org.eclipse.apoapsis.ortserver.model.repositories.OrtRunRepository
import org.eclipse.apoapsis.ortserver.model.runs.Issue
import org.eclipse.apoapsis.ortserver.model.runs.OrtRuleViolation
import org.eclipse.apoapsis.ortserver.model.runs.Package
import org.eclipse.apoapsis.ortserver.model.runs.PackageWithShortestDependencyPath
import org.eclipse.apoapsis.ortserver.services.IssueService
import org.eclipse.apoapsis.ortserver.services.OrtRunService
import org.eclipse.apoapsis.ortserver.services.PackageService
Expand Down Expand Up @@ -233,9 +232,10 @@ fun Route.runs() = route("runs") {

val pagingOptions = call.pagingOptions(SortProperty("purl", SortDirection.ASCENDING))

val packagesForOrtRun = packageService.listForOrtRunId(ortRun.id, pagingOptions.mapToModel())
val packagesForOrtRun = packageService
.listForOrtRunId(ortRun.id, pagingOptions.mapToModel())

val pagedResponse = packagesForOrtRun.mapToApi(Package::mapToApi)
val pagedResponse = packagesForOrtRun.mapToApi(PackageWithShortestDependencyPath::mapToApi)

call.respond(HttpStatusCode.OK, pagedResponse)
}
Expand Down
9 changes: 9 additions & 0 deletions core/src/main/kotlin/apiDocs/RunsDocs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import org.eclipse.apoapsis.ortserver.api.v1.model.RemoteArtifact
import org.eclipse.apoapsis.ortserver.api.v1.model.RepositoryType
import org.eclipse.apoapsis.ortserver.api.v1.model.RuleViolation
import org.eclipse.apoapsis.ortserver.api.v1.model.Severity
import org.eclipse.apoapsis.ortserver.api.v1.model.ShortestDependencyPath
import org.eclipse.apoapsis.ortserver.api.v1.model.SortDirection
import org.eclipse.apoapsis.ortserver.api.v1.model.SortProperty
import org.eclipse.apoapsis.ortserver.api.v1.model.VcsInfo
Expand Down Expand Up @@ -396,6 +397,14 @@ val getPackagesByRunId: OpenApiRoute.() -> Unit = {
vcsProcessed = VcsInfo(RepositoryType.GIT.name, "url", "revision", "path"),
isMetadataOnly = false,
isModified = false,
shortestDependencyPath = ShortestDependencyPath(
scope = "productionRuntimeClasspath",
projectIdentifier = Identifier("Gradle", "", "project-name", "1.0"),
path = listOf(
Identifier("Maven", "org.namespace", "some", "1.0"),
Identifier("Maven", "org.namespace", "other", "1.0")
)
)
)
),
PagingData(
Expand Down
55 changes: 41 additions & 14 deletions core/src/test/kotlin/api/RunsRouteIntegrationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ import org.eclipse.apoapsis.ortserver.model.runs.OrtRuleViolation
import org.eclipse.apoapsis.ortserver.model.runs.Package
import org.eclipse.apoapsis.ortserver.model.runs.ProcessedDeclaredLicense
import org.eclipse.apoapsis.ortserver.model.runs.RemoteArtifact
import org.eclipse.apoapsis.ortserver.model.runs.ShortestDependencyPath
import org.eclipse.apoapsis.ortserver.model.runs.VcsInfo
import org.eclipse.apoapsis.ortserver.model.runs.advisor.AdvisorConfiguration
import org.eclipse.apoapsis.ortserver.model.runs.advisor.AdvisorResult
Expand Down Expand Up @@ -890,6 +891,10 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({
configuration = AnalyzerJobConfiguration()
)

val project = dbExtension.fixtures.getProject()
val identifier1 = Identifier("Maven", "com.example", "example", "1.0")
val identifier2 = Identifier("Maven", "com.example", "example2", "1.0")

dbExtension.fixtures.analyzerRunRepository.create(
analyzerJobId = analyzerJob.id,
startTime = Clock.System.now().toDatabasePrecision(),
Expand All @@ -910,10 +915,10 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({
packageManagers = emptyMap(),
skipExcluded = true
),
projects = emptySet(),
projects = setOf(project),
packages = setOf(
Package(
Identifier("Maven", "com.example", "example", "1.0"),
identifier1,
purl = "pkg:maven/com.example/[email protected]",
cpe = null,
authors = setOf("Author One", "Author Two"),
Expand Down Expand Up @@ -954,12 +959,7 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({
isModified = false
),
Package(
Identifier(
type = "Maven",
namespace = "com.example",
name = "example2",
version = "1.0"
),
identifier2,
purl = "pkg:maven/com.example/[email protected]",
cpe = null,
authors = emptySet(),
Expand Down Expand Up @@ -998,7 +998,19 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({
)
),
issues = emptyList(),
dependencyGraphs = emptyMap()
dependencyGraphs = emptyMap(),
shortestDependencyPaths = mapOf(
identifier1 to ShortestDependencyPath(
project,
"compileClassPath",
emptyList()
),
identifier2 to ShortestDependencyPath(
project,
"compileClassPath",
listOf(identifier1)
)
)
)

val response = superuserClient.get("/api/v1/runs/${ortRun.id}/packages")
Expand All @@ -1008,11 +1020,26 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({

with(packages.data) {
shouldHaveSize(2)
first().identifier.name shouldBe "example"
first().authors shouldHaveSize 2
first().declaredLicenses shouldHaveSize 3
first().processedDeclaredLicense.mappedLicenses shouldHaveSize 2
first().processedDeclaredLicense.unmappedLicenses shouldHaveSize 4

with(first()) {
identifier.name shouldBe "example"
authors shouldHaveSize 2
declaredLicenses shouldHaveSize 3
processedDeclaredLicense.mappedLicenses shouldHaveSize 2
processedDeclaredLicense.unmappedLicenses shouldHaveSize 4

shortestDependencyPath shouldNotBeNull {
projectIdentifier shouldBe project.identifier.mapToApi()
scope shouldBe "compileClassPath"
path shouldBe emptyList()
}
}

last().shortestDependencyPath shouldNotBeNull {
projectIdentifier shouldBe project.identifier.mapToApi()
scope shouldBe "compileClassPath"
path shouldBe listOf(identifier1.mapToApi())
}
last().identifier.name shouldBe "example2"
}
}
Expand Down
26 changes: 24 additions & 2 deletions dao/src/testFixtures/kotlin/Fixtures.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ import org.eclipse.apoapsis.ortserver.model.runs.Identifier
import org.eclipse.apoapsis.ortserver.model.runs.Issue
import org.eclipse.apoapsis.ortserver.model.runs.OrtRuleViolation
import org.eclipse.apoapsis.ortserver.model.runs.Package
import org.eclipse.apoapsis.ortserver.model.runs.ProcessedDeclaredLicense
import org.eclipse.apoapsis.ortserver.model.runs.Project
import org.eclipse.apoapsis.ortserver.model.runs.ShortestDependencyPath
import org.eclipse.apoapsis.ortserver.model.runs.VcsInfo
import org.eclipse.apoapsis.ortserver.model.runs.advisor.AdvisorConfiguration
import org.eclipse.apoapsis.ortserver.model.runs.advisor.AdvisorResult

Expand Down Expand Up @@ -221,12 +224,30 @@ class Fixtures(private val db: Database) {
howToFix = "how to fix"
)

fun getProject() = Project(
identifier = Identifier("Gradle", "", "project", "1.0"),
definitionFilePath = "build.gradle.kts",
authors = emptySet(),
declaredLicenses = emptySet(),
processedDeclaredLicense = ProcessedDeclaredLicense(
null,
emptyMap(),
emptySet()
),
vcs = VcsInfo(RepositoryType.GIT, "https://example.com/git", "revision", ""),
vcsProcessed = VcsInfo(RepositoryType.GIT, "https://example.com/git", "revision", ""),
description = "",
homepageUrl = "https://example.com",
scopeNames = setOf("compileClasspath", "runtimeClasspath")
)

fun createAnalyzerRun(
analyzerJobId: Long = analyzerJob.id,
projects: Set<Project> = emptySet(),
packages: Set<Package> = emptySet(),
issues: List<Issue> = emptyList(),
dependencyGraphs: Map<String, DependencyGraph> = emptyMap()
dependencyGraphs: Map<String, DependencyGraph> = emptyMap(),
shortestDependencyPaths: Map<Identifier, ShortestDependencyPath> = emptyMap()
) = analyzerRunRepository.create(
analyzerJobId = analyzerJobId,
startTime = Clock.System.now(),
Expand All @@ -250,7 +271,8 @@ class Fixtures(private val db: Database) {
projects = projects,
packages = packages,
issues = issues,
dependencyGraphs = dependencyGraphs
dependencyGraphs = dependencyGraphs,
shortestDependencyPaths = shortestDependencyPaths
)

fun createAdvisorRun(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2025 The ORT Server Authors (See <https://github.com/eclipse-apoapsis/ort-server/blob/main/NOTICE>)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/

package org.eclipse.apoapsis.ortserver.model.runs

/**
* A data class representing a package, and the shortest dependency path that the package is found in (relative to a
* project found in a run).
*/
data class PackageWithShortestDependencyPath(
/** A package. */
val pkg: Package,

/** The shortest dependency path for the package. */
val shortestDependencyPath: ShortestDependencyPath? = null
)
21 changes: 16 additions & 5 deletions services/hierarchy/src/main/kotlin/PackageService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ import org.eclipse.apoapsis.ortserver.dao.repositories.analyzerrun.AnalyzerRunsT
import org.eclipse.apoapsis.ortserver.dao.repositories.analyzerrun.PackageDao
import org.eclipse.apoapsis.ortserver.dao.repositories.analyzerrun.PackagesAnalyzerRunsTable
import org.eclipse.apoapsis.ortserver.dao.repositories.analyzerrun.PackagesTable
import org.eclipse.apoapsis.ortserver.dao.repositories.analyzerrun.ShortestDependencyPathDao
import org.eclipse.apoapsis.ortserver.dao.repositories.analyzerrun.ShortestDependencyPathsTable
import org.eclipse.apoapsis.ortserver.dao.tables.shared.IdentifiersTable
import org.eclipse.apoapsis.ortserver.dao.utils.listCustomQuery
import org.eclipse.apoapsis.ortserver.model.EcosystemStats
import org.eclipse.apoapsis.ortserver.model.runs.Package
import org.eclipse.apoapsis.ortserver.model.runs.PackageWithShortestDependencyPath
import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters
import org.eclipse.apoapsis.ortserver.model.util.ListQueryResult

Expand All @@ -43,10 +45,11 @@ class PackageService(private val db: Database) {
suspend fun listForOrtRunId(
ortRunId: Long,
parameters: ListQueryParameters = ListQueryParameters.DEFAULT
): ListQueryResult<Package> = db.dbQuery {
PackageDao.listCustomQuery(parameters, ResultRow::toPackage) {
): ListQueryResult<PackageWithShortestDependencyPath> = db.dbQuery {
PackageDao.listCustomQuery(parameters, ResultRow::toPackageWithShortestDependencyPath) {
PackagesTable.joinAnalyzerTables()
.select(PackagesTable.columns)
.leftJoin(ShortestDependencyPathsTable)
.select(PackagesTable.columns + ShortestDependencyPathsTable.columns)
.where { AnalyzerJobsTable.ortRunId eq ortRunId }
}
}
Expand Down Expand Up @@ -78,7 +81,15 @@ class PackageService(private val db: Database) {
}
}

private fun ResultRow.toPackage(): Package = PackageDao.wrapRow(this).mapToModel()
private fun ResultRow.toPackageWithShortestDependencyPath(): PackageWithShortestDependencyPath =
PackageWithShortestDependencyPath(
pkg = PackageDao.wrapRow(this).mapToModel(),
shortestDependencyPath = if (getOrNull(ShortestDependencyPathsTable.id) != null) {
ShortestDependencyPathDao.wrapRow(this).mapToModel()
} else {
null
}
)

private fun PackagesTable.joinAnalyzerTables() =
innerJoin(PackagesAnalyzerRunsTable)
Expand Down
Loading

0 comments on commit cb89d6a

Please sign in to comment.