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, + }) + } + /> + + +