diff --git a/ontrack-docs/src/docs/asciidoc/notifications/notification-backend-workflow.adoc b/ontrack-docs/src/docs/asciidoc/notifications/notification-backend-workflow.adoc index 410873a2db..819dc720ec 100644 --- a/ontrack-docs/src/docs/asciidoc/notifications/notification-backend-workflow.adoc +++ b/ontrack-docs/src/docs/asciidoc/notifications/notification-backend-workflow.adoc @@ -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. diff --git a/ontrack-docs/src/docs/asciidoc/templating/sources/index.adoc b/ontrack-docs/src/docs/asciidoc/templating/sources/index.adoc index 05f586f865..9fba7d34b4 100644 --- a/ontrack-docs/src/docs/asciidoc/templating/sources/index.adoc +++ b/ontrack-docs/src/docs/asciidoc/templating/sources/index.adoc @@ -1,19 +1,21 @@ [[appendix-templating-sources-index]] ==== List of templating sources -* <> +* <> * <> * <> * <> +* <> * <> * <> * <> * <> -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[] diff --git a/ontrack-docs/src/docs/asciidoc/templating/sources/templating-source-build-changelog.adoc b/ontrack-docs/src/docs/asciidoc/templating/sources/templating-source-build-changelog.adoc new file mode 100644 index 0000000000..7d46c3271c --- /dev/null +++ b/ontrack-docs/src/docs/asciidoc/templating/sources/templating-source-build-changelog.adoc @@ -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} +---- diff --git a/ontrack-docs/src/docs/asciidoc/templating/sources/templating-source-changelog.adoc b/ontrack-docs/src/docs/asciidoc/templating/sources/templating-source-promotion-run-changelog.adoc similarity index 95% rename from ontrack-docs/src/docs/asciidoc/templating/sources/templating-source-changelog.adoc rename to ontrack-docs/src/docs/asciidoc/templating/sources/templating-source-promotion-run-changelog.adoc index feb999f857..53f762467a 100644 --- a/ontrack-docs/src/docs/asciidoc/templating/sources/templating-source-changelog.adoc +++ b/ontrack-docs/src/docs/asciidoc/templating/sources/templating-source-promotion-run-changelog.adoc @@ -1,5 +1,5 @@ -[[templating-source-changelog]] -===== changelog +[[templating-source-promotion-run-changelog]] +===== PromotionRun.changelog Renders a change log for this promotion run. diff --git a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/BuildChangeLogTemplatingSource.kt b/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/BuildChangeLogTemplatingSource.kt new file mode 100644 index 0000000000..bb1f9546f0 --- /dev/null +++ b/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/BuildChangeLogTemplatingSource.kt @@ -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, 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 + } + } + +} \ No newline at end of file diff --git a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/BuildChangeLogTemplatingSourceConfig.kt b/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/BuildChangeLogTemplatingSourceConfig.kt new file mode 100644 index 0000000000..3617d909f0 --- /dev/null +++ b/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/BuildChangeLogTemplatingSourceConfig.kt @@ -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 = 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, +) \ No newline at end of file diff --git a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/PromotionRunChangeLogTemplatingSource.kt b/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/PromotionRunChangeLogTemplatingSource.kt index 9b1b9cbf34..fe04e35d12 100644 --- a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/PromotionRunChangeLogTemplatingSource.kt +++ b/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/PromotionRunChangeLogTemplatingSource.kt @@ -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 @@ -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, diff --git a/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/changelog/AbstractSCMChangeLogTestSupport.kt b/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/changelog/AbstractSCMChangeLogTestSupport.kt index ac4d7455e9..ad99e13f91 100644 --- a/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/changelog/AbstractSCMChangeLogTestSupport.kt +++ b/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/changelog/AbstractSCMChangeLogTestSupport.kt @@ -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 diff --git a/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/changelog/BuildRenderChangeLogIT.kt b/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/changelog/BuildRenderChangeLogIT.kt new file mode 100644 index 0000000000..d686fdd044 --- /dev/null +++ b/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/changelog/BuildRenderChangeLogIT.kt @@ -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 + ) + } + } + } + +} \ No newline at end of file diff --git a/ontrack-model/src/main/java/net/nemerosa/ontrack/model/docs/DocumentationQualifier.kt b/ontrack-model/src/main/java/net/nemerosa/ontrack/model/docs/DocumentationQualifier.kt new file mode 100644 index 0000000000..239da95612 --- /dev/null +++ b/ontrack-model/src/main/java/net/nemerosa/ontrack/model/docs/DocumentationQualifier.kt @@ -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, +) diff --git a/ontrack-ui-graphql/src/main/java/net/nemerosa/ontrack/graphql/templating/RenderBuildGraphQLFieldContributor.kt b/ontrack-ui-graphql/src/main/java/net/nemerosa/ontrack/graphql/templating/RenderBuildGraphQLFieldContributor.kt new file mode 100644 index 0000000000..a34b79a244 --- /dev/null +++ b/ontrack-ui-graphql/src/main/java/net/nemerosa/ontrack/graphql/templating/RenderBuildGraphQLFieldContributor.kt @@ -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, + projectEntityType: ProjectEntityType + ): List? = + 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" + } + +} \ No newline at end of file diff --git a/ontrack-ui/src/test/java/net/nemerosa/ontrack/boot/docs/DocumentationGenerationIT.kt b/ontrack-ui/src/test/java/net/nemerosa/ontrack/boot/docs/DocumentationGenerationIT.kt index baee6e9fbc..878aff92e3 100644 --- a/ontrack-ui/src/test/java/net/nemerosa/ontrack/boot/docs/DocumentationGenerationIT.kt +++ b/ontrack-ui/src/test/java/net/nemerosa/ontrack/boot/docs/DocumentationGenerationIT.kt @@ -3,10 +3,11 @@ package net.nemerosa.ontrack.boot.docs import net.nemerosa.ontrack.extension.notifications.channels.NoTemplate import net.nemerosa.ontrack.extension.notifications.channels.NotificationChannel import net.nemerosa.ontrack.extension.workflows.execution.WorkflowNodeExecutor -import net.nemerosa.ontrack.it.AbstractDSLTestSupport import net.nemerosa.ontrack.model.annotations.getAPITypeDescription -import net.nemerosa.ontrack.model.annotations.getAPITypeName -import net.nemerosa.ontrack.model.docs.* +import net.nemerosa.ontrack.model.docs.DocumentationIgnore +import net.nemerosa.ontrack.model.docs.DocumentationQualifier +import net.nemerosa.ontrack.model.docs.getDocumentationExampleCode +import net.nemerosa.ontrack.model.docs.getFieldsDocumentation import net.nemerosa.ontrack.model.events.* import net.nemerosa.ontrack.model.templating.TemplatingFilter import net.nemerosa.ontrack.model.templating.TemplatingFunction @@ -15,7 +16,7 @@ import net.nemerosa.ontrack.model.templating.TemplatingSource import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired -import java.io.File +import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.findAnnotations import kotlin.reflect.full.hasAnnotation import kotlin.test.fail @@ -271,7 +272,7 @@ class DocumentationGenerationIT : AbstractDocumentationGenerationTestSupport() { level = 4, title = "List of templating sources", items = templatingSources.associate { templatingSource -> - getTemplatingSourceFileId(templatingSource) to templatingSource.field + getTemplatingSourceFileId(templatingSource) to getTemplatingSourceTitle(templatingSource) } ) @@ -379,7 +380,6 @@ class DocumentationGenerationIT : AbstractDocumentationGenerationTestSupport() { "templating-filter-${templatingFilter.id}" private fun generateTemplatingSource(directoryContext: DirectoryContext, templatingSource: TemplatingSource) { - val field = templatingSource.field val description = getAPITypeDescription(templatingSource::class) val parameters = if (templatingSource::class.hasAnnotation()) { emptyList() @@ -401,7 +401,7 @@ class DocumentationGenerationIT : AbstractDocumentationGenerationTestSupport() { directoryContext.writeFile( fileId = fileId, level = 5, - title = field, + title = getTemplatingSourceTitle(templatingSource), header = description, fields = parameters, example = example, @@ -415,7 +415,22 @@ class DocumentationGenerationIT : AbstractDocumentationGenerationTestSupport() { ) } - private fun getTemplatingSourceFileId(templatingSource: TemplatingSource) = - "templating-source-${templatingSource.field}" + private fun getTemplatingSourceFileId(templatingSource: TemplatingSource): String { + val qualifier = templatingSource::class.findAnnotation()?.value + if (qualifier.isNullOrBlank()) { + return "templating-source-${templatingSource.field}" + } else { + return "templating-source-${qualifier}-${templatingSource.field}" + } + } + + private fun getTemplatingSourceTitle(templatingSource: TemplatingSource): String { + val qualifier = templatingSource::class.findAnnotation() + if (qualifier == null) { + return templatingSource.field + } else { + return "${qualifier.name}.${templatingSource.field}" + } + } } \ No newline at end of file