Repository: reviewdog/reviewdog
Pull request #287 (2019-09-14 16:56:39)

Support report level for Check API


Patch:
diff --git a/.reviewdog.yml b/.reviewdog.yml
index 6553c58f..24dcbe47 100644
--- a/.reviewdog.yml
+++ b/.reviewdog.yml
@@ -4,6 +4,7 @@ runner:
   golint:
     cmd: golint $(go list ./... | grep -v /vendor/)
     format: golint
+    level: warning
   govet:
     cmd: go vet $(go list ./... | grep -v /vendor/)
     format: govet
diff --git a/.travis.yml b/.travis.yml
index f5090b31..55ce8527 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -41,7 +41,7 @@ before_script:
 
 script:
   - reviewdog -reporter=github-pr-check
-  - $(npm bin)/textlint -f checkstyle README.md | reviewdog -f=checkstyle -name="textlint" -reporter=github-pr-check
+  - $(npm bin)/textlint -f checkstyle README.md | reviewdog -f=checkstyle -name="textlint" -reporter=github-pr-check -level=info
   - go test -v -race ./...
   - go test -coverpkg=./... -coverprofile=coverage.txt ./...
 
diff --git a/README.md b/README.md
index 518592e9..a876451e 100644
--- a/README.md
+++ b/README.md
@@ -187,12 +187,14 @@ runner:
     errorformat: # (optional if there is supporeted format for <tool-name>. see reviewdog -list)
       - <list of errorformat>
     name: <tool-name> # (optional. you can overwrite <tool-name> defined by runner key)
+    level: <level> # (optional. same as -level flag. [info,warning,error])
 
   # examples
   golint:
     cmd: golint ./...
     errorformat:
       - "%f:%l:%c: %m"
+    level: warning
   govet:
     cmd: go tool vet -all -shadowstrict .
 ```
@@ -235,7 +237,18 @@ $ golint ./... | reviewdog -f=golint -diff="git diff master"
 [![github-pr-check sample](https://user-images.githubusercontent.com/3797062/40884858-6efd82a0-6756-11e8-9f1a-c6af4f920fb0.png)](https://github.com/reviewdog/reviewdog/pull/131/checks)
 
 github-pr-check reporter reports results to [GitHub Checks](https://help.github.com/articles/about-status-checks/).
-Two options to use this reporter.
+
+You can change report level for this reporter by `level` field in [config
+file](#reviewdog-config-file) or `-level` flag. You can control GitHub status
+check result with this feature. (default: error)
+
+| Level     | GitHub Status |
+| --------- | ------------- |
+| `info`    | neutral       |
+| `warning` | neutral       |
+| `error`   | failure       |
+
+There are two options to use this reporter.
 
 #### Option 1) Run reviewdog from GitHub Actions w/ secrets.GITHUB_TOKEN (experimental)
 
@@ -464,7 +477,7 @@ $ REVIEWDOG_GITLAB_API_TOKEN="<token>"
 
 If a CI service doesn't provide information such as Pull Request ID - reviewdog can guess it by branch name and commit SHA.
 Just pass the flag `guess`:
- 
+
 ```shell
 $ reviewdog -conf=.reviewdog.yml -reporter=github-pr-check -guess
 ```
diff --git a/cmd/reviewdog/doghouse.go b/cmd/reviewdog/doghouse.go
index 40a18823..6c7c5ac1 100644
--- a/cmd/reviewdog/doghouse.go
+++ b/cmd/reviewdog/doghouse.go
@@ -85,7 +85,7 @@ func checkResultSet(ctx context.Context, r io.Reader, opt *option, isProject boo
 		if err != nil {
 			return nil, err
 		}
-		resultSet, err = projectRunAndParse(ctx, conf, buildRunnersMap(opt.runners))
+		resultSet, err = projectRunAndParse(ctx, conf, buildRunnersMap(opt.runners), opt.level)
 		if err != nil {
 			return nil, err
 		}
@@ -98,7 +98,10 @@ func checkResultSet(ctx context.Context, r io.Reader, opt *option, isProject boo
 		if err != nil {
 			return nil, err
 		}
-		resultSet.Store(toolName(opt), rs)
+		resultSet.Store(toolName(opt), &reviewdog.Result{
+			Level:        opt.level,
+			CheckResults: rs,
+		})
 	}
 	return resultSet, nil
 }
@@ -107,9 +110,10 @@ func postResultSet(ctx context.Context, resultSet *reviewdog.ResultMap, ghInfo *
 	var g errgroup.Group
 	wd, _ := os.Getwd()
 	filteredResultSet := new(reviewdog.FilteredCheckMap)
-	resultSet.Range(func(name string, results []*reviewdog.CheckResult) {
-		as := make([]*doghouse.Annotation, 0, len(results))
-		for _, r := range results {
+	resultSet.Range(func(name string, result *reviewdog.Result) {
+		checkResults := result.CheckResults
+		as := make([]*doghouse.Annotation, 0, len(checkResults))
+		for _, r := range checkResults {
 			as = append(as, checkResultToAnnotation(r, wd))
 		}
 		req := &doghouse.CheckRequest{
@@ -120,6 +124,7 @@ func postResultSet(ctx context.Context, resultSet *reviewdog.ResultMap, ghInfo *
 			SHA:         ghInfo.SHA,
 			Branch:      ghInfo.Branch,
 			Annotations: as,
+			Level:       result.Level,
 		}
 		g.Go(func() error {
 			res, err := cli.Check(ctx, req)
diff --git a/cmd/reviewdog/main.go b/cmd/reviewdog/main.go
index d55dc369..c5577576 100644
--- a/cmd/reviewdog/main.go
+++ b/cmd/reviewdog/main.go
@@ -50,6 +50,7 @@ type option struct {
 	conf             string
 	runners          string
 	reporter         string
+	level            string
 	guessPullRequest bool
 }
 
@@ -64,6 +65,7 @@ const (
 	ciDoc               = `[deprecated] reviewdog automatically get necessary data. See also -reporter for migration`
 	confDoc             = `config file path`
 	runnersDoc          = `comma separated runners name to run in config file. default: run all runners`
+	levelDoc            = `report level currently used for github-pr-check reporter ("info","warning","error").`
 	guessPullRequestDoc = `guess Pull Request ID by branch name and commit SHA`
 	reporterDoc         = `reporter of reviewdog results. (local, github-pr-check, github-pr-review, gitlab-mr-discussion, gitlab-mr-commit)
 	"local" (default)
@@ -141,6 +143,7 @@ func init() {
 	flag.StringVar(&opt.conf, "conf", "", confDoc)
 	flag.StringVar(&opt.runners, "runners", "", runnersDoc)
 	flag.StringVar(&opt.reporter, "reporter", "local", reporterDoc)
+	flag.StringVar(&opt.level, "level", "error", levelDoc)
 	flag.BoolVar(&opt.guessPullRequest, "guess", false, guessPullRequestDoc)
 }
 
diff --git a/doghouse/server/doghouse.go b/doghouse/server/doghouse.go
index 1d0a3119..c631fa93 100644
--- a/doghouse/server/doghouse.go
+++ b/doghouse/server/doghouse.go
@@ -104,7 +104,7 @@ func (ch *Checker) postCheck(ctx context.Context, branch string, checks []*revie
 	}
 	conclusion := "success"
 	if len(annotations) > 0 {
-		conclusion = "action_required"
+		conclusion = ch.conclusion()
 	}
 	name := "reviewdog"
 	title := "reviewdog report"
@@ -129,6 +129,28 @@ func (ch *Checker) postCheck(ctx context.Context, branch string, checks []*revie
 	return ch.gh.CreateCheckRun(ctx, ch.req.Owner, ch.req.Repo, opt)
 }
 
+// https://developer.github.com/v3/checks/runs/#parameters-1
+func (ch *Checker) conclusion() string {
+	switch strings.ToLower(ch.req.Level) {
+	case "info", "warning":
+		return "neutral"
+	}
+	return "failure"
+}
+
+// https://developer.github.com/v3/checks/runs/#annotations-object
+func (ch *Checker) annotationLevel() string {
+	switch strings.ToLower(ch.req.Level) {
+	case "info":
+		return "notice"
+	case "warning":
+		return "warning"
+	case "failure":
+		return "failure"
+	}
+	return "failure"
+}
+
 func (ch *Checker) summary(checks []*reviewdog.FilteredCheck) string {
 	var lines []string
 	lines = append(lines, "reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:")
@@ -186,7 +208,7 @@ func (ch *Checker) toCheckRunAnnotation(c *reviewdog.FilteredCheck) *github.Chec
 		BlobHRef:        github.String(ch.brobHRef(c.Path)),
 		StartLine:       github.Int(c.Lnum),
 		EndLine:         github.Int(c.Lnum),
-		AnnotationLevel: github.String("warning"),
+		AnnotationLevel: github.String(ch.annotationLevel()),
 		Message:         github.String(c.Message),
 	}
 	if ch.req.Name != "" {
diff --git a/doghouse/service.go b/doghouse/service.go
index 41f87312..f35d0b0a 100644
--- a/doghouse/service.go
+++ b/doghouse/service.go
@@ -26,6 +26,10 @@ type CheckRequest struct {
 	// Name of the annotation tool.
 	// Optional.
 	Name string `json:"name,omitempty"`
+
+	// Level is report level for this request.
+	// One of ["info", "warning", "error"]. Default is "error".
+	Level string `json:"level"`
 }
 
 // CheckResponse represents doghouse GitHub check response.
diff --git a/project/conf.go b/project/conf.go
index 1c6101da..86b6d34e 100644
--- a/project/conf.go
+++ b/project/conf.go
@@ -20,6 +20,8 @@ type Runner struct {
 	Format string
 	// errorformat. (e.g. `%f:%l:%c:%m`, `%-G%.%#`)
 	Errorformat []string
+	// Report Level for this runner. ("info", "warning", "error")
+	Level string
 }
 
 // Parse parses reviewdog config in yaml format.
diff --git a/project/run.go b/project/run.go
index ca930f5a..8042b820 100644
--- a/project/run.go
+++ b/project/run.go
@@ -17,7 +17,7 @@ import (
 )
 
 // RunAndParse runs commands and parse results. Returns map of tool name to check results.
-func RunAndParse(ctx context.Context, conf *Config, runners map[string]bool) (*reviewdog.ResultMap, error) {
+func RunAndParse(ctx context.Context, conf *Config, runners map[string]bool, defaultLevel string) (*reviewdog.ResultMap, error) {
 	var results reviewdog.ResultMap
 	// environment variables for each commands
 	envs := filteredEnviron()
@@ -58,7 +58,11 @@ func RunAndParse(ctx context.Context, conf *Config, runners map[string]bool) (*r
 				return err
 			}
 			log.Printf("reviewdog: [finish]\trunner=%s", runner.Name)
-			results.Store(runner.Name, rs)
+			level := runner.Level
+			if level == "" {
+				level = defaultLevel
+			}
+			results.Store(runner.Name, &reviewdog.Result{Level: level, CheckResults: rs})
 			return nil
 		})
 	}
@@ -73,7 +77,7 @@ func RunAndParse(ctx context.Context, conf *Config, runners map[string]bool) (*r
 
 // Run runs reviewdog tasks based on Config.
 func Run(ctx context.Context, conf *Config, runners map[string]bool, c reviewdog.CommentService, d reviewdog.DiffService) error {
-	results, err := RunAndParse(ctx, conf, runners)
+	results, err := RunAndParse(ctx, conf, runners, "") // Level is not used.
 	if err != nil {
 		return err
 	}
@@ -89,7 +93,8 @@ func Run(ctx context.Context, conf *Config, runners map[string]bool, c reviewdog
 		return err
 	}
 	var g errgroup.Group
-	results.Range(func(toolname string, rs []*reviewdog.CheckResult) {
+	results.Range(func(toolname string, result *reviewdog.Result) {
+		rs := result.CheckResults
 		g.Go(func() error {
 			return reviewdog.RunFromResult(ctx, c, rs, filediffs, d.Strip(), toolname)
 		})
diff --git a/resultmap.go b/resultmap.go
index 3c4a1864..acce6796 100644
--- a/resultmap.go
+++ b/resultmap.go
@@ -11,19 +11,24 @@ type ResultMap struct {
 	sm sync.Map
 }
 
-// Store saves a new []*CheckResult into ResultMap.
-func (rm *ResultMap) Store(key string, crs []*CheckResult) {
-	rm.sm.Store(key, crs)
+type Result struct {
+	Level        string
+	CheckResults []*CheckResult
 }
 
-// Load fetches []*CheckResult from ResultMap
-func (rm *ResultMap) Load(key string) ([]*CheckResult, error) {
+// Store saves a new *Result into ResultMap.
+func (rm *ResultMap) Store(key string, r *Result) {
+	rm.sm.Store(key, r)
+}
+
+// Load fetches *Result from ResultMap
+func (rm *ResultMap) Load(key string) (*Result, error) {
 	v, ok := rm.sm.Load(key)
 	if !ok {
 		return nil, fmt.Errorf("fail to get the value of key %q from results", key)
 	}
 
-	t, ok := v.([]*CheckResult)
+	t, ok := v.(*Result)
 	if !ok {
 		return nil, errors.New("stored type in ResultMap is invalid")
 	}
@@ -32,9 +37,9 @@ func (rm *ResultMap) Load(key string) ([]*CheckResult, error) {
 }
 
 // Range retrieves `key` and `values` from ResultMap iteratively.
-func (rm *ResultMap) Range(f func(key string, val []*CheckResult)) {
+func (rm *ResultMap) Range(f func(key string, val *Result)) {
 	rm.sm.Range(func(k, v interface{}) bool {
-		f(k.(string), v.([]*CheckResult))
+		f(k.(string), v.(*Result))
 		return true
 	})
 }
@@ -55,8 +60,8 @@ type FilteredCheckMap struct {
 }
 
 // Store saves a new []*FilteredCheck into FilteredCheckMap.
-func (rm *FilteredCheckMap) Store(key string, crs []*FilteredCheck) {
-	rm.sm.Store(key, crs)
+func (rm *FilteredCheckMap) Store(key string, r []*FilteredCheck) {
+	rm.sm.Store(key, r)
 }
 
 // Load fetches []*FilteredCheck from FilteredCheckMap


Test patch:
diff --git a/cmd/reviewdog/doghouse_test.go b/cmd/reviewdog/doghouse_test.go
index 568d1a34..93a0efea 100644
--- a/cmd/reviewdog/doghouse_test.go
+++ b/cmd/reviewdog/doghouse_test.go
@@ -110,21 +110,21 @@ func TestNewDoghouseServerCli(t *testing.T) {
 }
 
 func TestCheckResultSet_Project(t *testing.T) {
-	defer func(f func(ctx context.Context, conf *project.Config, runners map[string]bool) (*reviewdog.ResultMap, error)) {
+	defer func(f func(ctx context.Context, conf *project.Config, runners map[string]bool, level string) (*reviewdog.ResultMap, error)) {
 		projectRunAndParse = f
 	}(projectRunAndParse)
 
 	var wantCheckResult reviewdog.ResultMap
-	wantCheckResult.Store("name1", []*reviewdog.CheckResult{
+	wantCheckResult.Store("name1", &reviewdog.Result{CheckResults: []*reviewdog.CheckResult{
 		{
 			Lnum:    1,
 			Col:     14,
 			Message: "msg",
 			Path:    "reviewdog.go",
 		},
-	})
+	}})
 
-	projectRunAndParse = func(ctx context.Context, conf *project.Config, runners map[string]bool) (*reviewdog.ResultMap, error) {
+	projectRunAndParse = func(ctx context.Context, conf *project.Config, runners map[string]bool, level string) (*reviewdog.ResultMap, error) {
 		return &wantCheckResult, nil
 	}
 
@@ -142,9 +142,9 @@ func TestCheckResultSet_Project(t *testing.T) {
 	if got.Len() != wantCheckResult.Len() {
 		t.Errorf("length of results is different. got = %d, want = %d\n", got.Len(), wantCheckResult.Len())
 	}
-	got.Range(func(k string, v []*reviewdog.CheckResult) {
+	got.Range(func(k string, r *reviewdog.Result) {
 		w, _ := wantCheckResult.Load(k)
-		if diff := cmp.Diff(v, w); diff != "" {
+		if diff := cmp.Diff(r, w); diff != "" {
 			t.Errorf("result has diff:\n%s", diff)
 		}
 	})
@@ -160,7 +160,7 @@ func TestCheckResultSet_NonProject(t *testing.T) {
 		t.Fatal(err)
 	}
 	var want reviewdog.ResultMap
-	want.Store("golint", []*reviewdog.CheckResult{
+	want.Store("golint", &reviewdog.Result{CheckResults: []*reviewdog.CheckResult{
 		{
 			Lnum:    14,
 			Col:     14,
@@ -168,14 +168,14 @@ func TestCheckResultSet_NonProject(t *testing.T) {
 			Path:    "reviewdog.go",
 			Lines:   []string{input},
 		},
-	})
+	}})
 
 	if got.Len() != want.Len() {
 		t.Errorf("length of results is different. got = %d, want = %d\n", got.Len(), want.Len())
 	}
-	got.Range(func(k string, v []*reviewdog.CheckResult) {
+	got.Range(func(k string, r *reviewdog.Result) {
 		w, _ := want.Load(k)
-		if diff := cmp.Diff(v, w); diff != "" {
+		if diff := cmp.Diff(r, w); diff != "" {
 			t.Errorf("result has diff:\n%s", diff)
 		}
 	})
@@ -245,7 +245,7 @@ func TestPostResultSet_withReportURL(t *testing.T) {
 	}
 
 	var resultSet reviewdog.ResultMap
-	resultSet.Store("name1", []*reviewdog.CheckResult{
+	resultSet.Store("name1", &reviewdog.Result{CheckResults: []*reviewdog.CheckResult{
 		{
 			Lnum:    14,
 			Message: "name1: test 1",
@@ -256,14 +256,14 @@ func TestPostResultSet_withReportURL(t *testing.T) {
 			Message: "name1: test 2",
 			Path:    "reviewdog.go",
 		},
-	})
-	resultSet.Store("name2", []*reviewdog.CheckResult{
+	}})
+	resultSet.Store("name2", &reviewdog.Result{CheckResults: []*reviewdog.CheckResult{
 		{
 			Lnum:    14,
 			Message: "name2: test 1",
 			Path:    "cmd/reviewdog/doghouse.go",
 		},
-	})
+	}})
 
 	ghInfo := &cienv.BuildInfo{
 		Owner:       owner,
@@ -292,7 +292,7 @@ func TestPostResultSet_withoutReportURL(t *testing.T) {
 	}
 
 	var resultSet reviewdog.ResultMap
-	resultSet.Store("name1", []*reviewdog.CheckResult{})
+	resultSet.Store("name1", &reviewdog.Result{CheckResults: []*reviewdog.CheckResult{}})
 
 	ghInfo := &cienv.BuildInfo{Owner: owner, Repo: repo, PullRequest: prNum, SHA: sha}
 
@@ -326,7 +326,7 @@ func TestPostResultSet_withEmptyResponse(t *testing.T) {
 	}
 
 	var resultSet reviewdog.ResultMap
-	resultSet.Store("name1", []*reviewdog.CheckResult{})
+	resultSet.Store("name1", &reviewdog.Result{CheckResults: []*reviewdog.CheckResult{}})
 
 	ghInfo := &cienv.BuildInfo{Owner: owner, Repo: repo, PullRequest: prNum, SHA: sha}
 
diff --git a/doghouse/server/doghouse_test.go b/doghouse/server/doghouse_test.go
index 5a647f0e..332c9a47 100644
--- a/doghouse/server/doghouse_test.go
+++ b/doghouse/server/doghouse_test.go
@@ -53,13 +53,14 @@ const sampleDiff = `--- sample.old.txt	2016-10-13 05:09:35.820791185 +0900
 
 func TestCheck_OK(t *testing.T) {
 	const (
-		name      = "haya14busa-linter"
-		owner     = "haya14busa"
-		repo      = "reviewdog"
-		prNum     = 14
-		sha       = "1414"
-		reportURL = "http://example.com/report_url"
-		branch    = "test-branch"
+		name       = "haya14busa-linter"
+		owner      = "haya14busa"
+		repo       = "reviewdog"
+		prNum      = 14
+		sha        = "1414"
+		reportURL  = "http://example.com/report_url"
+		branch     = "test-branch"
+		conclusion = "neutral"
 	)
 
 	req := &doghouse.CheckRequest{
@@ -82,6 +83,7 @@ func TestCheck_OK(t *testing.T) {
 				RawMessage: "raw test message outside diff",
 			},
 		},
+		Level: "warning",
 	}
 
 	cli := &fakeCheckerGitHubCli{}
@@ -108,6 +110,9 @@ func TestCheck_OK(t *testing.T) {
 		if opt.HeadSHA != sha {
 			t.Errorf("CreateCheckRunOptions.HeadSHA = %q, want %q", opt.HeadSHA, sha)
 		}
+		if *opt.Conclusion != conclusion {
+			t.Errorf("CreateCheckRunOptions.Conclusion = %q, want %q", *opt.Conclusion, conclusion)
+		}
 		annotations := opt.Output.Annotations
 		wantAnnotaions := []*github.CheckRunAnnotation{
 			{
diff --git a/project/conf_test.go b/project/conf_test.go
index 695d5658..647fd5eb 100644
--- a/project/conf_test.go
+++ b/project/conf_test.go
@@ -13,15 +13,18 @@ func TestParse(t *testing.T) {
 runner:
   golint:
     cmd: golint ./...
+    level: info
     errorformat:
       - "%f:%l:%c: %m"
   govet:
     cmd: go tool vet -all -shadowstrict .
     format: govet
+    level: warning
   namekey:
     cmd: echo 'name'
     name: nameoverwritten
     format: checkstyle
+    level: error
 `
 
 	want := &Config{
@@ -30,16 +33,19 @@ runner:
 				Cmd:         "golint ./...",
 				Errorformat: []string{`%f:%l:%c: %m`},
 				Name:        "golint",
+				Level:       "info",
 			},
 			"govet": {
 				Cmd:    "go tool vet -all -shadowstrict .",
 				Format: "govet",
 				Name:   "govet",
+				Level:  "warning",
 			},
 			"namekey": {
 				Cmd:    "echo 'name'",
 				Format: "checkstyle",
 				Name:   "nameoverwritten",
+				Level:  "error",
 			},
 		},
 	}