Skip to content

Commit

Permalink
#1340 Rendering a change log for build using GraphQL
Browse files Browse the repository at this point in the history
  • Loading branch information
dcoraboeuf committed Aug 28, 2024
1 parent 5ad9ef2 commit 3c50b7b
Show file tree
Hide file tree
Showing 12 changed files with 284 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Configuration:

*** **data** - JSON - required - Raw data associated with the node, to be used by the node executor.

*** **description** - String - optional - Description of the node in its workflow.

*** **executorId** - String - required - ID of the executor to use

*** **id** - String - required - Unique ID of the node in its workflow.
Expand Down
6 changes: 4 additions & 2 deletions ontrack-docs/src/docs/asciidoc/templating/sources/index.adoc
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
[[appendix-templating-sources-index]]
==== List of templating sources

* <<templating-source-changelog,changelog>>
* <<templating-source-build-changelog,Build.changelog>>
* <<templating-source-description,description>>
* <<templating-source-linked,linked>>
* <<templating-source-meta,meta>>
* <<templating-source-promotion-run-changelog,PromotionRun.changelog>>
* <<templating-source-qualifiedLongName,qualifiedLongName>>
* <<templating-source-release,release>>
* <<templating-source-scmBranch,scmBranch>>
* <<templating-source-version,version>>

include::templating-source-changelog.adoc[]
include::templating-source-build-changelog.adoc[]
include::templating-source-description.adoc[]
include::templating-source-linked.adoc[]
include::templating-source-meta.adoc[]
include::templating-source-promotion-run-changelog.adoc[]
include::templating-source-qualifiedLongName.adoc[]
include::templating-source-release.adoc[]
include::templating-source-scmBranch.adoc[]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[[templating-source-build-changelog]]
===== Build.changelog

Renders a change log for this build.

The "to build" is the one being referred to.

The "from build" is the build whose ID is set by the "from" parameter.

If `project` is set to a comma-separated list of strings, the change log will be rendered
for the recursive links, in the order to the projects being set (going deeper and deeper
in the links).

Applicable for:

* build

Configuration:

* **allQualifiers** - Boolean - required - Loop over all qualifiers for the last level of `dependencies`, including the default one. Qualifiers at `dependencies` take precedence.

* **commitsOption** - NONE, OPTIONAL, ALWAYS - required - Defines how to render commits for a change log

* **defaultQualifierFallback** - Boolean - required - If a qualifier has no previous link, uses the default qualifier (empty) qualifier.

* **dependencies** - List - required - Comma-separated list of project links to follow one by one for a get deep change log. Each item in the list is either a project name, or a project name and qualifier separated by a colon (:).

* **empty** - String - required - String to use to render an empty or non existent change log

* **from** - Int - required - ID to the build to get the change log from

* **title** - Boolean - required - Include a title for the change log

Example:

[source]
----
${build.changelog?from=1}
----
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[[templating-source-changelog]]
===== changelog
[[templating-source-promotion-run-changelog]]
===== PromotionRun.changelog

Renders a change log for this promotion run.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package net.nemerosa.ontrack.extension.scm.service

import net.nemerosa.ontrack.extension.scm.changelog.ChangeLogTemplatingService
import net.nemerosa.ontrack.extension.scm.changelog.ChangeLogTemplatingServiceConfig
import net.nemerosa.ontrack.model.annotations.APIDescription
import net.nemerosa.ontrack.model.docs.Documentation
import net.nemerosa.ontrack.model.docs.DocumentationExampleCode
import net.nemerosa.ontrack.model.docs.DocumentationQualifier
import net.nemerosa.ontrack.model.events.EventRenderer
import net.nemerosa.ontrack.model.structure.*
import net.nemerosa.ontrack.model.templating.AbstractTemplatingSource
import net.nemerosa.ontrack.model.templating.getRequiredTemplatingParam
import org.springframework.stereotype.Component

@Component
@APIDescription(
"""
Renders a change log for this build.
The "to build" is the one being referred to.
The "from build" is the build whose ID is set by the "from" parameter.
If `project` is set to a comma-separated list of strings, the change log will be rendered
for the recursive links, in the order to the projects being set (going deeper and deeper
in the links).
"""
)
@Documentation(BuildChangeLogTemplatingSourceConfig::class)
@DocumentationQualifier("build", "Build")
@DocumentationExampleCode("${'$'}{build.changelog?from=1}")
class BuildChangeLogTemplatingSource(
private val changeLogTemplatingService: ChangeLogTemplatingService,
private val structureService: StructureService,
) : AbstractTemplatingSource(
field = "changelog",
type = ProjectEntityType.BUILD,
) {

override fun render(entity: ProjectEntity, configMap: Map<String, String>, renderer: EventRenderer): String {
val empty = ChangeLogTemplatingServiceConfig.emptyValue(configMap)
return if (entity is Build) {
val fromId = configMap.getRequiredTemplatingParam(BuildChangeLogTemplatingSourceConfig::from.name).toInt()
val from = structureService.getBuild(ID.of(fromId))
changeLogTemplatingService.render(
fromBuild = entity,
toBuild = from,
configMap = configMap,
renderer = renderer,
)
} else {
empty
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package net.nemerosa.ontrack.extension.scm.service

import net.nemerosa.ontrack.extension.scm.changelog.ChangeLogTemplatingCommitsOption
import net.nemerosa.ontrack.extension.scm.changelog.ChangeLogTemplatingServiceConfig
import net.nemerosa.ontrack.model.annotations.APIDescription

class BuildChangeLogTemplatingSourceConfig(
empty: String = "",
dependencies: List<String> = emptyList(),
title: Boolean = false,
allQualifiers: Boolean = false,
defaultQualifierFallback: Boolean = false,
commitsOption: ChangeLogTemplatingCommitsOption = ChangeLogTemplatingCommitsOption.NONE,
@APIDescription("ID to the build to get the change log from")
val from: Int,
) : ChangeLogTemplatingServiceConfig(
empty,
dependencies,
title,
allQualifiers,
defaultQualifierFallback,
commitsOption,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import net.nemerosa.ontrack.extension.scm.changelog.PromotionChangeLogTemplating
import net.nemerosa.ontrack.model.annotations.APIDescription
import net.nemerosa.ontrack.model.docs.Documentation
import net.nemerosa.ontrack.model.docs.DocumentationExampleCode
import net.nemerosa.ontrack.model.docs.DocumentationQualifier
import net.nemerosa.ontrack.model.events.EventRenderer
import net.nemerosa.ontrack.model.structure.ProjectEntity
import net.nemerosa.ontrack.model.structure.ProjectEntityType
Expand All @@ -31,6 +32,7 @@ import org.springframework.stereotype.Component
"""
)
@Documentation(PromotionRunChangeLogTemplatingSourceConfig::class)
@DocumentationQualifier("promotion-run", "PromotionRun")
@DocumentationExampleCode("${'$'}{promotionRun.changelog}")
class PromotionRunChangeLogTemplatingSource(
private val promotionChangeLogTemplatingService: PromotionChangeLogTemplatingService,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package net.nemerosa.ontrack.extension.scm.changelog

import net.nemerosa.ontrack.extension.scm.mock.MockSCMTester
import net.nemerosa.ontrack.it.AbstractDSLTestSupport
import net.nemerosa.ontrack.graphql.AbstractQLKTITSupport
import net.nemerosa.ontrack.model.structure.Build
import org.springframework.beans.factory.annotation.Autowired

abstract class AbstractSCMChangeLogTestSupport : AbstractDSLTestSupport() {
abstract class AbstractSCMChangeLogTestSupport : AbstractQLKTITSupport() {

@Autowired
private lateinit var mockSCMTester: MockSCMTester
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package net.nemerosa.ontrack.extension.scm.changelog

import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class BuildRenderChangeLogIT: AbstractSCMChangeLogTestSupport() {

/**
* Rendering a change log using the GraphQL Build.render field.
*/
@Test
fun `Build render change log`() {
prepareChangeLogTestCase { _, from, to ->
run(
"""
query BuildChangeLog(
${'$'}buildToId: Int!,
${'$'}template: String!,
) {
build(id: ${'$'}buildToId) {
render(
format: "plain",
template: ${'$'}template
)
}
}
""",
mapOf(
"buildToId" to to.id(),
"template" to """
${'$'}{build.changelog?from=${from.id}}
""".trimIndent()
)
) { data ->
val render = data.path("build").path("render").asText()
assertEquals(
"""
* ISS-21 Some new feature
* ISS-22 Some fixes are needed
* ISS-23 Some nicer UI
""".trimIndent(),
render
)
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package net.nemerosa.ontrack.model.docs

/**
* Used by a class (or function, or property) to qualify a documented
* element so that it can distinguished from a similar entry.
*/
@Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Repeatable
annotation class DocumentationQualifier(
val value: String,
val name: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package net.nemerosa.ontrack.graphql.templating

import graphql.Scalars.GraphQLString
import graphql.schema.GraphQLFieldDefinition
import net.nemerosa.ontrack.graphql.schema.GQLProjectEntityFieldContributor
import net.nemerosa.ontrack.graphql.support.stringArgument
import net.nemerosa.ontrack.graphql.support.toNotNull
import net.nemerosa.ontrack.model.events.EventRendererRegistry
import net.nemerosa.ontrack.model.events.PlainEventRenderer
import net.nemerosa.ontrack.model.structure.Build
import net.nemerosa.ontrack.model.structure.ProjectEntity
import net.nemerosa.ontrack.model.structure.ProjectEntityType
import net.nemerosa.ontrack.model.templating.TemplatingService
import org.springframework.stereotype.Component

@Component
class RenderBuildGraphQLFieldContributor(
private val templatingService: TemplatingService,
private val eventRendererRegistry: EventRendererRegistry
) : GQLProjectEntityFieldContributor {

override fun getFields(
projectEntityClass: Class<out ProjectEntity>,
projectEntityType: ProjectEntityType
): List<GraphQLFieldDefinition>? =
if (projectEntityType == ProjectEntityType.BUILD) {
listOf(
GraphQLFieldDefinition.newFieldDefinition()
.name("render")
.description("Renders a template for this build")
.type(GraphQLString.toNotNull())
.argument(stringArgument(ARG_FORMAT, "Format of the rendering"))
.argument(stringArgument(ARG_TEMPLATE, "Template for the rendering"))
.dataFetcher { env ->
val build: Build = env.getSource()
val format: String = env.getArgument(ARG_FORMAT)
val template: String = env.getArgument(ARG_TEMPLATE)

// Renderer
val renderer = eventRendererRegistry.findEventRendererById(format)
?: PlainEventRenderer.INSTANCE

// Rendering context
val context = mapOf(
"build" to build,
"branch" to build.branch,
"project" to build.project,
)

// Rendering
templatingService.render(
template = template,
context = context,
renderer = renderer,
)
}
.build()
)
} else {
null
}

companion object {
private const val ARG_FORMAT = "format"
private const val ARG_TEMPLATE = "template"
}

}
Loading

0 comments on commit 3c50b7b

Please sign in to comment.