diff --git a/README.md b/README.md
index 3c3c054..91f5bbd 100644
--- a/README.md
+++ b/README.md
@@ -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
@@ -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
diff --git a/htmlOutput.tpl b/htmlOutput.tpl
new file mode 100644
index 0000000..d9462d8
--- /dev/null
+++ b/htmlOutput.tpl
@@ -0,0 +1,20 @@
+
+
+ Possible Flake Tests
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main.go b/main.go
index 2c2d4d7..9c2fd0c 100644
--- a/main.go
+++ b/main.go
@@ -2,6 +2,7 @@ package main
import (
"bytes"
+ _ "embed"
"encoding/csv"
"flag"
"fmt"
@@ -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")
@@ -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
}
@@ -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)
@@ -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{
@@ -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 {
@@ -391,6 +436,7 @@ type params struct {
junitReportsDir string
timestamp string
csvOutput string
+ htmlOutput string
}
func NewTestCase(tc junit.Test, p params) testCase {
diff --git a/main_test.go b/main_test.go
index 9aa4f29..e9c9aa1 100644
--- a/main_test.go
+++ b/main_test.go
@@ -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"
)
@@ -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, `
+
+ Possible Flake Tests
+
+
+
+
+
+`, buf.String())
+}