Skip to content

Commit 6510470

Browse files
committed
Configurable tag columns for build scan reports
1 parent c892310 commit 6510470

File tree

7 files changed

+323
-26
lines changed

7 files changed

+323
-26
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,30 @@ develocity:
7676
# To have the bot create a GitHub check listing Develocity build scans
7777
# for every commit that has completed checks related to CI (GitHub Actions or Jenkins)
7878
addCheck: true
79+
# To group tags in a separate column and/or alter/remove some tags,
80+
# a list of column rules can be configured:
81+
tags:
82+
- column: "OS"
83+
pattern: "Linux"
84+
- column: "OS" # Multiple rules can target the same column
85+
pattern: "Windows.*"
86+
replacement: "Windows"
87+
- column: "Java"
88+
pattern: "jdk-(.*)"
89+
replacement: "$1"
90+
- column: "Backend"
91+
pattern: "elasticsearch-(.*)"
92+
replacement: "es-$1" # Replacement can refer to capture groups
93+
- column: "Backend"
94+
pattern: "(.*-)?opensearch-(.*)"
95+
replacement: "$1os-$2"
96+
- column: "Backend"
97+
pattern: "lucene"
98+
- column: "DB"
99+
pattern: "h2|postgres|postgres(ql)?|mysql|mssql|derby|tidb|cockroach(db)?|oracle.*|db2"
100+
replacement: "$0"
101+
- pattern: "hibernate.search|elasticsearch|opensearch|main|\\d+.\\d+|PR-\\d+"
102+
replacement: "" # Just remove these tags
79103
```
80104
81105
### Altering the infrastructure

src/main/java/org/hibernate/infra/bot/ExtractDevelocityBuildScans.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,26 @@ public class ExtractDevelocityBuildScans {
4141
@Inject
4242
DevelocityReportFormatter reportFormatter;
4343

44-
void checkRunRerequested(@CheckRun.Rerequested GHEventPayload.CheckRun payload) {
44+
void checkRunRerequested(@CheckRun.Rerequested GHEventPayload.CheckRun payload,
45+
@ConfigFile("hibernate-github-bot.yml") RepositoryConfig repositoryConfig) {
4546
var repository = payload.getRepository();
4647
var checkRun = payload.getCheckRun();
4748
if ( !DEVELOCITY_CHECK_RUN_NAME.equals( checkRun.getName() ) ) {
4849
return;
4950
}
5051
String sha = checkRun.getHeadSha();
51-
extractCIBuildScans( repository, sha );
52+
extractCIBuildScans( repository, repositoryConfig.develocity.buildScan, sha );
5253
}
5354

5455
void checkRunCompleted(@CheckRun.Completed GHEventPayload.CheckRun payload,
5556
@ConfigFile("hibernate-github-bot.yml") RepositoryConfig repositoryConfig) {
5657
if ( repositoryConfig == null
5758
|| repositoryConfig.develocity == null
58-
|| repositoryConfig.develocity.buildScan == null
59-
|| !repositoryConfig.develocity.buildScan.addCheck ) {
59+
|| repositoryConfig.develocity.buildScan == null ) {
60+
return;
61+
}
62+
var buildScanConfig = repositoryConfig.develocity.buildScan;
63+
if ( !buildScanConfig.addCheck ) {
6064
return;
6165
}
6266
var repository = payload.getRepository();
@@ -66,10 +70,11 @@ void checkRunCompleted(@CheckRun.Completed GHEventPayload.CheckRun payload,
6670
return;
6771
}
6872
String sha = checkRun.getHeadSha();
69-
extractCIBuildScans( repository, sha );
73+
extractCIBuildScans( repository, buildScanConfig, sha );
7074
}
7175

72-
private void extractCIBuildScans(GHRepository repository, String sha) {
76+
private void extractCIBuildScans(GHRepository repository, RepositoryConfig.Develocity.BuildScan config,
77+
String sha) {
7378
try {
7479
long checkId = createDevelocityCheck( repository, sha );
7580
Throwable failure = null;
@@ -105,7 +110,7 @@ private void extractCIBuildScans(GHRepository repository, String sha) {
105110
if ( failure != null ) {
106111
Log.errorf( failure, "Failed to extract all build scans from commit %s" + sha );
107112
}
108-
updateDevelocityCheck( repository, checkId, buildScans, failure );
113+
updateDevelocityCheck( repository, config, checkId, buildScans, failure );
109114
}
110115
catch (IOException | RuntimeException e) {
111116
Log.errorf( e, "Failed to report build scans from commit %s" + sha );
@@ -184,12 +189,12 @@ private long createDevelocityCheck(GHRepository repository, String sha) throws I
184189
.getId();
185190
}
186191

187-
private void updateDevelocityCheck(GHRepository repository, long checkId,
188-
List<DevelocityCIBuildScan> buildScans, Throwable failure)
192+
private void updateDevelocityCheck(GHRepository repository, RepositoryConfig.Develocity.BuildScan config,
193+
long checkId, List<DevelocityCIBuildScan> buildScans, Throwable failure)
189194
throws IOException {
190195
String formattedBuildScanList = "";
191196
try {
192-
formattedBuildScanList = reportFormatter.summary( buildScans );
197+
formattedBuildScanList = reportFormatter.summary( buildScans, config );
193198
}
194199
catch (RuntimeException e) {
195200
if ( failure == null ) {

src/main/java/org/hibernate/infra/bot/config/RepositoryConfig.java

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.hibernate.infra.bot.config;
22

3+
import java.util.ArrayList;
34
import java.util.Collections;
45
import java.util.List;
56
import java.util.Optional;
@@ -11,7 +12,7 @@ public class RepositoryConfig {
1112

1213
public JiraConfig jira;
1314

14-
public DevelocityConfig develocity;
15+
public Develocity develocity;
1516

1617
public static class JiraConfig {
1718
private Optional<Pattern> issueKeyPattern = Optional.empty();
@@ -82,18 +83,50 @@ public Pattern getTitlePattern() {
8283
}
8384

8485
public void setTitlePattern(String titlePattern) {
85-
this.titlePattern = Pattern.compile( titlePattern );
86+
this.titlePattern = Patterns.compile( titlePattern );
8687
}
8788
}
8889

89-
public static class DevelocityConfig {
90-
public DevelocityBuildScanConfig buildScan;
91-
}
90+
public static class Develocity {
91+
public BuildScan buildScan;
92+
93+
public static class BuildScan {
94+
95+
public boolean addCheck = false;
96+
97+
public List<ColumnRule> tags = new ArrayList<>();
98+
99+
public BuildScan() {
100+
}
92101

93-
public static class DevelocityBuildScanConfig {
102+
public BuildScan(boolean addCheck, List<ColumnRule> tags) {
103+
this.addCheck = addCheck;
104+
this.tags = tags;
105+
}
106+
}
107+
108+
public static class ColumnRule {
109+
public String column;
110+
private Pattern pattern;
111+
public Optional<String> replacement = Optional.empty();
112+
113+
public ColumnRule() {
114+
}
94115

95-
public boolean addCheck = false;
116+
public ColumnRule(String column, Pattern pattern, Optional<String> replacement) {
117+
this.column = column;
118+
this.pattern = pattern;
119+
this.replacement = replacement;
120+
}
96121

122+
public Pattern getPattern() {
123+
return pattern;
124+
}
125+
126+
public void setPattern(String pattern) {
127+
this.pattern = Patterns.compile( pattern );
128+
}
129+
}
97130
}
98131

99132
}

src/main/java/org/hibernate/infra/bot/develocity/DevelocityReportFormatter.java

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,89 @@
11
package org.hibernate.infra.bot.develocity;
22

33
import java.net.URI;
4+
import java.util.Collection;
5+
import java.util.HashMap;
6+
import java.util.LinkedHashMap;
7+
import java.util.LinkedHashSet;
48
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Set;
11+
import java.util.TreeSet;
512
import java.util.stream.Collectors;
613

714
import jakarta.enterprise.context.ApplicationScoped;
815

16+
import org.hibernate.infra.bot.config.RepositoryConfig;
17+
918
import io.quarkus.qute.CheckedTemplate;
1019
import io.quarkus.qute.TemplateExtension;
1120
import io.quarkus.qute.TemplateInstance;
1221

1322
@ApplicationScoped
1423
public class DevelocityReportFormatter {
1524

16-
public String summary(List<DevelocityCIBuildScan> buildScans) {
17-
return Templates.summary( buildScans )
25+
public String summary(List<DevelocityCIBuildScan> buildScans, RepositoryConfig.Develocity.BuildScan config) {
26+
Collection<TagColumn> tagColumns = extractTagColumns( buildScans, config );
27+
return Templates.summary( buildScans, tagColumns )
1828
.render();
1929
}
2030

31+
public record TagColumn(String name, Map<DevelocityCIBuildScan, Set<String>> content) {
32+
}
33+
34+
private Collection<TagColumn> extractTagColumns(List<DevelocityCIBuildScan> buildScans,
35+
RepositoryConfig.Develocity.BuildScan config) {
36+
Map<String, TagColumn> tagColumns = new LinkedHashMap<>();
37+
// Preserve the order in which groups are declared
38+
for ( RepositoryConfig.Develocity.ColumnRule rule : config.tags ) {
39+
tagColumns.put( rule.column, new TagColumn( rule.column, new HashMap<>() ) );
40+
}
41+
// Put the default groups last
42+
tagColumns.put( null, new TagColumn( null, new HashMap<>() ) );
43+
44+
for ( DevelocityCIBuildScan buildScan : buildScans ) {
45+
for ( String tag : buildScan.tags() ) {
46+
boolean matched = false;
47+
for ( RepositoryConfig.Develocity.ColumnRule rule : config.tags ) {
48+
var matcher = rule.getPattern().matcher( tag );
49+
if ( matcher.matches() ) {
50+
matched = true;
51+
var addedTag = tag;
52+
if ( rule.replacement.isPresent() ) {
53+
addedTag = matcher.replaceAll( rule.replacement.get() );
54+
}
55+
if ( !addedTag.isBlank() ) {
56+
tagColumns.get( rule.column ).content()
57+
.computeIfAbsent( buildScan, ignored -> new TreeSet<>() )
58+
.add( addedTag );
59+
}
60+
break;
61+
}
62+
}
63+
if ( !matched ) {
64+
tagColumns.get( null ).content()
65+
// Use LinkedHashSet for the default column to preserve tag order
66+
.computeIfAbsent( buildScan, ignored -> new LinkedHashSet<>() )
67+
.add( tag );
68+
}
69+
}
70+
}
71+
72+
// Remove empty columns
73+
for ( var it = tagColumns.entrySet().iterator(); it.hasNext(); ) {
74+
var column = it.next().getValue();
75+
if ( column.content.isEmpty() ) {
76+
it.remove();
77+
}
78+
}
79+
80+
return tagColumns.values();
81+
}
82+
2183
@CheckedTemplate
2284
private static class Templates {
23-
public static native TemplateInstance summary(List<DevelocityCIBuildScan> buildScans);
85+
public static native TemplateInstance summary(List<DevelocityCIBuildScan> buildScans,
86+
Collection<TagColumn> tagColumns);
2487
}
2588

2689
@TemplateExtension
@@ -31,13 +94,13 @@ static String backQuoted(String content) {
3194
return content == null || content.isBlank() ? null : '`' + content + '`';
3295
}
3396

34-
static List<String> backQuoted(List<String> content) {
97+
static List<String> backQuoted(Collection<String> content) {
3598
return content.stream()
3699
.map( TemplateExtensions::backQuoted )
37100
.toList();
38101
}
39102

40-
static String spaceDelimited(List<String> content) {
103+
static String spaceDelimited(Collection<String> content) {
41104
return content.stream()
42105
.filter( s -> s != null && !s.isBlank() )
43106
.collect( Collectors.joining( " " ) );
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.hibernate.infra.bot.jackson;
2+
3+
import java.util.List;
4+
5+
import jakarta.inject.Inject;
6+
import jakarta.inject.Singleton;
7+
8+
import com.fasterxml.jackson.databind.ObjectMapper;
9+
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
10+
11+
import io.quarkiverse.githubapp.runtime.UtilsProducer;
12+
import io.quarkus.runtime.Startup;
13+
14+
@Singleton
15+
@Startup
16+
public class ObjectMapperCustomizer {
17+
18+
@Inject
19+
public void addModules(ObjectMapper jsonMapper, @UtilsProducer.Yaml ObjectMapper yamlMapper) {
20+
for ( ObjectMapper mapper : List.of( jsonMapper, yamlMapper ) ) {
21+
mapper.registerModule( new Jdk8Module() );
22+
}
23+
}
24+
25+
}

src/main/resources/templates/DevelocityReportFormatter/summary.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
{#if buildScans == null || buildScans.empty}
22
No build scan found for this CI run.
33
{#else}
4-
| Status | Job/Workflow | Tags | Goals | Build Scan | Tests | Logs |
5-
| :-: | -- | -- | -- | :-: | :-: | :-: |
4+
| Status | Job/Workflow {!
5+
!}{#for tagColumn in tagColumns}{!
6+
!}| {#if tagColumn.name}{tagColumn.name}{#else}{#if tagColumns.size == 1}Tags{#else}Other tags{/if}{/if} {!
7+
!}{/for}{!
8+
!}| Goals | Build Scan | Tests | Logs |
9+
| :-: | -- {!
10+
!}{#for tagColumn in tagColumns}| -- {/for}{!
11+
!}| -- | :-: | :-: | :-: |
612
{#for buildScan in buildScans}
713
|[{buildScan.status.emoji}]({buildScan.statusUri}){!
814
!}|{list:create(buildScan.jobOrWorkflow, buildScan.stage).spaceDelimited.backQuoted}{!
9-
!}|{buildScan.tags.backQuoted.spaceDelimited}{!
15+
!}{#for tagColumn in tagColumns}{!
16+
!}|{tagColumn.content.get(buildScan).or(list:create()).backQuoted.spaceDelimited}{!
17+
!}{/for}{!
1018
!}|{buildScan.goals.spaceDelimited.backQuoted}{!
1119
!}|[:mag:]({buildScan.buildScanUri}){!
1220
!}|[{buildScan.testStatus.emoji}]({buildScan.testsUri}){!

0 commit comments

Comments
 (0)