Skip to content

feat: Pull Request Reviews #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pkg/github/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
244 changes: 244 additions & 0 deletions pkg/github/pull_request_reviews.go
Original file line number Diff line number Diff line change
@@ -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["cursor"] = 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,
})
}
24 changes: 24 additions & 0 deletions pkg/github/pull_request_reviews_handler.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading