Skip to content

Commit

Permalink
Generate HTML report
Browse files Browse the repository at this point in the history
  • Loading branch information
janisz committed Aug 3, 2023
1 parent f32d66b commit b3aa32b
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 14 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Usage of junit2jira:
Convert XML to a CSV file (use dash [-] for stdout)
-dry-run
When set to true issues will NOT be created.
-html-output string
Generate HTML report (use dash [-] for stdout)
-jira-url string
Url of JIRA instance (default "https://issues.redhat.com/")
-job-name string
Expand All @@ -40,6 +42,9 @@ Usage of junit2jira:
Number of reported failures that should cause single issue creation. (default 10)
-timestamp string
Timestamp of CI test. (default "2023-04-18T12:07:44+02:00")
-v short alias for -version
-version
print version information and exit
```
## Example usage
Expand Down
20 changes: 20 additions & 0 deletions htmlOutput.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<html>
<head>
<title><h4>Possible Flake Tests</h4></title>
<style>
body { color: #e8e8e8; background-color: #424242; font-family: "Roboto", "Helvetica", "Arial", sans-serif }
a { color: #ff8caa }
a:visited { color: #ff8caa }
</style>
</head>
<body>
<ul>
{{- $url := .JiraUrl -}}
{{- range $issue := .Issues }}
<li><a target=_blank href="{{ $url }}browse/{{ $issue.Key }}">
{{- $issue.Key }}: {{ if $issue.Fields }}{{ $issue.Fields.Summary }}{{ end -}}
</a>
{{- end }}
</ul>
</body>
</html>
74 changes: 60 additions & 14 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bytes"
_ "embed"
"encoding/csv"
"flag"
"fmt"
Expand Down Expand Up @@ -29,6 +30,7 @@ ORDER BY created DESC`

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

err = j.createIssuesOrComments(failedTests)
issues, err := j.createIssuesOrComments(failedTests)
if err != nil {
return errors.Wrap(err, "could not create issues or comments")
}
return j.createHtml(issues)
}

//go:embed htmlOutput.tpl
var htmlOutputTemplate string

func (j junit2jira) createHtml(issues []*jira.Issue) error {
if j.htmlOutput == "" || len(issues) == 0 {
return nil
}
out := os.Stdout
if j.htmlOutput != "-" {
file, err := os.Create(j.htmlOutput)
if err != nil {
return fmt.Errorf("could not create file %s: %w", j.htmlOutput, err)
}
out = file
defer file.Close()
}
return j.renderHtml(issues, out)
}

type htmlData struct {
Issues []*jira.Issue
JiraUrl string
}

func (j junit2jira) renderHtml(issues []*jira.Issue, out io.Writer) error {
t, err := template.New(j.htmlOutput).Parse(htmlOutputTemplate)
if err != nil {
return fmt.Errorf("could parse template: %w", err)
}
err = t.Execute(out, htmlData{
Issues: issues,
JiraUrl: j.jiraUrl,
})
if err != nil {
return fmt.Errorf("could not render template %s: %w", j.htmlOutput, err)
}
return nil
}

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

func (j junit2jira) createIssuesOrComments(failedTests []testCase) error {
func (j junit2jira) createIssuesOrComments(failedTests []testCase) ([]*jira.Issue, error) {
var result error
issues := make([]*jira.Issue, 0, len(failedTests))
for _, tc := range failedTests {
err := j.createIssueOrComment(tc)
issue, err := j.createIssueOrComment(tc)
if err != nil {
result = multierror.Append(result, err)
}
if issue != nil {
issues = append(issues, issue)
}
}
return result
return issues, result
}

func (j junit2jira) createIssueOrComment(tc testCase) error {
func (j junit2jira) createIssueOrComment(tc testCase) (*jira.Issue, error) {
summary, err := tc.summary()
if err != nil {
return fmt.Errorf("could not get summary: %w", err)
return nil, fmt.Errorf("could not get summary: %w", err)
}
description, err := tc.description()
if err != nil {
return fmt.Errorf("could not get description: %w", err)
return nil, fmt.Errorf("could not get description: %w", err)
}
log.Println("Searching for ", summary)
search, response, err := j.jiraClient.Issue.Search(fmt.Sprintf(jql, summary), nil)
if err != nil {
logError(err, response)
return fmt.Errorf("could not search: %w", err)
return nil, fmt.Errorf("could not search: %w", err)
}

issue := findMatchingIssue(search, summary)
Expand All @@ -148,15 +193,15 @@ func (j junit2jira) createIssueOrComment(tc testCase) error {
log.Println("Dry run: will just print issue content")
log.Println(summary)
log.Println(description)
return nil
return nil, nil
}
create, response, err := j.jiraClient.Issue.Create(newIssue(summary, description))
if response != nil && err != nil {
logError(err, response)
return fmt.Errorf("could not create issue %s: %w", summary, err)
return nil, fmt.Errorf("could not create issue %s: %w", summary, err)
}
log.Printf("Created new issues: %s:%s", create.Key, summary)
return nil
return create, nil
}

comment := jira.Comment{
Expand All @@ -168,16 +213,16 @@ func (j junit2jira) createIssueOrComment(tc testCase) error {
if j.dryRun {
log.Println("Dry run: will just print comment")
log.Println(description)
return nil
return issue, nil
}

addComment, response, err := j.jiraClient.Issue.AddComment(issue.ID, &comment)
if response != nil && err != nil {
logError(err, response)
return fmt.Errorf("could not create issue %s: %w", summary, err)
return nil, fmt.Errorf("could not create issue %s: %w", summary, err)
}
log.Printf("Created comment %s for %s:%s ", addComment.ID, issue.Key, summary)
return nil
return issue, nil
}

func newIssue(summary string, description string) *jira.Issue {
Expand Down Expand Up @@ -391,6 +436,7 @@ type params struct {
junitReportsDir string
timestamp string
csvOutput string
htmlOutput string
}

func NewTestCase(tc junit.Test, p params) testCase {
Expand Down
35 changes: 35 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package main

import (
"bytes"
"github.com/andygrunwald/go-jira"
"github.com/joshdk/go-junit"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

Expand Down Expand Up @@ -329,3 +331,36 @@ func TestCsvOutput(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "BuildId,Timestamp,Classname,Name,Duration,Status,JobName,BuildTag\n", buf.String())
}

func TestHtmlOutput(t *testing.T) {
j := junit2jira{params: params{jiraUrl: "https://issues.redhat.com/"}}

buf := bytes.NewBufferString("")
require.NoError(t, j.renderHtml(nil, buf))

issues := []*jira.Issue{
{Key: "ROX-1", Fields: &jira.IssueFields{Summary: "abc"}},
{Key: "ROX-2", Fields: &jira.IssueFields{Summary: "def"}},
{Key: "ROX-3"},
}
buf = bytes.NewBufferString("")
require.NoError(t, j.renderHtml(issues, buf))

assert.Equal(t, `<html>
<head>
<title><h4>Possible Flake Tests</h4></title>
<style>
body { color: #e8e8e8; background-color: #424242; font-family: "Roboto", "Helvetica", "Arial", sans-serif }
a { color: #ff8caa }
a:visited { color: #ff8caa }
</style>
</head>
<body>
<ul>
<li><a target=_blank href="https://issues.redhat.com/browse/ROX-1">ROX-1: abc</a>
<li><a target=_blank href="https://issues.redhat.com/browse/ROX-2">ROX-2: def</a>
<li><a target=_blank href="https://issues.redhat.com/browse/ROX-3">ROX-3: </a>
</ul>
</body>
</html>`, buf.String())
}

0 comments on commit b3aa32b

Please sign in to comment.