diff --git a/pkg/github/datasource.go b/pkg/github/datasource.go
index cedf52e5..e53fe84d 100644
--- a/pkg/github/datasource.go
+++ b/pkg/github/datasource.go
@@ -82,6 +82,16 @@ func (d *Datasource) HandlePullRequestsQuery(ctx context.Context, query *models.
return GetPullRequestsInRange(ctx, d.client, opt, req.TimeRange.From, req.TimeRange.To)
}
+// HandlePullRequestsQuery is the query handler for listing GitHub PullRequests
+func (d *Datasource) HandleReviewsQuery(ctx context.Context, query *models.PullRequestsQuery, req backend.DataQuery) (dfutil.Framer, error) {
+ opt := models.PullRequestOptionsWithRepo(query.Options, query.Owner, query.Repository)
+
+ if req.TimeRange.From.Unix() <= 0 && req.TimeRange.To.Unix() <= 0 {
+ return GetAllPullRequestReviews(ctx, d.client, opt)
+ }
+ return GetPullRequestReviewsInRange(ctx, d.client, opt, req.TimeRange.From, req.TimeRange.To)
+}
+
// HandleContributorsQuery is the query handler for listing GitHub Contributors
func (d *Datasource) HandleContributorsQuery(ctx context.Context, query *models.ContributorsQuery, req backend.DataQuery) (dfutil.Framer, error) {
opt := models.ListContributorsOptions{
diff --git a/pkg/github/pull_request_reviews.go b/pkg/github/pull_request_reviews.go
new file mode 100644
index 00000000..028e7076
--- /dev/null
+++ b/pkg/github/pull_request_reviews.go
@@ -0,0 +1,244 @@
+package github
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/grafana/github-datasource/pkg/models"
+ "github.com/grafana/grafana-plugin-sdk-go/data"
+ "github.com/pkg/errors"
+ "github.com/shurcooL/githubv4"
+)
+
+// QueryListPullRequests lists all pull requests in a repository
+//
+// {
+// search(query: "is:pr repo:grafana/grafana merged:2020-08-19..*", type: ISSUE, first: 100) {
+// nodes {
+// ... on PullRequest {
+// Number
+// Title
+// URL
+// State
+// Author
+// Repository
+// reviews(first: 100) {
+// createdAt
+// updatedAt
+// state
+// url
+// author {
+// id
+// login
+// name
+// company
+// email
+// url
+// }
+// comments(first: 0) {
+// totalCount
+// }
+// }
+// }
+// }
+// }
+// }
+type QueryListPullRequestReviews struct {
+ Search struct {
+ Nodes []struct {
+ PullRequest struct {
+ Number int64
+ Title string
+ URL string
+ State githubv4.PullRequestState
+ Author Author
+ Repository Repository
+ Reviews struct {
+ Nodes []struct {
+ Review struct {
+ CreatedAt githubv4.DateTime
+ UpdatedAt githubv4.DateTime
+ URL string
+ Author Author
+ State githubv4.PullRequestReviewState
+ Comments struct {
+ TotalCount int64
+ } `graphql:"comments(first: 0)"`
+ } `graphql:"... on PullRequestReview"`
+ }
+ PageInfo models.PageInfo
+ } `graphql:"reviews(first: 100, after: $reviewCursor)"`
+ } `graphql:"... on PullRequest"`
+ }
+ PageInfo models.PageInfo
+ } `graphql:"search(query: $query, type: ISSUE, first: 100, after: $prCursor)"`
+}
+
+type Author struct {
+ User models.User `graphql:"... on User"`
+}
+
+type Review struct {
+ CreatedAt githubv4.DateTime
+ UpdatedAt githubv4.DateTime
+ URL string
+ Author Author
+ State githubv4.PullRequestReviewState
+ CommentsCount int64
+}
+
+type PullRequestWithReviews struct {
+ Number int64
+ Title string
+ State githubv4.PullRequestState
+ URL string
+ Author Author
+ Repository Repository
+ Reviews []Review
+}
+
+// PullRequestReviews is a list of GitHub Pull Request Reviews
+type PullRequestReviews []PullRequestWithReviews
+
+// Frames coverts the list of Pull Request Reviews to a Grafana DataFrame
+func (prs PullRequestReviews) Frames() data.Frames {
+ frame := data.NewFrame(
+ "pull_request_reviews",
+ data.NewField("pull_request_number", nil, []int64{}),
+ data.NewField("pull_request_title", nil, []string{}),
+ data.NewField("pull_request_state", nil, []string{}),
+ data.NewField("pull_request_url", nil, []string{}),
+ data.NewField("pull_request_author_name", nil, []string{}),
+ data.NewField("pull_request_author_login", nil, []string{}),
+ data.NewField("pull_request_author_email", nil, []string{}),
+ data.NewField("pull_request_author_company", nil, []string{}),
+ data.NewField("repository", nil, []string{}),
+ data.NewField("review_author_name", nil, []string{}),
+ data.NewField("review_author_login", nil, []string{}),
+ data.NewField("review_author_email", nil, []string{}),
+ data.NewField("review_author_company", nil, []string{}),
+ data.NewField("review_url", nil, []string{}),
+ data.NewField("review_state", nil, []string{}),
+ data.NewField("review_comment_count", nil, []int64{}),
+ data.NewField("review_updated_at", nil, []time.Time{}),
+ data.NewField("review_created_at", nil, []time.Time{}),
+ )
+
+ for _, pr := range prs {
+ for _, review := range pr.Reviews {
+ frame.AppendRow(
+ pr.Number,
+ pr.Title,
+ string(pr.State),
+ pr.URL,
+ pr.Author.User.Name,
+ pr.Author.User.Login,
+ pr.Author.User.Email,
+ pr.Author.User.Company,
+ pr.Repository.NameWithOwner,
+ review.Author.User.Name,
+ review.Author.User.Login,
+ review.Author.User.Email,
+ review.Author.User.Company,
+ review.URL,
+ string(review.State),
+ review.CommentsCount,
+ review.UpdatedAt.Time,
+ review.CreatedAt.Time,
+ )
+ }
+ }
+
+ return data.Frames{frame}
+}
+
+// GetAllPullRequestReviews uses the graphql search endpoint API to search all pull requests in the repository
+// and all reviews for those pull requests.
+func GetAllPullRequestReviews(ctx context.Context, client models.Client, opts models.ListPullRequestsOptions) (PullRequestReviews, error) {
+ var (
+ variables = map[string]interface{}{
+ "prCursor": (*githubv4.String)(nil),
+ "reviewCursor": (*githubv4.String)(nil),
+ "query": githubv4.String(buildQuery(opts)),
+ }
+
+ pullRequestReviews = PullRequestReviews{}
+ )
+
+ for {
+ q := &QueryListPullRequestReviews{}
+ if err := client.Query(ctx, q, variables); err != nil {
+ return nil, errors.WithStack(err)
+ }
+
+ prs := make([]PullRequestWithReviews, len(q.Search.Nodes))
+
+ for i, prNode := range q.Search.Nodes {
+ pr := prNode.PullRequest
+
+ prs[i] = PullRequestWithReviews{
+ Number: pr.Number,
+ Title: pr.Title,
+ State: pr.State,
+ URL: pr.URL,
+ Author: pr.Author,
+ Repository: pr.Repository,
+ }
+
+ for {
+ for _, reviewNode := range pr.Reviews.Nodes {
+ review := reviewNode.Review
+
+ prs[i].Reviews = append(prs[i].Reviews, Review{
+ CreatedAt: review.CreatedAt,
+ UpdatedAt: review.UpdatedAt,
+ URL: review.URL,
+ Author: review.Author,
+ State: review.State,
+ CommentsCount: review.Comments.TotalCount,
+ })
+ }
+
+ if !pr.Reviews.PageInfo.HasNextPage {
+ variables["reviewCursor"] = (*githubv4.String)(nil)
+ break
+ }
+
+ variables["reviewCursor"] = pr.Reviews.PageInfo.EndCursor
+ if err := client.Query(ctx, q, variables); err != nil {
+ return nil, errors.WithStack(err)
+ }
+ }
+ }
+
+ pullRequestReviews = append(pullRequestReviews, prs...)
+
+ if !q.Search.PageInfo.HasNextPage {
+ break
+ }
+ variables["prCursor"] = q.Search.PageInfo.EndCursor
+ }
+
+ return pullRequestReviews, nil
+}
+
+// GetPullRequestReviewsInRange uses the graphql search endpoint API to find pull request reviews in the given time range.
+func GetPullRequestReviewsInRange(ctx context.Context, client models.Client, opts models.ListPullRequestsOptions, from time.Time, to time.Time) (PullRequestReviews, error) {
+ var q string
+
+ if opts.TimeField != models.PullRequestNone {
+ q = fmt.Sprintf("%s:%s..%s", opts.TimeField.String(), from.Format(time.RFC3339), to.Format(time.RFC3339))
+ }
+
+ if opts.Query != nil {
+ q = fmt.Sprintf("%s %s", *opts.Query, q)
+ }
+
+ return GetAllPullRequestReviews(ctx, client, models.ListPullRequestsOptions{
+ Repository: opts.Repository,
+ Owner: opts.Owner,
+ TimeField: opts.TimeField,
+ Query: &q,
+ })
+}
diff --git a/pkg/github/pull_request_reviews_handler.go b/pkg/github/pull_request_reviews_handler.go
new file mode 100644
index 00000000..2d44edd9
--- /dev/null
+++ b/pkg/github/pull_request_reviews_handler.go
@@ -0,0 +1,24 @@
+package github
+
+import (
+ "context"
+
+ "github.com/grafana/github-datasource/pkg/dfutil"
+ "github.com/grafana/github-datasource/pkg/models"
+ "github.com/grafana/grafana-plugin-sdk-go/backend"
+)
+
+func (s *QueryHandler) handlePullRequestReviewsQuery(ctx context.Context, q backend.DataQuery) backend.DataResponse {
+ query := &models.PullRequestsQuery{}
+ if err := UnmarshalQuery(q.JSON, query); err != nil {
+ return *err
+ }
+ return dfutil.FrameResponseWithError(s.Datasource.HandleReviewsQuery(ctx, query, q))
+}
+
+// HandlePullRequests handles the plugin query for github PullRequests
+func (s *QueryHandler) HandlePullRequestReviews(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
+ return &backend.QueryDataResponse{
+ Responses: processQueries(ctx, req, s.handlePullRequestReviewsQuery),
+ }, nil
+}
diff --git a/pkg/github/pull_request_reviews_test.go b/pkg/github/pull_request_reviews_test.go
new file mode 100644
index 00000000..35e6e65b
--- /dev/null
+++ b/pkg/github/pull_request_reviews_test.go
@@ -0,0 +1,166 @@
+package github
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/grafana/github-datasource/pkg/models"
+ "github.com/grafana/github-datasource/pkg/testutil"
+ "github.com/shurcooL/githubv4"
+)
+
+func TestListPullRequestReviews(t *testing.T) {
+ var (
+ ctx = context.Background()
+ opts = models.ListPullRequestsOptions{
+ Repository: "grafana",
+ Owner: "grafana",
+ TimeField: models.PullRequestClosedAt,
+ }
+ )
+
+ testVariables := testutil.GetTestVariablesFunction("query", "prCursor", "reviewCursor")
+
+ client := testutil.NewTestClient(t,
+ testVariables,
+ testutil.GetTestQueryFunction(&QueryListPullRequestReviews{}),
+ )
+
+ _, err := GetPullRequestReviewsInRange(ctx, client, opts, time.Now().Add(-30*24*time.Hour), time.Now())
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestPullRequestReviewsDataFrame(t *testing.T) {
+ openedAt, err := time.Parse(time.RFC3339, "2020-08-25T16:21:56+00:00")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ firstUser := models.User{
+ ID: "1",
+ Login: "testUser",
+ Name: "Test User",
+ Company: "ACME corp",
+ Email: "user@example.com",
+ }
+ secondUser := models.User{
+ ID: "2",
+ Login: "testUser2",
+ Name: "Second User",
+ Company: "ACME corp",
+ Email: "user2@example.com",
+ }
+ thirdUser := models.User{
+ ID: "3",
+ Login: "testUser3",
+ Name: "Third User",
+ Company: "ACME corp",
+ Email: "user3@example.com",
+ }
+
+ pullRequestReviews := PullRequestReviews{
+ {
+ Number: 1,
+ Title: "PullRequest #1",
+ URL: "https://github.com/grafana/github-datasource/pulls/1",
+ State: githubv4.PullRequestStateOpen,
+ Author: Author{
+ User: firstUser,
+ },
+ Repository: Repository{
+ NameWithOwner: "grafana/github-datasource",
+ },
+ Reviews: []Review{
+ {
+ URL: "https://github.com/grafana/github-datasource/pull/1#pullrequestreview-2461579074",
+ State: githubv4.PullRequestReviewStateApproved,
+ Author: Author{
+ User: secondUser,
+ },
+ CreatedAt: githubv4.DateTime{
+ Time: openedAt,
+ },
+ UpdatedAt: githubv4.DateTime{
+ Time: openedAt,
+ },
+ CommentsCount: 10,
+ },
+ {
+ URL: "https://github.com/grafana/github-datasource/pull/1#pullrequestreview-2461579074",
+ State: githubv4.PullRequestReviewStateApproved,
+ Author: Author{
+ User: thirdUser,
+ },
+ CreatedAt: githubv4.DateTime{
+ Time: openedAt,
+ },
+ UpdatedAt: githubv4.DateTime{
+ Time: openedAt,
+ },
+ CommentsCount: 1,
+ },
+ },
+ },
+ {
+ Number: 2,
+ Title: "PullRequest #2",
+ URL: "https://github.com/grafana/github-datasource/pulls/2",
+ State: githubv4.PullRequestStateOpen,
+ Author: Author{
+ User: secondUser,
+ },
+ Repository: Repository{
+ NameWithOwner: "grafana/github-datasource",
+ },
+ Reviews: []Review{
+ {
+ URL: "https://github.com/grafana/github-datasource/pull/1#pullrequestreview-2461579074",
+ State: githubv4.PullRequestReviewStateApproved,
+ Author: Author{
+ User: firstUser,
+ },
+ CreatedAt: githubv4.DateTime{
+ Time: openedAt,
+ },
+ UpdatedAt: githubv4.DateTime{
+ Time: openedAt,
+ },
+ CommentsCount: 19,
+ },
+ },
+ },
+ {
+ Number: 3,
+ Title: "PullRequest #2",
+ URL: "https://github.com/grafana/github-datasource/pulls/3",
+ State: githubv4.PullRequestStateOpen,
+ Author: Author{
+ User: secondUser,
+ },
+ Repository: Repository{
+ NameWithOwner: "grafana/github-datasource",
+ },
+ Reviews: []Review{
+ {
+ URL: "https://github.com/grafana/github-datasource/pull/1#pullrequestreview-2461579074",
+ State: githubv4.PullRequestReviewStateApproved,
+ Author: Author{
+ User: firstUser,
+ },
+ CreatedAt: githubv4.DateTime{
+ Time: openedAt,
+ },
+ UpdatedAt: githubv4.DateTime{
+ Time: openedAt,
+ },
+ CommentsCount: 1,
+ },
+ },
+ },
+ }
+
+ testutil.CheckGoldenFramer(t, "pull_request_reviews", pullRequestReviews)
+}
diff --git a/pkg/github/query_handler.go b/pkg/github/query_handler.go
index 333b207e..5ff9bb65 100644
--- a/pkg/github/query_handler.go
+++ b/pkg/github/query_handler.go
@@ -47,6 +47,7 @@ func GetQueryHandlers(s *QueryHandler) *datasource.QueryTypeMux {
mux.HandleFunc(models.QueryTypeContributors, s.HandleContributors)
mux.HandleFunc(models.QueryTypeLabels, s.HandleLabels)
mux.HandleFunc(models.QueryTypePullRequests, s.HandlePullRequests)
+ mux.HandleFunc(models.QueryTypePullRequestReviews, s.HandlePullRequestReviews)
mux.HandleFunc(models.QueryTypeReleases, s.HandleReleases)
mux.HandleFunc(models.QueryTypeTags, s.HandleTags)
mux.HandleFunc(models.QueryTypePackages, s.HandlePackages)
diff --git a/pkg/github/testdata/pull_request_reviews.golden.jsonc b/pkg/github/testdata/pull_request_reviews.golden.jsonc
new file mode 100644
index 00000000..b8264c69
--- /dev/null
+++ b/pkg/github/testdata/pull_request_reviews.golden.jsonc
@@ -0,0 +1,268 @@
+// 🌟 This was machine generated. Do not edit. 🌟
+//
+// Frame[0]
+// Name: pull_request_reviews
+// Dimensions: 18 Fields by 4 Rows
+// +---------------------------+--------------------------+--------------------------+------------------------------------------------------+--------------------------------+---------------------------------+---------------------------------+-----------------------------------+---------------------------+--------------------------+---------------------------+---------------------------+-----------------------------+----------------------------------------------------------------------------------+--------------------+----------------------------+---------------------------------+---------------------------------+
+// | Name: pull_request_number | Name: pull_request_title | Name: pull_request_state | Name: pull_request_url | Name: pull_request_author_name | Name: pull_request_author_login | Name: pull_request_author_email | Name: pull_request_author_company | Name: repository | Name: review_author_name | Name: review_author_login | Name: review_author_email | Name: review_author_company | Name: review_url | Name: review_state | Name: review_comment_count | Name: review_updated_at | Name: review_created_at |
+// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
+// | Type: []int64 | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []int64 | Type: []time.Time | Type: []time.Time |
+// +---------------------------+--------------------------+--------------------------+------------------------------------------------------+--------------------------------+---------------------------------+---------------------------------+-----------------------------------+---------------------------+--------------------------+---------------------------+---------------------------+-----------------------------+----------------------------------------------------------------------------------+--------------------+----------------------------+---------------------------------+---------------------------------+
+// | 1 | PullRequest #1 | OPEN | https://github.com/grafana/github-datasource/pulls/1 | Test User | testUser | user@example.com | ACME corp | grafana/github-datasource | Second User | testUser2 | user2@example.com | ACME corp | https://github.com/grafana/github-datasource/pull/1#pullrequestreview-2461579074 | APPROVED | 10 | 2020-08-25 16:21:56 +0000 +0000 | 2020-08-25 16:21:56 +0000 +0000 |
+// | 1 | PullRequest #1 | OPEN | https://github.com/grafana/github-datasource/pulls/1 | Test User | testUser | user@example.com | ACME corp | grafana/github-datasource | Third User | testUser3 | user3@example.com | ACME corp | https://github.com/grafana/github-datasource/pull/1#pullrequestreview-2461579074 | APPROVED | 1 | 2020-08-25 16:21:56 +0000 +0000 | 2020-08-25 16:21:56 +0000 +0000 |
+// | 2 | PullRequest #2 | OPEN | https://github.com/grafana/github-datasource/pulls/2 | Second User | testUser2 | user2@example.com | ACME corp | grafana/github-datasource | Test User | testUser | user@example.com | ACME corp | https://github.com/grafana/github-datasource/pull/1#pullrequestreview-2461579074 | APPROVED | 19 | 2020-08-25 16:21:56 +0000 +0000 | 2020-08-25 16:21:56 +0000 +0000 |
+// | 3 | PullRequest #2 | OPEN | https://github.com/grafana/github-datasource/pulls/3 | Second User | testUser2 | user2@example.com | ACME corp | grafana/github-datasource | Test User | testUser | user@example.com | ACME corp | https://github.com/grafana/github-datasource/pull/1#pullrequestreview-2461579074 | APPROVED | 1 | 2020-08-25 16:21:56 +0000 +0000 | 2020-08-25 16:21:56 +0000 +0000 |
+// +---------------------------+--------------------------+--------------------------+------------------------------------------------------+--------------------------------+---------------------------------+---------------------------------+-----------------------------------+---------------------------+--------------------------+---------------------------+---------------------------+-----------------------------+----------------------------------------------------------------------------------+--------------------+----------------------------+---------------------------------+---------------------------------+
+//
+//
+// 🌟 This was machine generated. Do not edit. 🌟
+{
+ "status": 200,
+ "frames": [
+ {
+ "schema": {
+ "name": "pull_request_reviews",
+ "fields": [
+ {
+ "name": "pull_request_number",
+ "type": "number",
+ "typeInfo": {
+ "frame": "int64"
+ }
+ },
+ {
+ "name": "pull_request_title",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "pull_request_state",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "pull_request_url",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "pull_request_author_name",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "pull_request_author_login",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "pull_request_author_email",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "pull_request_author_company",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "repository",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "review_author_name",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "review_author_login",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "review_author_email",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "review_author_company",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "review_url",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "review_state",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string"
+ }
+ },
+ {
+ "name": "review_comment_count",
+ "type": "number",
+ "typeInfo": {
+ "frame": "int64"
+ }
+ },
+ {
+ "name": "review_updated_at",
+ "type": "time",
+ "typeInfo": {
+ "frame": "time.Time"
+ }
+ },
+ {
+ "name": "review_created_at",
+ "type": "time",
+ "typeInfo": {
+ "frame": "time.Time"
+ }
+ }
+ ]
+ },
+ "data": {
+ "values": [
+ [
+ 1,
+ 1,
+ 2,
+ 3
+ ],
+ [
+ "PullRequest #1",
+ "PullRequest #1",
+ "PullRequest #2",
+ "PullRequest #2"
+ ],
+ [
+ "OPEN",
+ "OPEN",
+ "OPEN",
+ "OPEN"
+ ],
+ [
+ "https://github.com/grafana/github-datasource/pulls/1",
+ "https://github.com/grafana/github-datasource/pulls/1",
+ "https://github.com/grafana/github-datasource/pulls/2",
+ "https://github.com/grafana/github-datasource/pulls/3"
+ ],
+ [
+ "Test User",
+ "Test User",
+ "Second User",
+ "Second User"
+ ],
+ [
+ "testUser",
+ "testUser",
+ "testUser2",
+ "testUser2"
+ ],
+ [
+ "user@example.com",
+ "user@example.com",
+ "user2@example.com",
+ "user2@example.com"
+ ],
+ [
+ "ACME corp",
+ "ACME corp",
+ "ACME corp",
+ "ACME corp"
+ ],
+ [
+ "grafana/github-datasource",
+ "grafana/github-datasource",
+ "grafana/github-datasource",
+ "grafana/github-datasource"
+ ],
+ [
+ "Second User",
+ "Third User",
+ "Test User",
+ "Test User"
+ ],
+ [
+ "testUser2",
+ "testUser3",
+ "testUser",
+ "testUser"
+ ],
+ [
+ "user2@example.com",
+ "user3@example.com",
+ "user@example.com",
+ "user@example.com"
+ ],
+ [
+ "ACME corp",
+ "ACME corp",
+ "ACME corp",
+ "ACME corp"
+ ],
+ [
+ "https://github.com/grafana/github-datasource/pull/1#pullrequestreview-2461579074",
+ "https://github.com/grafana/github-datasource/pull/1#pullrequestreview-2461579074",
+ "https://github.com/grafana/github-datasource/pull/1#pullrequestreview-2461579074",
+ "https://github.com/grafana/github-datasource/pull/1#pullrequestreview-2461579074"
+ ],
+ [
+ "APPROVED",
+ "APPROVED",
+ "APPROVED",
+ "APPROVED"
+ ],
+ [
+ 10,
+ 1,
+ 19,
+ 1
+ ],
+ [
+ 1598372516000,
+ 1598372516000,
+ 1598372516000,
+ 1598372516000
+ ],
+ [
+ 1598372516000,
+ 1598372516000,
+ 1598372516000,
+ 1598372516000
+ ]
+ ]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/pkg/models/query.go b/pkg/models/query.go
index 09332020..8c2e2b04 100644
--- a/pkg/models/query.go
+++ b/pkg/models/query.go
@@ -13,6 +13,8 @@ const (
QueryTypeReleases = "Releases"
// QueryTypePullRequests is used when querying pull requests in a GitHub repository
QueryTypePullRequests = "Pull_Requests"
+ // QueryTypePullRequestReviews is used when querying pull request reviews in a GitHub repository
+ QueryTypePullRequestReviews = "Pull_Request_Reviews"
// QueryTypeLabels is used when querying labels in a GitHub repository
QueryTypeLabels = "Labels"
// QueryTypeRepositories is used when querying for a GitHub repository
@@ -54,6 +56,12 @@ type PullRequestsQuery struct {
Options ListPullRequestsOptions `json:"options"`
}
+// PullRequestsQuery is used when querying for GitHub Pull Requests
+type PullRequestReviewsQuery struct {
+ Query
+ Options ListPullRequestsOptions `json:"options"`
+}
+
// CommitsQuery is used when querying for GitHub commits
type CommitsQuery struct {
Query
diff --git a/src/constants.ts b/src/constants.ts
index 95c99c52..44c3f7e6 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -5,6 +5,7 @@ export enum QueryType {
Tags = 'Tags',
Releases = 'Releases',
Pull_Requests = 'Pull_Requests',
+ Pull_Request_Reviews = 'Pull_Request_Reviews',
Labels = 'Labels',
Repositories = 'Repositories',
Organizations = 'Organizations',
diff --git a/src/types/query.ts b/src/types/query.ts
index f408b029..e5a4d8bd 100644
--- a/src/types/query.ts
+++ b/src/types/query.ts
@@ -10,6 +10,7 @@ export interface RepositoryOptions {
export interface GitHubQuery extends Indexable, DataQuery, RepositoryOptions {
options?:
| PullRequestsOptions
+ | PullRequestReviewsOptions
| ReleasesOptions
| LabelsOptions
| TagsOptions
@@ -40,6 +41,11 @@ export interface PullRequestsOptions extends Indexable {
query?: string;
}
+export interface PullRequestReviewsOptions extends Indexable {
+ timeField?: PullRequestTimeField;
+ query?: string;
+}
+
export interface CommitsOptions extends Indexable {
gitRef?: string;
}
diff --git a/src/views/QueryEditor.tsx b/src/views/QueryEditor.tsx
index 5a47a3dd..725e3084 100644
--- a/src/views/QueryEditor.tsx
+++ b/src/views/QueryEditor.tsx
@@ -12,6 +12,7 @@ import QueryEditorCommits from './QueryEditorCommits';
import QueryEditorIssues from './QueryEditorIssues';
import QueryEditorMilestones from './QueryEditorMilestones';
import QueryEditorPullRequests from './QueryEditorPullRequests';
+import QueryEditorPullRequestReviews from './QueryEditorPullRequestReviews';
import QueryEditorTags from './QueryEditorTags';
import QueryEditorContributors from './QueryEditorContributors';
import QueryEditorLabels from './QueryEditorLabels';
@@ -78,6 +79,11 @@ const queryEditors: {
),
},
+ [QueryType.Pull_Request_Reviews]: {
+ component: (props: Props, onChange: (val: any) => void) => (
+
+ ),
+ },
[QueryType.Vulnerabilities]: {
component: (props: Props, onChange: (val: any) => void) => (
diff --git a/src/views/QueryEditorPullRequestReviews.tsx b/src/views/QueryEditorPullRequestReviews.tsx
new file mode 100644
index 00000000..c05d7f88
--- /dev/null
+++ b/src/views/QueryEditorPullRequestReviews.tsx
@@ -0,0 +1,77 @@
+import React, { useState } from 'react';
+import { Input, Select, InlineField } from '@grafana/ui';
+import { SelectableValue } from '@grafana/data';
+import { RightColumnWidth, LeftColumnWidth } from './QueryEditor';
+import { PullRequestTimeField } from '../constants';
+import type { PullRequestReviewsOptions } from '../types/query';
+
+interface Props extends PullRequestReviewsOptions {
+ onChange: (value: PullRequestReviewsOptions) => void;
+}
+
+const timeFieldOptions: Array> = Object.keys(PullRequestTimeField)
+ .filter((_, i) => PullRequestTimeField[i] !== undefined)
+ .map((_, i) => {
+ return {
+ label: `${PullRequestTimeField[i]}`,
+ value: i as PullRequestTimeField,
+ };
+ });
+
+const defaultTimeField = timeFieldOptions[0].value;
+
+const QueryEditorPullRequestReviews = (props: Props) => {
+ const [query, setQuery] = useState(props.query || '');
+ return (
+ <>
+ (
+ <>
+ For more information, visit
+
+ https://docs.github.com/en/github/searching-for-information-on-github/searching-issues-and-pull-requests
+
+ >
+ )}
+ interactive={true}
+ >
+ setQuery(el.currentTarget.value)}
+ onBlur={(el) =>
+ props.onChange({
+ ...props,
+ query: el.currentTarget.value,
+ })
+ }
+ />
+
+
+
+ >
+ );
+};
+
+export default QueryEditorPullRequestReviews;