Repository: srvaroa/labeler (go)

Issue:
Add support for issues
This action is awesome! It works perfectly for PRs but I've noticed it doesn't work for issues. Investigating how to support this for issues as well and will try to raise a PR.

Pull request:
Support issues
Fixes #54

Patch:
diff --git a/Makefile b/Makefile
index 2e6e388..96e877b 100644
--- a/Makefile
+++ b/Makefile
@@ -22,4 +22,4 @@ build: $(GO_DEPENDENCIES)
 	CGO_ENABLED=$(CGO_ENABLED) $(GO) build $(BUILDTAGS) $(BUILDFLAGS) -o action cmd/action.go
 
 test:
-	DISABLE_SSO=true CGO_ENABLED=$(CGO_ENABLED) $(GO) test -coverprofile=coverage.out $(PACKAGE_DIRS)
+	DISABLE_SSO=true CGO_ENABLED=$(CGO_ENABLED) $(GO) test -count 1 -coverprofile=coverage.out $(PACKAGE_DIRS)
diff --git a/README.md b/README.md
index 473eb85..584ec7e 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,37 @@
-# Configurable Pull Request labels based on conditions
+# Configurable labels based on conditions for pull requests and Issues
 
 [![labeler release (latest SemVer)](https://img.shields.io/github/v/release/srvaroa/labeler?sort=semver)](https://github.com/srvaroa/labeler/releases)  
 
-Implements a [GitHub
+Implements an all-in-one [GitHub
 Action](https://help.github.com/en/categories/automating-your-workflow-with-github-actions)
-that labels Pull Requests based on configurable conditions.
+that can manage multiple labels for both pull requests and Issues using
+configurable matching rules.
 
-It is inspired by the example [Pull Request
-Labeller](https://github.com/actions/labeler), but intends to provide a
-richer set of options.
+## Updates
+
+The action will strive to maintain backwards compatibility with older
+configuration versions. It is nevertheless encouraged to update your
+configuration files to benefit from newer features. Please follow our
+[releases](https://github.com/srvaroa/labeler/releases) page to stay up
+to date.
 
 ## Installing
 
-Add a file `.github/workflows/main.yml` to your repository with these
-contents:
+The action is configured by adding a file `.github/labeler.yml`. The
+file contains matching rules expanded in the `Configuration` section
+below.
+
+### How to trigger action
+
+To trigger the action on events, add a file `.github/workflows/main.yml`
+to your repository with these contents:
 
 ```yaml
 name: Label PRs
 
 on:
 - pull_request
+- issues
 
 jobs:
   build:
@@ -32,25 +44,46 @@ jobs:
         GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
 ```
 
-Then add a new file `.github/labeler.yml` with the configuration as
-described below in the `Configuration` section.
+Use the [`on`
+clause](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows)
+to control when to run it.
+
+* To trigger on PR events, [use
+  `pull_request`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request).
+  to trigger on PR events and run on the merge commit of the PR. Use
+  [`pull_request_target`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target)
+  instead if you prefer to run on the base.
+* To trigger on issue events, add [`issues`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issues).
 
-If you want to run the action on the base of the pull request, rather
-than on the merge commit, you should trigger the action on
-`pull_request_target`.  Check the [GitHub
-documentation](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request_target)
-for more details.
+You may combine multiple event triggers.
+
+A final option is to trigger the action periodically using the
+[`schedule`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule)
+trigger. For backwards compatibility reasons this will examine all
+active pull requests and update their labels. If you wish to examine
+issues as well, you'll need to explicitly add the `issues` flag in your
+config file:
+
+```yaml
+version: 1
+issues: True
+labels:
+- label: "WIP"
+  title: "^WIP:.*"
+```
+
+## Troubleshooting
 
 This action will avoid failing in all cases, so if you're experiencing
-unexpected behaviour it's worth looking at execution logs just in case.
-Typical errors are:
+unexpected behaviour it's worth looking at execution logs.  Typical
+errors are:
 
-* The configuration file is non existent, or has invalid yaml.
+* The configuration file is non existent, or invalid yaml.
 * Running the action from a fork, as the `GITHUB_TOKEN` has not enough
   permissions to label the main repository ([issue for
   solving this](https://github.com/srvaroa/labeler/issues/3))
 
-## Configuration
+## Configuring matching rules
 
 Configuration can be stored at `.github/labeler.yml` as a plain list of
 label matchers, which consist of a label and a set of conditions for
@@ -58,10 +91,10 @@ each.  When *all* conditions for a label match, then the Action will set
 the given label.  When *any* condition for a label does not match, then
 the Action will unset the given label.
 
-Here is an example of a matcher for label "Example":
+All matchers follow this configuration pattern:
 
 ```yaml
-<label>: "Example"
+<label>: "MyLabel"
 <condition_name>: <condition_parameters>
 <condition_name>: <condition_parameters>
 ```
@@ -76,9 +109,9 @@ labels:
   title: "^WIP:.*"
 ```
 
-A Pull Request with title "WIP: this is work in progress" would be labelled as
-`WIP`.  If the Pull Request title changes to "This is done", then the `WIP`
-label would be removed.
+A PR or issue with title "WIP: this is work in progress" would be
+labelled as `WIP`.  If the title changes to "This is done", then the
+`WIP` label would be removed.
 
 Each label may combine multiple conditions.  The action combines all
 conditions with an AND operation.  That is, the label will be applied if
@@ -94,9 +127,9 @@ labels:
   mergeable: false
 ```
 
-A Pull Request with title "WIP: this is work in progress" *and* not in a
-mergeable state would be labelled as `WIP`.  If the Pull Request title changes
-to "This is done", or it becomes mergeable, then the `WIP` label would be
+A pull request with title "WIP: this is work in progress" *and* not in a
+mergeable state would be labelled as `WIP`.  If the title changes to
+"This is done", or it becomes mergeable, then the `WIP` label would be
 removed.
 
 If you wish to apply an OR, you may set multiple matchers for the same
@@ -127,11 +160,11 @@ labels:
   title: "^WIP:.*"
 ```
 
-A PR with title 'WIP: my feature' will get the `WIP` label.
+A PR or issue with title 'WIP: my feature' will get the `WIP` label.
 
-Now the title changes to `My feature`. Since the labeler configuration
-includes the `WIP` label, and its rule does not match anymore, the label
-will get removed.
+Now the title changes to `My feature` the label will get remove. This is
+because the labeler configuration includes the `WIP` label, and its rule
+does not match anymore.
 
 In some cases you would prefer that the action adds labels, but never
 removes them regardless of the matching status. To achieve this you can
@@ -154,52 +187,58 @@ With this config, the behaviour changes:
 
 ## Conditions
 
-Below are the conditions currently supported in label matchers. All conditions
-evaluate only when they are explicitly added in configuration (that is, there
-are no default values).
+Below are the conditions currently supported in label matchers. Note
+that some conditions are only applicable to pull requests.
+
+All conditions evaluate only when they are explicitly added in
+configuration (that is, there are no default values).
 
-### Regex on title
+### Title
 
-This condition is satisfied when the PR title matches on the given regex.
+This condition is satisfied when the title matches on the given regex.
 
 ```yaml
 title: "^WIP:.*"
 ```
 
-### Regex on branch
+### Branch (PRs only)
 
-This condition is satisfied when the PR branch matches on the given regex.
+This condition is satisfied when the PR branch matches on the given
+regex.
 
 ```yaml
 branch: "^feature/.*"
 ```
 
-### Regex on base branch
+### Base branch (PRs only)
 
-This condition is satisfied when the PR base branch matches on the given regex.
+This condition is satisfied when the PR base branch matches on the given
+regex.
 
 ```yaml
 base-branch: "master"
 ```
 
-### Regex on PR body 
+### Body (PRs and Issues)
 
-This condition is satisfied when the body (description) matches on the given regex.
+This condition is satisfied when the body (description) matches on the
+given regex.
 
 ``` yaml
 body: "^patch.*"
 ```
 
-### Regex on PR files
+### Files affected (PRs only)
 
-This condition is satisfied when any of the PR files matches on the given regexs.
+This condition is satisfied when any of the PR files matches on the
+given regexs.
 
 ```yaml
 files: 
 - "cmd/.*_tests.go"
 ```
 
-### Draft status
+### Draft status (PRs only)
 
 This condition is satisfied when the PR [draft
 state](https://developer.github.com/v3/pulls/#response-1) matches that of the
@@ -217,11 +256,11 @@ draft: false
 
 Matches if the PR is not a draft.
 
-### Mergeable status
+### Mergeable status (PRs only)
 
 This condition is satisfied when the [mergeable
-state](https://developer.github.com/v3/pulls/#response-1) matches that of the
-PR. 
+state](https://developer.github.com/v3/pulls/#response-1) matches that
+of the PR. 
 
 ```yaml
 mergeable: true
@@ -235,15 +274,16 @@ mergeable: false
 
 Will match if the label is not mergeable. 
 
-### Match to PR Author
+### Authors (PRs and Issues)
 
-This condition is satisfied when the PR author matches any of the given usernames.
+This condition is satisfied when the author of the PR or Issue matches
+any of the given usernames.
 
 ```yaml
-author: "serubin"
+authors: ["serubin"]
 ```
 
-### PR size
+### Size (PRs only)
 
 This condition is satisfied when the total number of changed lines in
 the PR is within given thresholds.
diff --git a/cmd/action.go b/cmd/action.go
index 223c0d5..0f607d1 100644
--- a/cmd/action.go
+++ b/cmd/action.go
@@ -49,24 +49,15 @@ func main() {
 
 	if eventName == "schedule" {
 		t := strings.Split(os.Getenv("GITHUB_REPOSITORY"), "/")
-		owner, repoName := t[0], t[1]
-
-		prs, _, err := gh.PullRequests.List(context.Background(), owner, repoName, &github.PullRequestListOptions{})
-		if err != nil {
-			return
-		}
-
-		for _, pr := range prs {
-			err = l.ExecuteOn(pr)
-			log.Printf("Unable to execute action: %+v", err)
-		}
+		owner, repo := t[0], t[1]
+		l.ProcessAllPRs(owner, repo)
+		l.ProcessAllIssues(owner, repo)
 	} else {
 		err = l.HandleEvent(eventName, eventPayload)
 		if err != nil {
 			log.Printf("Unable to execute action: %+v", err)
 		}
 	}
-
 }
 
 func getRepoFile(gh *github.Client, repo, file, sha string) (*[]byte, error) {
@@ -163,21 +154,21 @@ func getEventPayload() *[]byte {
 func newLabeler(gh *github.Client, config *labeler.LabelerConfigV1) *labeler.Labeler {
 	l := labeler.Labeler{
 
-		FetchRepoConfig: func(owner string, repoName string) (*labeler.LabelerConfigV1, error) {
+		FetchRepoConfig: func() (*labeler.LabelerConfigV1, error) {
 			return config, nil
 		},
 
-		ReplaceLabelsForPr: func(owner string, repoName string, prNumber int, labels []string) error {
-			log.Printf("Setting labels to %s/%s#%d: %s", owner, repoName, prNumber, labels)
+		ReplaceLabels: func(target *labeler.Target, labels []string) error {
+			log.Printf("Setting labels to %s/%s#%d: %s", target.Owner, target.RepoName, target.IssueNo, labels)
 			_, _, err := gh.Issues.ReplaceLabelsForIssue(
-				context.Background(), owner, repoName, prNumber, labels)
+				context.Background(), target.Owner, target.RepoName, target.IssueNo, labels)
 			return err
 		},
 
-		GetCurrentLabels: func(owner string, repoName string, prNumber int) ([]string, error) {
+		GetCurrentLabels: func(target *labeler.Target) ([]string, error) {
 			opts := github.ListOptions{} // TODO: ignoring pagination here
 			currLabels, _, err := gh.Issues.ListLabelsByIssue(
-				context.Background(), owner, repoName, prNumber, &opts)
+				context.Background(), target.Owner, target.RepoName, target.IssueNo, &opts)
 
 			labels := []string{}
 			for _, label := range currLabels {
@@ -185,7 +176,7 @@ func newLabeler(gh *github.Client, config *labeler.LabelerConfigV1) *labeler.Lab
 			}
 			return labels, err
 		},
-
+		GitHub: gh,
 		Client: labeler.NewDefaultHttpClient(),
 	}
 	return &l
diff --git a/pkg/condition_author.go b/pkg/condition_author.go
index ffd71db..645fd4f 100644
--- a/pkg/condition_author.go
+++ b/pkg/condition_author.go
@@ -4,8 +4,6 @@ import (
 	"fmt"
 	"log"
 	"strings"
-
-	gh "github.com/google/go-github/v35/github"
 )
 
 func NewAuthorCondition() Condition {
@@ -13,16 +11,17 @@ func NewAuthorCondition() Condition {
 		GetName: func() string {
 			return "Author matches"
 		},
-		Evaluate: func(pr *gh.PullRequest, matcher LabelMatcher) (bool, error) {
+		CanEvaluate: func(target *Target) bool {
+			return true
+		},
+		Evaluate: func(target *Target, matcher LabelMatcher) (bool, error) {
 			if len(matcher.Authors) <= 0 {
 				return false, fmt.Errorf("Users are not set in config")
 			}
 
-			prAuthor := pr.GetUser().Login
-
-			log.Printf("Matching `%s` against: `%v`", matcher.Authors, *prAuthor)
+			log.Printf("Matching `%s` against: `%v`", matcher.Authors, target.Author)
 			for _, author := range matcher.Authors {
-				if strings.ToLower(author) == strings.ToLower(*prAuthor) {
+				if strings.ToLower(author) == strings.ToLower(target.Author) {
 					return true, nil
 				}
 			}
diff --git a/pkg/condition_basebranch.go b/pkg/condition_basebranch.go
index e331e86..3609d8c 100644
--- a/pkg/condition_basebranch.go
+++ b/pkg/condition_basebranch.go
@@ -4,8 +4,6 @@ import (
 	"fmt"
 	"log"
 	"regexp"
-
-	gh "github.com/google/go-github/v35/github"
 )
 
 func NewBaseBranchCondition() Condition {
@@ -13,11 +11,14 @@ func NewBaseBranchCondition() Condition {
 		GetName: func() string {
 			return "Base branch matches regex"
 		},
-		Evaluate: func(pr *gh.PullRequest, matcher LabelMatcher) (bool, error) {
+		CanEvaluate: func(target *Target) bool {
+			return target.ghPR != nil
+		},
+		Evaluate: func(target *Target, matcher LabelMatcher) (bool, error) {
 			if len(matcher.BaseBranch) <= 0 {
 				return false, fmt.Errorf("branch is not set in config")
 			}
-			prBranchName := pr.Base.GetRef()
+			prBranchName := target.ghPR.Base.GetRef()
 			log.Printf("Matching `%s` against: `%s`", matcher.Branch, prBranchName)
 			isMatched, _ := regexp.Match(matcher.BaseBranch, []byte(prBranchName))
 			return isMatched, nil
diff --git a/pkg/conditions_body.go b/pkg/condition_body.go
similarity index 52%
rename from pkg/conditions_body.go
rename to pkg/condition_body.go
index 338fe9f..44f0414 100644
--- a/pkg/conditions_body.go
+++ b/pkg/condition_body.go
@@ -4,8 +4,6 @@ import (
 	"fmt"
 	"log"
 	"regexp"
-
-	gh "github.com/google/go-github/v35/github"
 )
 
 func NewBodyCondition() Condition {
@@ -13,12 +11,15 @@ func NewBodyCondition() Condition {
 		GetName: func() string {
 			return "Body matches regex"
 		},
-		Evaluate: func(pr *gh.PullRequest, matcher LabelMatcher) (bool, error) {
+		CanEvaluate: func(target *Target) bool {
+			return true
+		},
+		Evaluate: func(target *Target, matcher LabelMatcher) (bool, error) {
 			if len(matcher.Body) <= 0 {
 				return false, fmt.Errorf("body is not set in config")
 			}
-			log.Printf("Matching `%s` against: `%s`", matcher.Body, pr.GetBody())
-			isMatched, _ := regexp.Match(matcher.Body, []byte(pr.GetBody()))
+			log.Printf("Matching `%s` against: `%s`", matcher.Body, target.Body)
+			isMatched, _ := regexp.Match(matcher.Body, []byte(target.Body))
 			return isMatched, nil
 		},
 	}
diff --git a/pkg/condition_branch.go b/pkg/condition_branch.go
index 37e1c31..c992a71 100644
--- a/pkg/condition_branch.go
+++ b/pkg/condition_branch.go
@@ -4,8 +4,6 @@ import (
 	"fmt"
 	"log"
 	"regexp"
-
-	gh "github.com/google/go-github/v35/github"
 )
 
 func NewBranchCondition() Condition {
@@ -13,11 +11,14 @@ func NewBranchCondition() Condition {
 		GetName: func() string {
 			return "Branch matches regex"
 		},
-		Evaluate: func(pr *gh.PullRequest, matcher LabelMatcher) (bool, error) {
+		CanEvaluate: func(target *Target) bool {
+			return target.ghPR != nil
+		},
+		Evaluate: func(target *Target, matcher LabelMatcher) (bool, error) {
 			if len(matcher.Branch) <= 0 {
 				return false, fmt.Errorf("branch is not set in config")
 			}
-			prBranchName := pr.Head.GetRef()
+			prBranchName := target.ghPR.Head.GetRef()
 			log.Printf("Matching `%s` against: `%s`", matcher.Branch, prBranchName)
 			isMatched, _ := regexp.Match(matcher.Branch, []byte(prBranchName))
 			return isMatched, nil
diff --git a/pkg/condition_files.go b/pkg/condition_files.go
index 38f72cc..e529b14 100644
--- a/pkg/condition_files.go
+++ b/pkg/condition_files.go
@@ -20,14 +20,18 @@ func NewFilesCondition(l *Labeler) Condition {
 		GetName: func() string {
 			return "File matches regex"
 		},
-		Evaluate: func(pr *gh.PullRequest, matcher LabelMatcher) (bool, error) {
+		CanEvaluate: func(target *Target) bool {
+			return target.ghPR != nil
+		},
+		Evaluate: func(target *Target, matcher LabelMatcher) (bool, error) {
+
 			if len(matcher.Files) <= 0 {
 				return false, fmt.Errorf("Files are not set in config")
 			}
 
 			if len(prFiles) == 0 {
 				var err error
-				prFiles, err = l.getPrFileNames(pr)
+				prFiles, err = l.getPrFileNames(target.ghPR)
 				if err != nil {
 					return false, err
 				}
diff --git a/pkg/condition_isdraft.go b/pkg/condition_isdraft.go
index 2fa5cf5..f6e208b 100644
--- a/pkg/condition_isdraft.go
+++ b/pkg/condition_isdraft.go
@@ -3,8 +3,6 @@ package labeler
 import (
 	"fmt"
 	"strconv"
-
-	gh "github.com/google/go-github/v35/github"
 )
 
 func NewIsDraftCondition() Condition {
@@ -12,15 +10,18 @@ func NewIsDraftCondition() Condition {
 		GetName: func() string {
 			return "Pull Request is draft"
 		},
-		Evaluate: func(pr *gh.PullRequest, matcher LabelMatcher) (bool, error) {
+		CanEvaluate: func(target *Target) bool {
+			return target.ghPR != nil
+		},
+		Evaluate: func(target *Target, matcher LabelMatcher) (bool, error) {
 			b, err := strconv.ParseBool(matcher.Draft)
 			if err != nil {
 				return false, fmt.Errorf("draft is not set in config")
 			}
 			if b {
-				return pr.GetDraft(), nil
+				return target.ghPR.GetDraft(), nil
 			}
-			return !pr.GetDraft(), nil
+			return !target.ghPR.GetDraft(), nil
 		},
 	}
 }
diff --git a/pkg/condition_ismergeable.go b/pkg/condition_ismergeable.go
index a9540c1..cc35c88 100644
--- a/pkg/condition_ismergeable.go
+++ b/pkg/condition_ismergeable.go
@@ -3,8 +3,6 @@ package labeler
 import (
 	"fmt"
 	"strconv"
-
-	gh "github.com/google/go-github/v35/github"
 )
 
 func NewIsMergeableCondition() Condition {
@@ -12,15 +10,18 @@ func NewIsMergeableCondition() Condition {
 		GetName: func() string {
 			return "Pull Request is mergeable"
 		},
-		Evaluate: func(pr *gh.PullRequest, matcher LabelMatcher) (bool, error) {
+		CanEvaluate: func(target *Target) bool {
+			return target.ghPR != nil
+		},
+		Evaluate: func(target *Target, matcher LabelMatcher) (bool, error) {
 			b, err := strconv.ParseBool(matcher.Mergeable)
 			if err != nil {
 				return false, fmt.Errorf("mergeable is not set in config")
 			}
 			if b {
-				return pr.GetMergeable(), nil
+				return target.ghPR.GetMergeable(), nil
 			}
-			return !pr.GetMergeable(), nil
+			return !target.ghPR.GetMergeable(), nil
 		},
 	}
 }
diff --git a/pkg/condition_size.go b/pkg/condition_size.go
index 78b8d74..a58bdba 100644
--- a/pkg/condition_size.go
+++ b/pkg/condition_size.go
@@ -5,8 +5,6 @@ import (
 	"log"
 	"math"
 	"strconv"
-
-	gh "github.com/google/go-github/v35/github"
 )
 
 func NewSizeCondition() Condition {
@@ -14,7 +12,10 @@ func NewSizeCondition() Condition {
 		GetName: func() string {
 			return "Pull Request contains a number of changes"
 		},
-		Evaluate: func(pr *gh.PullRequest, matcher LabelMatcher) (bool, error) {
+		CanEvaluate: func(target *Target) bool {
+			return target.ghPR != nil
+		},
+		Evaluate: func(target *Target, matcher LabelMatcher) (bool, error) {
 			if len(matcher.SizeBelow) == 0 && len(matcher.SizeAbove) == 0 {
 				return false, fmt.Errorf("size-above and size-below are not set in config")
 			}
@@ -28,7 +29,7 @@ func NewSizeCondition() Condition {
 				lowerBound = 0
 				log.Printf("Lower boundary set to 0 (config has invalid or empty value)")
 			}
-			totalChanges := int64(math.Abs(float64(pr.GetAdditions() + pr.GetDeletions())))
+			totalChanges := int64(math.Abs(float64(target.ghPR.GetAdditions() + target.ghPR.GetDeletions())))
 			log.Printf("Matching %d changes in PR against bounds: (%d, %d)", totalChanges, lowerBound, upperBound)
 			isWithinBounds := totalChanges > lowerBound && totalChanges < upperBound
 			return isWithinBounds, nil
diff --git a/pkg/condition_title.go b/pkg/condition_title.go
index 59929c6..99dc9c7 100644
--- a/pkg/condition_title.go
+++ b/pkg/condition_title.go
@@ -4,8 +4,6 @@ import (
 	"fmt"
 	"log"
 	"regexp"
-
-	gh "github.com/google/go-github/v35/github"
 )
 
 func NewTitleCondition() Condition {
@@ -13,12 +11,15 @@ func NewTitleCondition() Condition {
 		GetName: func() string {
 			return "Title matches regex"
 		},
-		Evaluate: func(pr *gh.PullRequest, matcher LabelMatcher) (bool, error) {
+		CanEvaluate: func(target *Target) bool {
+			return true
+		},
+		Evaluate: func(target *Target, matcher LabelMatcher) (bool, error) {
 			if len(matcher.Title) <= 0 {
 				return false, fmt.Errorf("title is not set in config")
 			}
-			log.Printf("Matching `%s` against: `%s`", matcher.Title, pr.GetTitle())
-			isMatched, _ := regexp.Match(matcher.Title, []byte(pr.GetTitle()))
+			log.Printf("Matching `%s` against: `%s`", matcher.Title, target.Title)
+			isMatched, _ := regexp.Match(matcher.Title, []byte(target.Title))
 			return isMatched, nil
 		},
 	}
diff --git a/pkg/labeler.go b/pkg/labeler.go
index e3acdfc..97033ef 100644
--- a/pkg/labeler.go
+++ b/pkg/labeler.go
@@ -1,7 +1,9 @@
 package labeler
 
 import (
+	"context"
 	"log"
+	"strings"
 
 	gh "github.com/google/go-github/v35/github"
 )
@@ -24,7 +26,10 @@ type LabelerConfigV0 map[string]LabelMatcher
 
 type LabelerConfigV1 struct {
 	Version int32
-	// when set to true, we will only add labels when they match a rule
+	// When set to true, scheduled executions will process both PRs and
+	// issues. Else, we will only process PRs. Defaults to "False"
+	Issues bool
+	// When set to true, we will only add labels when they match a rule
 	// but it will NOT remove labels that were previously set and stop
 	// matching a rule
 	AppendOnly bool
@@ -37,15 +42,28 @@ type LabelUpdates struct {
 }
 
 type Labeler struct {
-	FetchRepoConfig    func(owner string, repoName string) (*LabelerConfigV1, error)
-	ReplaceLabelsForPr func(owner string, repoName string, prNumber int, labels []string) error
-	GetCurrentLabels   func(owner string, repoName string, prNumber int) ([]string, error)
-	Client             HttpClient
+	FetchRepoConfig  func() (*LabelerConfigV1, error)
+	ReplaceLabels    func(target *Target, labels []string) error
+	GetCurrentLabels func(target *Target) ([]string, error)
+	GitHub           *gh.Client
+	Client           HttpClient
 }
 
 type Condition struct {
-	Evaluate func(pr *gh.PullRequest, matcher LabelMatcher) (bool, error)
-	GetName  func() string
+	CanEvaluate func(target *Target) bool
+	Evaluate    func(target *Target, matcher LabelMatcher) (bool, error)
+	GetName     func() string
+}
+
+type Target struct {
+	Author   string
+	Body     string
+	IssueNo  int
+	Title    string
+	Owner    string
+	RepoName string
+	ghPR     *gh.PullRequest
+	ghIssue  *gh.Issue
 }
 
 // HandleEvent takes a GitHub Event and its raw payload (see link below)
@@ -62,25 +80,62 @@ func (l *Labeler) HandleEvent(
 	}
 	switch event := event.(type) {
 	case *gh.PullRequestEvent:
-		err = l.ExecuteOn(event.PullRequest)
+		err = l.ExecuteOn(wrapPrAsTarget(event.PullRequest))
 	case *gh.PullRequestTargetEvent:
-		err = l.ExecuteOn(event.PullRequest)
+		err = l.ExecuteOn(wrapPrAsTarget(event.PullRequest))
+	case *gh.IssuesEvent:
+		err = l.ExecuteOn(wrapIssueAsTarget(event.Issue))
+	default:
+		log.Printf("Event type is not supported, please review your workflow config")
 	}
 	return err
 }
 
-func (l *Labeler) ExecuteOn(pr *gh.PullRequest) error {
-	owner := pr.Base.Repo.GetOwner().GetLogin()
-	repoName := *pr.Base.Repo.Name
+func wrapPrAsTarget(pr *gh.PullRequest) *Target {
+	return &Target{
+		Author:   *pr.GetUser().Login,
+		Body:     pr.GetBody(),
+		IssueNo:  *pr.Number,
+		Title:    pr.GetTitle(),
+		Owner:    pr.Base.Repo.GetOwner().GetLogin(),
+		RepoName: *pr.Base.Repo.Name,
+		ghPR:     pr,
+		ghIssue:  nil,
+	}
+}
+
+func wrapIssueAsTarget(issue *gh.Issue) *Target {
+
+	// TODO: go-github@v50 has a Repository property that
+	// avoids this.
+	repoUrlSplit := strings.Split(*issue.RepositoryURL, "/")
+	repoName := repoUrlSplit[len(repoUrlSplit)-1]
+	owner := repoUrlSplit[len(repoUrlSplit)-2]
+
+	return &Target{
+		Author:   *issue.GetUser().Login,
+		Body:     issue.GetBody(),
+		IssueNo:  *issue.Number,
+		Title:    issue.GetTitle(),
+		Owner:    owner,
+		RepoName: repoName,
+		ghPR:     nil,
+		ghIssue:  issue,
+	}
+}
+
+func (l *Labeler) ExecuteOn(target *Target) error {
+
+	log.Printf("Matching labels on target %+v", target)
 
-	config, err := l.FetchRepoConfig(owner, repoName)
+	config, err := l.FetchRepoConfig()
 
-	labelUpdates, err := l.findMatches(pr, config)
+	labelUpdates, err := l.findMatches(target, config)
 	if err != nil {
 		return err
 	}
 
-	currLabels, err := l.GetCurrentLabels(owner, repoName, *pr.Number)
+	currLabels, err := l.GetCurrentLabels(target)
 	if err != nil {
 		return err
 	}
@@ -115,13 +170,13 @@ func (l *Labeler) ExecuteOn(pr *gh.PullRequest) error {
 			desiredLabels = append(desiredLabels, k)
 		}
 	}
-	log.Printf("Desired labels: %s", desiredLabels)
+	log.Printf("Final set of labels: `%q`", desiredLabels)
 
-	return l.ReplaceLabelsForPr(owner, repoName, *pr.Number, desiredLabels)
+	return l.ReplaceLabels(target, desiredLabels)
 }
 
-// findMatches returns all updates to be made to labels for the given PR
-func (l *Labeler) findMatches(pr *gh.PullRequest, config *LabelerConfigV1) (LabelUpdates, error) {
+// findMatches returns all updates to be made to labels for the given target
+func (l *Labeler) findMatches(target *Target, config *LabelerConfigV1) (LabelUpdates, error) {
 
 	labelUpdates := LabelUpdates{
 		set: map[string]bool{},
@@ -156,11 +211,16 @@ func (l *Labeler) findMatches(pr *gh.PullRequest, config *LabelerConfigV1) (Labe
 		delete(labelUpdates.set, label)
 
 		for _, c := range conditions {
-			isMatched, err := c.Evaluate(pr, matcher)
+			if !c.CanEvaluate(target) {
+				log.Printf("[%s] skip, event not supported by condition", c.GetName())
+				continue
+			}
+			isMatched, err := c.Evaluate(target, matcher)
 			if err != nil {
-				log.Printf("%s: condition %s skipped (%s)", label, c.GetName(), err)
+				log.Printf("[%s] skip, %s", c.GetName(), err)
 				continue
 			}
+			log.Printf("[%s] yields %t", c.GetName(), isMatched)
 
 			prev, ok := labelUpdates.set[label]
 			if ok { // Other conditions were evaluated for the label
@@ -169,7 +229,6 @@ func (l *Labeler) findMatches(pr *gh.PullRequest, config *LabelerConfigV1) (Labe
 				labelUpdates.set[label] = isMatched
 			}
 
-			log.Printf("%s: condition %s yields %t", label, c.GetName(), isMatched)
 			if isMatched {
 				continue
 			}
@@ -178,3 +237,51 @@ func (l *Labeler) findMatches(pr *gh.PullRequest, config *LabelerConfigV1) (Labe
 
 	return labelUpdates, nil
 }
+
+func (l *Labeler) ProcessAllIssues(owner, repo string) {
+
+	config, err := l.FetchRepoConfig()
+	if err != nil {
+		log.Printf("Unable to load configuration %+v", err)
+		return
+	}
+
+	if !config.Issues {
+		log.Println("Issues must be explicitly enabled in order to " +
+			"process issues in the scheduled execution mode")
+	}
+
+	issues, _, err := l.GitHub.Issues.ListByRepo(
+		context.Background(),
+		owner, repo,
+		&gh.IssueListByRepoOptions{})
+
+	if err != nil {
+		log.Printf("Unable to list issues in %s/%s: %+v", owner, repo, err)
+		return
+	}
+
+	for _, pr := range issues {
+		err = l.ExecuteOn(wrapIssueAsTarget(pr))
+		log.Printf("Unable to execute action: %+v", err)
+	}
+}
+
+func (l *Labeler) ProcessAllPRs(owner, repo string) {
+
+	prs, _, err := l.GitHub.PullRequests.List(
+		context.Background(),
+		owner, repo,
+		&gh.PullRequestListOptions{})
+
+	if err != nil {
+		log.Printf("Unable to list pull requests in %s/%s: %+v", owner, repo, err)
+		return
+	}
+
+	for _, pr := range prs {
+		err = l.ExecuteOn(wrapPrAsTarget(pr))
+		log.Printf("Unable to execute action: %+v", err)
+	}
+
+}


Test patch:
diff --git a/pkg/labeler_test.go b/pkg/labeler_test.go
index f124ba9..10be9b3 100644
--- a/pkg/labeler_test.go
+++ b/pkg/labeler_test.go
@@ -21,6 +21,7 @@ func loadPayload(name string) ([]byte, error) {
 }
 
 type TestCase struct {
+	event          string // issues or pull_request
 	payloads       []string
 	name           string
 	config         LabelerConfigV1
@@ -33,6 +34,7 @@ func TestHandleEvent(t *testing.T) {
 	// These all use the payload in payload files
 	testCases := []TestCase{
 		{
+			event:          "pull_request",
 			payloads:       []string{"create_pr", "reopen_pr"},
 			name:           "Empty config",
 			config:         LabelerConfigV1{},
@@ -40,6 +42,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"Fix"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr", "reopen_pr"},
 			name:     "Config with no rules",
 			config: LabelerConfigV1{
@@ -54,6 +57,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"Fix"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr", "reopen_pr"},
 			name:     "Add a label when not set and config matches",
 			config: LabelerConfigV1{
@@ -69,6 +73,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"WIP"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr", "reopen_pr"},
 			name:     "Remove a label when set and config does not match",
 			config: LabelerConfigV1{
@@ -84,6 +89,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr", "reopen_pr"},
 			name:     "Respect a label when set, and not present in config",
 			config: LabelerConfigV1{
@@ -99,6 +105,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"SomeLabel"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr", "reopen_pr"},
 			name:     "A combination of all cases",
 			config: LabelerConfigV1{
@@ -118,6 +125,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"WIP", "ShouldRespect"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr", "reopen_pr"},
 			name:     "Add a label with two conditions, both matching",
 			config: LabelerConfigV1{
@@ -134,6 +142,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"WIP"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr", "create_draft_pr"},
 			name:     "Draft PR without explicit value is not evaluated",
 			config: LabelerConfigV1{
@@ -148,6 +157,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr"},
 			name:     "Non draft PR with explicit config is evaluated",
 			config: LabelerConfigV1{
@@ -164,6 +174,7 @@ func TestHandleEvent(t *testing.T) {
 		},
 
 		{
+			event:    "pull_request",
 			payloads: []string{"create_draft_pr"},
 			name:     "Draft PR",
 			config: LabelerConfigV1{
@@ -178,8 +189,8 @@ func TestHandleEvent(t *testing.T) {
 			initialLabels:  []string{},
 			expectedLabels: []string{"ThisIsADraft"},
 		},
-
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr", "reopen_pr"},
 			name:     "Add a label with two conditions, one not matching (1)",
 			config: LabelerConfigV1{
@@ -198,6 +209,7 @@ func TestHandleEvent(t *testing.T) {
 		{
 			// covers evaluation order making a True in the last
 			// condition, while previous ones are false
+			event:    "pull_request",
 			payloads: []string{"create_pr", "reopen_pr"},
 			name:     "Add a label with two conditions, one not matching (2)",
 			config: LabelerConfigV1{
@@ -214,6 +226,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"small_pr"},
 			name:     "Test the size_below rule",
 			config: LabelerConfigV1{
@@ -229,6 +242,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"S"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"mid_pr"},
 			name:     "Test the size_below and size_above rules",
 			config: LabelerConfigV1{
@@ -245,6 +259,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"M"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"big_pr"},
 			name:     "Test the size_above rule",
 			config: LabelerConfigV1{
@@ -260,6 +275,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"L"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"small_pr"},
 			name:     "Test the branch rule (matching)",
 			config: LabelerConfigV1{
@@ -275,6 +291,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"Branch"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"small_pr"},
 			name:     "Test the branch rule (not matching)",
 			config: LabelerConfigV1{
@@ -290,6 +307,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr"},
 			name:     "Test the base branch rule (matching)",
 			config: LabelerConfigV1{
@@ -305,6 +323,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"Branch"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr"},
 			name:     "Test the base branch rule (not matching)",
 			config: LabelerConfigV1{
@@ -320,6 +339,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr"},
 			name:     "Test the body rule (matching)",
 			config: LabelerConfigV1{
@@ -335,6 +355,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"Body"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr"},
 			name:     "Test the body rule (not matching)",
 			config: LabelerConfigV1{
@@ -350,6 +371,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"diff_pr"},
 			name:     "Test the files rule",
 			config: LabelerConfigV1{
@@ -367,6 +389,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"Files"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"small_pr"},
 			name:     "Multiple conditions for the same tag function as OR",
 			config: LabelerConfigV1{
@@ -386,6 +409,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"Branch"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"small_pr"},
 			name:     "Multiple conditions for the same tag function as OR",
 			config: LabelerConfigV1{
@@ -405,6 +429,7 @@ func TestHandleEvent(t *testing.T) {
 			expectedLabels: []string{"Author"},
 		},
 		{
+			event:    "pull_request",
 			payloads: []string{"create_pr", "reopen_pr"},
 			name:     "AppendOnly enabled forbids deletions",
 			config: LabelerConfigV1{
@@ -422,6 +447,140 @@ func TestHandleEvent(t *testing.T) {
 			// BUT because AppendOnly is set, we do not erase it
 			expectedLabels: []string{"Fix"},
 		},
+
+		// Issues
+
+		{
+			event:    "issues",
+			payloads: []string{"issue_open"},
+			name:     "Add a label to issue when title matches",
+			config: LabelerConfigV1{
+				Version: 1,
+				Labels: []LabelMatcher{
+					{
+						Label: "Test",
+						Title: "^Testy.*t",
+					},
+				},
+			},
+			initialLabels:  []string{},
+			expectedLabels: []string{"Test"},
+		},
+		{
+			event:    "issues",
+			payloads: []string{"issue_open"},
+			name:     "Remove a label from issue when title does not match",
+			config: LabelerConfigV1{
+				Version: 1,
+				Labels: []LabelMatcher{
+					{
+						Label: "Test",
+						Title: "Wontmatch",
+					},
+				},
+			},
+			initialLabels:  []string{"Test", "Meh"},
+			expectedLabels: []string{"Meh"},
+		},
+		{
+			event:    "issues",
+			payloads: []string{"issue_open"},
+			name:     "Add label to issue when author matches",
+			config: LabelerConfigV1{
+				Version: 1,
+				Labels: []LabelMatcher{
+					{
+						Label:   "Test",
+						Authors: []string{"spiderman", "srvaroa"},
+					},
+				},
+			},
+			initialLabels:  []string{},
+			expectedLabels: []string{"Test"},
+		},
+		{
+			event:    "issues",
+			payloads: []string{"issue_open"},
+			name:     "Remove label from issue when author does not match",
+			config: LabelerConfigV1{
+				Version: 1,
+				Labels: []LabelMatcher{
+					{
+						Label:   "Test",
+						Authors: []string{"spiderman"},
+					},
+				},
+			},
+			initialLabels:  []string{"Test", "Meh"},
+			expectedLabels: []string{"Meh"},
+		},
+		{
+			event:    "issues",
+			payloads: []string{"issue_open"},
+			name:     "Add label to issue when body matches",
+			config: LabelerConfigV1{
+				Version: 1,
+				Labels: []LabelMatcher{
+					{
+						Label: "Test",
+						Body:  ".+ descr.+on!$",
+					},
+				},
+			},
+			initialLabels:  []string{},
+			expectedLabels: []string{"Test"},
+		},
+		{
+			event:    "issues",
+			payloads: []string{"issue_open"},
+			name:     "Remove label from issue when body does not match",
+			config: LabelerConfigV1{
+				Version: 1,
+				Labels: []LabelMatcher{
+					{
+						Label: "Test",
+						Body:  "will_not_match",
+					},
+				},
+			},
+			initialLabels:  []string{"Test", "Meh"},
+			expectedLabels: []string{"Meh"},
+		},
+		{
+			event:    "issues",
+			payloads: []string{"issue_open"},
+			name:     "Leave tags untouched when rule not supported in issues",
+			config: LabelerConfigV1{
+				Version: 1,
+				Labels: []LabelMatcher{
+					{
+						Label:     "ShouldNotAppear1",
+						Mergeable: "False",
+					},
+					{
+						Label:     "ShouldNotAppear2",
+						SizeBelow: "10",
+						SizeAbove: "0",
+					},
+					{
+						Label: "ShouldNotAppear3",
+						Files: []string{
+							"^.*.md",
+						},
+					},
+					{
+						Label:  "ShouldNotAppear4",
+						Branch: "master",
+					},
+					{
+						Label:      "ShouldNotAppear5",
+						BaseBranch: "master",
+					},
+				},
+			},
+			initialLabels:  []string{"Test", "WIP"},
+			expectedLabels: []string{"Test", "WIP"},
+		},
 	}
 
 	for _, tc := range testCases {
@@ -429,13 +588,15 @@ func TestHandleEvent(t *testing.T) {
 			for _, file := range tc.payloads {
 				payload, err := loadPayload(file)
 				if err != nil {
+					fmt.Printf("Test `%s`: failed to load %s\n", tc.name, file)
 					t.Fatal(err)
 				}
 
-				fmt.Println(tc.name)
+				fmt.Printf("--> TEST: %s \n", tc.name)
 				l := NewTestLabeler(t, tc)
-				err = l.HandleEvent("pull_request", &payload)
+				err = l.HandleEvent(tc.event, &payload)
 				if err != nil {
+					fmt.Printf("Test failed: %s\n", tc.name)
 					t.Fatal(err)
 				}
 			}
@@ -445,13 +606,13 @@ func TestHandleEvent(t *testing.T) {
 
 func NewTestLabeler(t *testing.T, tc TestCase) Labeler {
 	return Labeler{
-		FetchRepoConfig: func(owner, repoName string) (*LabelerConfigV1, error) {
+		FetchRepoConfig: func() (*LabelerConfigV1, error) {
 			return &tc.config, nil
 		},
-		GetCurrentLabels: func(owner, repoName string, prNumber int) ([]string, error) {
+		GetCurrentLabels: func(target *Target) ([]string, error) {
 			return tc.initialLabels, nil
 		},
-		ReplaceLabelsForPr: func(owner, repoName string, prNumber int, labels []string) error {
+		ReplaceLabels: func(target *Target, labels []string) error {
 			sort.Strings(tc.expectedLabels)
 			sort.Strings(labels)
 			if reflect.DeepEqual(tc.expectedLabels, labels) {
diff --git a/test_data/config_v1_issues.yml b/test_data/config_v1_issues.yml
new file mode 100644
index 0000000..923a309
--- /dev/null
+++ b/test_data/config_v1_issues.yml
@@ -0,0 +1,24 @@
+version: 1
+issues: True
+labels:
+  - label: WIP
+    branch: "wip"
+  - label: WIP
+    title: "^WIP:.*"
+  - label: WOP
+    title: "^WOP:.*"
+  - label: S
+    size-below: 10
+  - label: M
+    size-above: 9
+    size-below: 100
+  - label: L
+    size-above: 100
+  - label: "TestFileMatch"
+    files: 
+    - "cmd/.*.go"
+    - "pkg/.*.go"
+  - label: "Test"
+    authors: 
+      - "Test1"
+      - "Test2"
diff --git a/test_data/issue_open_payload b/test_data/issue_open_payload
new file mode 100644
index 0000000..de6d652
--- /dev/null
+++ b/test_data/issue_open_payload
@@ -0,0 +1,189 @@
+{
+  "action": "opened",
+  "issue": {
+    "url": "https://api.github.com/repos/srvaroa/test-repo/issues/1",
+    "repository_url": "https://api.github.com/repos/srvaroa/test-repo",
+    "labels_url": "https://api.github.com/repos/srvaroa/test-repo/issues/1/labels{/name}",
+    "comments_url": "https://api.github.com/repos/srvaroa/test-repo/issues/1/comments",
+    "events_url": "https://api.github.com/repos/srvaroa/test-repo/issues/1/events",
+    "html_url": "https://github.com/srvaroa/test-repo/issues/1",
+    "id": 1581029562,
+    "node_id": "I_kwDODfGd5c5ePJi6",
+    "number": 1,
+    "title": "Testy test",
+    "user": {
+      "login": "srvaroa",
+      "id": 346110,
+      "node_id": "MDQ6VXNlcjM0NjExMA==",
+      "avatar_url": "https://avatars.githubusercontent.com/u/346110?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/srvaroa",
+      "html_url": "https://github.com/srvaroa",
+      "followers_url": "https://api.github.com/users/srvaroa/followers",
+      "following_url": "https://api.github.com/users/srvaroa/following{/other_user}",
+      "gists_url": "https://api.github.com/users/srvaroa/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/srvaroa/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/srvaroa/subscriptions",
+      "organizations_url": "https://api.github.com/users/srvaroa/orgs",
+      "repos_url": "https://api.github.com/users/srvaroa/repos",
+      "events_url": "https://api.github.com/users/srvaroa/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/srvaroa/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "labels": [
+
+    ],
+    "state": "open",
+    "locked": false,
+    "assignee": null,
+    "assignees": [
+
+    ],
+    "milestone": null,
+    "comments": 0,
+    "created_at": "2023-02-11T21:59:41Z",
+    "updated_at": "2023-02-11T21:59:41Z",
+    "closed_at": null,
+    "author_association": "OWNER",
+    "active_lock_reason": null,
+    "body": "This is the description!",
+    "reactions": {
+      "url": "https://api.github.com/repos/srvaroa/test-repo/issues/1/reactions",
+      "total_count": 0,
+      "+1": 0,
+      "-1": 0,
+      "laugh": 0,
+      "hooray": 0,
+      "confused": 0,
+      "heart": 0,
+      "rocket": 0,
+      "eyes": 0
+    },
+    "timeline_url": "https://api.github.com/repos/srvaroa/test-repo/issues/1/timeline",
+    "performed_via_github_app": null,
+    "state_reason": null
+  },
+  "repository": {
+    "id": 233938405,
+    "node_id": "MDEwOlJlcG9zaXRvcnkyMzM5Mzg0MDU=",
+    "name": "test-repo",
+    "full_name": "srvaroa/test-repo",
+    "private": true,
+    "owner": {
+      "login": "srvaroa",
+      "id": 346110,
+      "node_id": "MDQ6VXNlcjM0NjExMA==",
+      "avatar_url": "https://avatars.githubusercontent.com/u/346110?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/srvaroa",
+      "html_url": "https://github.com/srvaroa",
+      "followers_url": "https://api.github.com/users/srvaroa/followers",
+      "following_url": "https://api.github.com/users/srvaroa/following{/other_user}",
+      "gists_url": "https://api.github.com/users/srvaroa/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/srvaroa/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/srvaroa/subscriptions",
+      "organizations_url": "https://api.github.com/users/srvaroa/orgs",
+      "repos_url": "https://api.github.com/users/srvaroa/repos",
+      "events_url": "https://api.github.com/users/srvaroa/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/srvaroa/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "html_url": "https://github.com/srvaroa/test-repo",
+    "description": null,
+    "fork": false,
+    "url": "https://api.github.com/repos/srvaroa/test-repo",
+    "forks_url": "https://api.github.com/repos/srvaroa/test-repo/forks",
+    "keys_url": "https://api.github.com/repos/srvaroa/test-repo/keys{/key_id}",
+    "collaborators_url": "https://api.github.com/repos/srvaroa/test-reptest-repo{/collaborator}",
+    "teams_url": "https://api.github.com/repos/srvaroa/test-repo/teams",
+    "hooks_url": "https://api.github.com/repos/srvaroa/test-repo/hooks",
+    "issue_events_url": "https://api.github.com/repos/srvaroa/test-repo/issues/events{/number}",
+    "events_url": "https://api.github.com/repos/srvaroa/test-repo/events",
+    "assignees_url": "https://api.github.com/repos/srvaroa/test-repo/assignees{/user}",
+    "branches_url": "https://api.github.com/repos/srvaroa/test-repo/branches{/branch}",
+    "tags_url": "https://api.github.com/repos/srvaroa/test-repo/tags",
+    "blobs_url": "https://api.github.com/repos/srvaroa/test-repo/git/blobs{/sha}",
+    "git_tags_url": "https://api.github.com/repos/srvaroa/test-repo/git/tags{/sha}",
+    "git_refs_url": "https://api.github.com/repos/srvaroa/test-repo/git/refs{/sha}",
+    "trees_url": "https://api.github.com/repos/srvaroa/test-repo/git/trees{/sha}",
+    "statuses_url": "https://api.github.com/repos/srvaroa/test-repo/statuses/{sha}",
+    "languages_url": "https://api.github.com/repos/srvaroa/test-repo/languages",
+    "stargazers_url": "https://api.github.com/repos/srvaroa/test-repo/stargazers",
+    "contributors_url": "https://api.github.com/repos/srvaroa/test-repo/contributors",
+    "subscribers_url": "https://api.github.com/repos/srvaroa/test-repo/subscribers",
+    "subscription_url": "https://api.github.com/repos/srvaroa/test-repo/subscription",
+    "commits_url": "https://api.github.com/repos/srvaroa/test-repo/commits{/sha}",
+    "git_commits_url": "https://api.github.com/repos/srvaroa/test-repo/git/commits{/sha}",
+    "comments_url": "https://api.github.com/repos/srvaroa/test-repo/comments{/number}",
+    "issue_comment_url": "https://api.github.com/repos/srvaroa/test-repo/issues/comments{/number}",
+    "contents_url": "https://api.github.com/repos/srvaroa/test-repo/contents/{+path}",
+    "compare_url": "https://api.github.com/repos/srvaroa/test-repo/compare/{base}...{head}",
+    "merges_url": "https://api.github.com/repos/srvaroa/test-repo/merges",
+    "archive_url": "https://api.github.com/repos/srvaroa/test-repo/{archive_format}{/ref}",
+    "downloads_url": "https://api.github.com/repos/srvaroa/test-repo/downloads",
+    "issues_url": "https://api.github.com/repos/srvaroa/test-repo/issues{/number}",
+    "pulls_url": "https://api.github.com/repos/srvaroa/test-repo/pulls{/number}",
+    "milestones_url": "https://api.github.com/repos/srvaroa/test-repo/milestones{/number}",
+    "notifications_url": "https://api.github.com/repos/srvaroa/test-repo/notifications{?since,all,participating}",
+    "labels_url": "https://api.github.com/repos/srvaroa/test-repo/labels{/name}",
+    "releases_url": "https://api.github.com/repos/srvaroa/test-repo/releases{/id}",
+    "deployments_url": "https://api.github.com/repos/srvaroa/test-repo/deployments",
+    "created_at": "2020-01-14T21:22:11Z",
+    "updated_at": "2020-03-02T22:27:53Z",
+    "pushed_at": "2020-03-02T22:27:51Z",
+    "git_url": "git://github.com/srvaroa/test-repo.git",
+    "ssh_url": "git@github.com:srvaroa/test-repo.git",
+    "clone_url": "https://github.com/srvaroa/test-repo.git",
+    "svn_url": "https://github.com/srvaroa/test-repo",
+    "homepage": null,
+    "size": 42,
+    "stargazers_count": 0,
+    "watchers_count": 0,
+    "language": "Go",
+    "has_issues": true,
+    "has_projects": true,
+    "has_downloads": true,
+    "has_wiki": true,
+    "has_pages": false,
+    "has_discussions": false,
+    "forks_count": 0,
+    "mirror_url": null,
+    "archived": false,
+    "disabled": false,
+    "open_issues_count": 1,
+    "license": null,
+    "allow_forking": true,
+    "is_template": false,
+    "web_commit_signoff_required": false,
+    "topics": [
+
+    ],
+    "visibility": "private",
+    "forks": 0,
+    "open_issues": 1,
+    "watchers": 0,
+    "default_branch": "master"
+  },
+  "sender": {
+    "login": "srvaroa",
+    "id": 346110,
+    "node_id": "MDQ6VXNlcjM0NjExMA==",
+    "avatar_url": "https://avatars.githubusercontent.com/u/346110?v=4",
+    "gravatar_id": "",
+    "url": "https://api.github.com/users/srvaroa",
+    "html_url": "https://github.com/srvaroa",
+    "followers_url": "https://api.github.com/users/srvaroa/followers",
+    "following_url": "https://api.github.com/users/srvaroa/following{/other_user}",
+    "gists_url": "https://api.github.com/users/srvaroa/gists{/gist_id}",
+    "starred_url": "https://api.github.com/users/srvaroa/starred{/owner}{/repo}",
+    "subscriptions_url": "https://api.github.com/users/srvaroa/subscriptions",
+    "organizations_url": "https://api.github.com/users/srvaroa/orgs",
+    "repos_url": "https://api.github.com/users/srvaroa/repos",
+    "events_url": "https://api.github.com/users/srvaroa/events{/privacy}",
+    "received_events_url": "https://api.github.com/users/srvaroa/received_events",
+    "type": "User",
+    "site_admin": false
+  }
+}