Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions admin-app/src/main/resources/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,11 @@ paths:
# Data Ingest Endpoints
/api/data-ingest/builds:
put:
summary: Persist application build metadata
summary: Persist application build identity
description: |
Saves application build information, including build version and associated Git commit metadata.
This endpoint captures critical build data required for change tracking, impact analysis, risk assessment, and
correlation with test coverage metrics. The service ingests build metadata from instrumented applications to enable
detailed analysis of code changes, test recommendations, and coverage reports across different application versions.
Saves application build identity information, including groupId, appId, commitSha, and buildVersion.
This endpoint only persists the build identity fields. To save additional Git metadata (branch, commitDate,
commitMessage, commitAuthor), use the PUT /api/data-ingest/builds/info endpoint.
operationId: putBuild
tags:
- data-ingest
Expand All @@ -98,6 +97,35 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
/api/data-ingest/builds/info:
put:
summary: Persist application build Git metadata
description: |
Saves Git metadata (branch, commitDate, commitMessage, commitAuthor) for an application build.
The build is identified by groupId, appId, commitSha, and buildVersion. If a build with the given
identity does not exist, a new build is created. If it already exists, only the Git metadata fields
Comment thread
iryabov marked this conversation as resolved.
are updated while preserving the existing identity and instance data.
operationId: putBuildInfo
tags:
- data-ingest
security:
- apiKeyAuth: [ ]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BuildPayload'
application/protobuf:
schema:
$ref: '#/components/schemas/BuildPayload'
responses:
'200':
description: Build info saved
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
/api/data-ingest/instances:
put:
summary: Persist application instance metadata
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ val TestDetails.definitionId: String
}

suspend fun HttpClient.putBuild(payload: BuildPayload): HttpResponse {
return put("/data-ingest/builds") {
return put("/data-ingest/builds/info") {
setBody(payload)
}.assertSuccessStatus()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ class Build(
val appId: String,
val commitSha: String?,
val buildVersion: String?,
val branch: String?,
val instanceId: String?,
val commitDate: LocalDateTime?,
val commitMessage: String?,
val commitAuthor: String?
val branch: String? = null,
val commitDate: LocalDateTime? = null,
val commitMessage: String? = null,
val commitAuthor: String? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import com.epam.drill.admin.writer.rawdata.entity.Build
import java.time.LocalDate

interface BuildRepository {
suspend fun create(build: Build)
suspend fun saveBuildInfo(build: Build)
suspend fun saveBuildId(build: Build)
suspend fun existsById(groupId: String, appId: String, buildId: String): Boolean
suspend fun deleteAllCreatedBefore(groupId: String, createdBefore: LocalDate)
suspend fun deleteByBuildId(groupId: String, appId: String, buildId: String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ import org.jetbrains.exposed.sql.upsert
import java.time.LocalDate

class BuildRepositoryImpl: BuildRepository {
override suspend fun create(build: Build) {
override suspend fun saveBuildInfo(build: Build) {
BuildTable.upsert(
onUpdateExclude = listOf(BuildTable.createdAt),
onUpdateExclude = listOf(
BuildTable.createdAt,
),
) {
it[id] = build.id
it[groupId] = build.groupId
Expand All @@ -44,6 +46,27 @@ class BuildRepositoryImpl: BuildRepository {
it[updatedAt] = org.jetbrains.exposed.sql.javatime.CurrentDateTime
}
}

override suspend fun saveBuildId(build: Build) {
BuildTable.upsert(
onUpdateExclude = listOf(
BuildTable.createdAt,
BuildTable.branch,
BuildTable.committedAt,
BuildTable.commitAuthor,
BuildTable.commitMessage
),
) {
it[id] = build.id
it[groupId] = build.groupId
it[appId] = build.appId
it[commitSha] = build.commitSha
it[buildVersion] = build.buildVersion
it[instanceId] = build.instanceId
it[updatedAt] = org.jetbrains.exposed.sql.javatime.CurrentDateTime
}
}

override suspend fun existsById(groupId: String, appId: String, buildId: String): Boolean {
return BuildTable.selectAll().where {
(BuildTable.groupId eq groupId) and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ private val logger = KotlinLogging.logger {}
@Resource("builds")
class BuildsRoute()

@Resource("builds/info")
class BuildsInfoRoute()

@Resource("instances")
class InstancesRoute()

Expand Down Expand Up @@ -81,6 +84,7 @@ class MethodIgnoreRulesRoute() {
fun Route.dataIngestRoutes() {
route("/data-ingest") {
putBuilds()
putBuildsInfo()
putInstances()
postCoverage()
putMethods()
Expand All @@ -104,6 +108,15 @@ fun Route.putBuilds() {
}
}

fun Route.putBuildsInfo() {
val rawDataWriter by closestDI().instance<RawDataWriter>()

put<BuildsInfoRoute> {
rawDataWriter.saveBuildInfo(call.decompressAndReceive())
call.ok("Build info saved")
}
}

fun Route.putInstances() {
val rawDataWriter by closestDI().instance<RawDataWriter>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.epam.drill.admin.writer.rawdata.views.MethodIgnoreRuleView

interface RawDataWriter {
suspend fun saveBuild(buildPayload: BuildPayload)
suspend fun saveBuildInfo(buildPayload: BuildPayload)
suspend fun saveInstance(instancePayload: InstancePayload)
suspend fun saveMethods(methodsPayload: MethodsPayload)
suspend fun saveCoverage(coveragePayload: CoveragePayload)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,26 @@ class RawDataServiceImpl(
) : RawDataWriter {

override suspend fun saveBuild(buildPayload: BuildPayload) {
val build = Build(
id = generateBuildId(
buildPayload.groupId,
buildPayload.appId,
"",
buildPayload.commitSha,
buildPayload.buildVersion
),
groupId = buildPayload.groupId,
appId = buildPayload.appId,
instanceId = null,
commitSha = buildPayload.commitSha,
buildVersion = buildPayload.buildVersion,
)
transaction {
buildRepository.saveBuildId(build)
}
}

override suspend fun saveBuildInfo(buildPayload: BuildPayload) {
val build = Build(
id = generateBuildId(
buildPayload.groupId,
Expand All @@ -66,7 +86,7 @@ class RawDataServiceImpl(
commitAuthor = buildPayload.commitAuthor
)
transaction {
buildRepository.create(build)
buildRepository.saveBuildInfo(build)
}
}

Expand Down Expand Up @@ -94,12 +114,8 @@ class RawDataServiceImpl(
instanceId = instancePayload.instanceId,
commitSha = instancePayload.commitSha,
buildVersion = instancePayload.buildVersion,
branch = null,
commitDate = null,
commitMessage = null,
commitAuthor = null
)
buildRepository.create(build)
buildRepository.saveBuildId(build)
}
instanceRepository.create(instance)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.epam.drill.admin.writer.rawdata

import com.epam.drill.admin.writer.rawdata.route.putBuilds
import com.epam.drill.admin.writer.rawdata.route.putBuildsInfo
import com.epam.drill.admin.writer.rawdata.table.BuildTable
import com.epam.drill.admin.test.*
import com.epam.drill.admin.writer.rawdata.config.RawDataWriterDatabaseConfig
Expand All @@ -28,6 +29,7 @@ import java.time.LocalDateTime
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue

class BuildsApiTest : DatabaseTests({ RawDataWriterDatabaseConfig.init(it) }) {
Expand All @@ -50,8 +52,56 @@ class BuildsApiTest : DatabaseTests({ RawDataWriterDatabaseConfig.init(it) }) {
"groupId": "$testGroup",
"appId": "$testApp",
"buildVersion": "$testBuildVersion",
"commitSha": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
}
""".trimIndent()
)
}.apply {
assertEquals(HttpStatusCode.OK, status)
assertJsonEquals(
"""
{
"message": "Build saved"
}
""".trimIndent(), bodyAsText()
)
}

val savedBuilds = BuildTable.selectAll()
.filter { it[BuildTable.groupId] == testGroup }
.filter { it[BuildTable.appId] == testApp }
.filter { it[BuildTable.buildVersion] == testBuildVersion }
assertEquals(1, savedBuilds.size)
savedBuilds.forEach {
assertNull(it[BuildTable.branch])
assertNotNull(it[BuildTable.commitSha])
assertNull(it[BuildTable.commitAuthor])
assertNull(it[BuildTable.commitMessage])
assertNull(it[BuildTable.committedAt])
assertTrue(it[BuildTable.createdAt] >= timeBeforeTest)
}
}

@Test
fun `given new build, put builds info should create new build with info fields and return OK`() = withRollback {
val testGroup = "test-group"
val testApp = "test-app"
val testBuildVersion = "2.0.0"
val timeBeforeTest = LocalDateTime.now()
val app = drillApplication(rawDataServicesDIModule) {
putBuildsInfo()
}

app.client.put("/builds/info") {
header(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setBody(
"""
{
"groupId": "$testGroup",
"appId": "$testApp",
"buildVersion": "$testBuildVersion",
"commitSha": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
"branch": "main",
"commitSha": "d3516472fd72cd0f9ccb7a1dc4b5e7b80a014fd2",
"commitMessage": "Initial commit",
"commitDate": "Thu Feb 27 10:06:24 2025 +0100",
"commitAuthor": "John Doe"
Expand All @@ -63,7 +113,7 @@ class BuildsApiTest : DatabaseTests({ RawDataWriterDatabaseConfig.init(it) }) {
assertJsonEquals(
"""
{
"message": "Build saved"
"message": "Build info saved"
}
""".trimIndent(), bodyAsText()
)
Expand All @@ -75,12 +125,69 @@ class BuildsApiTest : DatabaseTests({ RawDataWriterDatabaseConfig.init(it) }) {
.filter { it[BuildTable.buildVersion] == testBuildVersion }
assertEquals(1, savedBuilds.size)
savedBuilds.forEach {
assertNotNull(it[BuildTable.branch])
assertEquals("main", it[BuildTable.branch])
assertNotNull(it[BuildTable.commitSha])
assertEquals("John Doe", it[BuildTable.commitAuthor])
assertEquals("Initial commit", it[BuildTable.commitMessage])
assertNotNull(it[BuildTable.committedAt])
assertTrue(it[BuildTable.createdAt] >= timeBeforeTest)
}
}

@Test
fun `given existing build info, put builds should not update info fields and return OK`() = withRollback {
val testGroup = "test-group"
val testApp = "test-app"
val testBuildVersion = "3.0.0"
val testCommitSha = "b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3"
val app = drillApplication(rawDataServicesDIModule) {
putBuildsInfo()
putBuilds()
}
app.client.put("/builds/info") {
header(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setBody(
"""
{
"groupId": "$testGroup",
"appId": "$testApp",
"buildVersion": "$testBuildVersion",
"commitSha": "$testCommitSha",
"branch": "develop",
"commitMessage": "Feature commit",
"commitDate": "Thu Feb 27 10:06:24 2025 +0100",
"commitAuthor": "Jane Doe"
}
""".trimIndent()
)
}

app.client.put("/builds") {
header(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setBody(
"""
{
"groupId": "$testGroup",
"appId": "$testApp",
"buildVersion": "$testBuildVersion",
"commitSha": "$testCommitSha"
}
""".trimIndent()
)
}.apply {
assertEquals(HttpStatusCode.OK, status)
}

val buildsBeforeInfo = BuildTable.selectAll()
.filter { it[BuildTable.groupId] == testGroup }
.filter { it[BuildTable.appId] == testApp }
.filter { it[BuildTable.buildVersion] == testBuildVersion }
assertEquals(1, buildsBeforeInfo.size)
buildsBeforeInfo.forEach {
assertNotNull(it[BuildTable.branch])
assertNotNull(it[BuildTable.commitAuthor])
assertNotNull(it[BuildTable.commitMessage])
assertNotNull(it[BuildTable.committedAt])
assertTrue(it[BuildTable.createdAt] >= timeBeforeTest)
}
}

Expand Down
Loading