Skip to content

Commit b3aa32b

Browse files
committed
Generate HTML report
1 parent f32d66b commit b3aa32b

File tree

4 files changed

+120
-14
lines changed

4 files changed

+120
-14
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Usage of junit2jira:
2828
Convert XML to a CSV file (use dash [-] for stdout)
2929
-dry-run
3030
When set to true issues will NOT be created.
31+
-html-output string
32+
Generate HTML report (use dash [-] for stdout)
3133
-jira-url string
3234
Url of JIRA instance (default "https://issues.redhat.com/")
3335
-job-name string
@@ -40,6 +42,9 @@ Usage of junit2jira:
4042
Number of reported failures that should cause single issue creation. (default 10)
4143
-timestamp string
4244
Timestamp of CI test. (default "2023-04-18T12:07:44+02:00")
45+
-v short alias for -version
46+
-version
47+
print version information and exit
4348
```
4449
4550
## Example usage

htmlOutput.tpl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<html>
2+
<head>
3+
<title><h4>Possible Flake Tests</h4></title>
4+
<style>
5+
body { color: #e8e8e8; background-color: #424242; font-family: "Roboto", "Helvetica", "Arial", sans-serif }
6+
a { color: #ff8caa }
7+
a:visited { color: #ff8caa }
8+
</style>
9+
</head>
10+
<body>
11+
<ul>
12+
{{- $url := .JiraUrl -}}
13+
{{- range $issue := .Issues }}
14+
<li><a target=_blank href="{{ $url }}browse/{{ $issue.Key }}">
15+
{{- $issue.Key }}: {{ if $issue.Fields }}{{ $issue.Fields.Summary }}{{ end -}}
16+
</a>
17+
{{- end }}
18+
</ul>
19+
</body>
20+
</html>

main.go

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"bytes"
5+
_ "embed"
56
"encoding/csv"
67
"flag"
78
"fmt"
@@ -29,6 +30,7 @@ ORDER BY created DESC`
2930

3031
func main() {
3132
p := params{}
33+
flag.StringVar(&p.htmlOutput, "html-output", "", "Generate HTML report (use dash [-] for stdout)")
3234
flag.StringVar(&p.csvOutput, "csv-output", "", "Convert XML to a CSV file (use dash [-] for stdout)")
3335
flag.StringVar(&p.jiraUrl, "jira-url", "https://issues.redhat.com/", "Url of JIRA instance")
3436
flag.StringVar(&p.junitReportsDir, "junit-reports-dir", os.Getenv("ARTIFACT_DIR"), "Dir that contains jUnit reports XML files")
@@ -88,10 +90,49 @@ func run(p params) error {
8890
return errors.Wrap(err, "could not find failed tests")
8991
}
9092

91-
err = j.createIssuesOrComments(failedTests)
93+
issues, err := j.createIssuesOrComments(failedTests)
9294
if err != nil {
9395
return errors.Wrap(err, "could not create issues or comments")
9496
}
97+
return j.createHtml(issues)
98+
}
99+
100+
//go:embed htmlOutput.tpl
101+
var htmlOutputTemplate string
102+
103+
func (j junit2jira) createHtml(issues []*jira.Issue) error {
104+
if j.htmlOutput == "" || len(issues) == 0 {
105+
return nil
106+
}
107+
out := os.Stdout
108+
if j.htmlOutput != "-" {
109+
file, err := os.Create(j.htmlOutput)
110+
if err != nil {
111+
return fmt.Errorf("could not create file %s: %w", j.htmlOutput, err)
112+
}
113+
out = file
114+
defer file.Close()
115+
}
116+
return j.renderHtml(issues, out)
117+
}
118+
119+
type htmlData struct {
120+
Issues []*jira.Issue
121+
JiraUrl string
122+
}
123+
124+
func (j junit2jira) renderHtml(issues []*jira.Issue, out io.Writer) error {
125+
t, err := template.New(j.htmlOutput).Parse(htmlOutputTemplate)
126+
if err != nil {
127+
return fmt.Errorf("could parse template: %w", err)
128+
}
129+
err = t.Execute(out, htmlData{
130+
Issues: issues,
131+
JiraUrl: j.jiraUrl,
132+
})
133+
if err != nil {
134+
return fmt.Errorf("could not render template %s: %w", j.htmlOutput, err)
135+
}
95136
return nil
96137
}
97138

@@ -111,31 +152,35 @@ func (j junit2jira) createCsv(testSuites []junit.Suite) error {
111152
return junit2csv(testSuites, j.params, out)
112153
}
113154

114-
func (j junit2jira) createIssuesOrComments(failedTests []testCase) error {
155+
func (j junit2jira) createIssuesOrComments(failedTests []testCase) ([]*jira.Issue, error) {
115156
var result error
157+
issues := make([]*jira.Issue, 0, len(failedTests))
116158
for _, tc := range failedTests {
117-
err := j.createIssueOrComment(tc)
159+
issue, err := j.createIssueOrComment(tc)
118160
if err != nil {
119161
result = multierror.Append(result, err)
120162
}
163+
if issue != nil {
164+
issues = append(issues, issue)
165+
}
121166
}
122-
return result
167+
return issues, result
123168
}
124169

125-
func (j junit2jira) createIssueOrComment(tc testCase) error {
170+
func (j junit2jira) createIssueOrComment(tc testCase) (*jira.Issue, error) {
126171
summary, err := tc.summary()
127172
if err != nil {
128-
return fmt.Errorf("could not get summary: %w", err)
173+
return nil, fmt.Errorf("could not get summary: %w", err)
129174
}
130175
description, err := tc.description()
131176
if err != nil {
132-
return fmt.Errorf("could not get description: %w", err)
177+
return nil, fmt.Errorf("could not get description: %w", err)
133178
}
134179
log.Println("Searching for ", summary)
135180
search, response, err := j.jiraClient.Issue.Search(fmt.Sprintf(jql, summary), nil)
136181
if err != nil {
137182
logError(err, response)
138-
return fmt.Errorf("could not search: %w", err)
183+
return nil, fmt.Errorf("could not search: %w", err)
139184
}
140185

141186
issue := findMatchingIssue(search, summary)
@@ -148,15 +193,15 @@ func (j junit2jira) createIssueOrComment(tc testCase) error {
148193
log.Println("Dry run: will just print issue content")
149194
log.Println(summary)
150195
log.Println(description)
151-
return nil
196+
return nil, nil
152197
}
153198
create, response, err := j.jiraClient.Issue.Create(newIssue(summary, description))
154199
if response != nil && err != nil {
155200
logError(err, response)
156-
return fmt.Errorf("could not create issue %s: %w", summary, err)
201+
return nil, fmt.Errorf("could not create issue %s: %w", summary, err)
157202
}
158203
log.Printf("Created new issues: %s:%s", create.Key, summary)
159-
return nil
204+
return create, nil
160205
}
161206

162207
comment := jira.Comment{
@@ -168,16 +213,16 @@ func (j junit2jira) createIssueOrComment(tc testCase) error {
168213
if j.dryRun {
169214
log.Println("Dry run: will just print comment")
170215
log.Println(description)
171-
return nil
216+
return issue, nil
172217
}
173218

174219
addComment, response, err := j.jiraClient.Issue.AddComment(issue.ID, &comment)
175220
if response != nil && err != nil {
176221
logError(err, response)
177-
return fmt.Errorf("could not create issue %s: %w", summary, err)
222+
return nil, fmt.Errorf("could not create issue %s: %w", summary, err)
178223
}
179224
log.Printf("Created comment %s for %s:%s ", addComment.ID, issue.Key, summary)
180-
return nil
225+
return issue, nil
181226
}
182227

183228
func newIssue(summary string, description string) *jira.Issue {
@@ -391,6 +436,7 @@ type params struct {
391436
junitReportsDir string
392437
timestamp string
393438
csvOutput string
439+
htmlOutput string
394440
}
395441

396442
func NewTestCase(tc junit.Test, p params) testCase {

main_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package main
22

33
import (
44
"bytes"
5+
"github.com/andygrunwald/go-jira"
56
"github.com/joshdk/go-junit"
67
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
79
"testing"
810
)
911

@@ -329,3 +331,36 @@ func TestCsvOutput(t *testing.T) {
329331
assert.NoError(t, err)
330332
assert.Equal(t, "BuildId,Timestamp,Classname,Name,Duration,Status,JobName,BuildTag\n", buf.String())
331333
}
334+
335+
func TestHtmlOutput(t *testing.T) {
336+
j := junit2jira{params: params{jiraUrl: "https://issues.redhat.com/"}}
337+
338+
buf := bytes.NewBufferString("")
339+
require.NoError(t, j.renderHtml(nil, buf))
340+
341+
issues := []*jira.Issue{
342+
{Key: "ROX-1", Fields: &jira.IssueFields{Summary: "abc"}},
343+
{Key: "ROX-2", Fields: &jira.IssueFields{Summary: "def"}},
344+
{Key: "ROX-3"},
345+
}
346+
buf = bytes.NewBufferString("")
347+
require.NoError(t, j.renderHtml(issues, buf))
348+
349+
assert.Equal(t, `<html>
350+
<head>
351+
<title><h4>Possible Flake Tests</h4></title>
352+
<style>
353+
body { color: #e8e8e8; background-color: #424242; font-family: "Roboto", "Helvetica", "Arial", sans-serif }
354+
a { color: #ff8caa }
355+
a:visited { color: #ff8caa }
356+
</style>
357+
</head>
358+
<body>
359+
<ul>
360+
<li><a target=_blank href="https://issues.redhat.com/browse/ROX-1">ROX-1: abc</a>
361+
<li><a target=_blank href="https://issues.redhat.com/browse/ROX-2">ROX-2: def</a>
362+
<li><a target=_blank href="https://issues.redhat.com/browse/ROX-3">ROX-3: </a>
363+
</ul>
364+
</body>
365+
</html>`, buf.String())
366+
}

0 commit comments

Comments
 (0)