diff --git a/pkg/github/client/client.go b/pkg/github/client/client.go index bdbcff00..fde3028b 100644 --- a/pkg/github/client/client.go +++ b/pkg/github/client/client.go @@ -177,6 +177,25 @@ func (client *Client) ListAlertsForOrg(ctx context.Context, owner string, opts * return alerts, resp, err } +// GetCommitFiles returns the list of files changed in a specific commit. +// Note: the GitHub API returns at most 300 files for a single commit. +func (client *Client) GetCommitFiles(ctx context.Context, owner, repo, sha string, opts *googlegithub.ListOptions) ([]*googlegithub.CommitFile, *googlegithub.Response, error) { + commit, resp, err := client.restClient.Repositories.GetCommit(ctx, owner, repo, sha, opts) + if err != nil { + return nil, nil, addErrorSourceToError(err, resp) + } + return commit.Files, resp, nil +} + +// ListPullRequestFiles returns the list of files changed in a specific pull request. +func (client *Client) ListPullRequestFiles(ctx context.Context, owner, repo string, prNumber int, opts *googlegithub.ListOptions) ([]*googlegithub.CommitFile, *googlegithub.Response, error) { + files, resp, err := client.restClient.PullRequests.ListFiles(ctx, owner, repo, prNumber, opts) + if err != nil { + return nil, nil, addErrorSourceToError(err, resp) + } + return files, resp, nil +} + // GetWorkflowUsage returns the workflow usage for a specific workflow. func (client *Client) GetWorkflowUsage(ctx context.Context, owner, repo, workflow string, timeRange backend.TimeRange) (models.WorkflowUsage, error) { actors := make(map[string]struct{}, 0) diff --git a/pkg/github/codescanning_test.go b/pkg/github/codescanning_test.go index 50f19f5e..65629588 100644 --- a/pkg/github/codescanning_test.go +++ b/pkg/github/codescanning_test.go @@ -52,6 +52,14 @@ func (m *mockClient) ListAlertsForOrg(ctx context.Context, owner string, opts *g return m.mockAlerts, m.mockResponse, nil } +func (m *mockClient) GetCommitFiles(ctx context.Context, owner, repo, sha string, opts *googlegithub.ListOptions) ([]*googlegithub.CommitFile, *googlegithub.Response, error) { + return nil, nil, nil +} + +func (m *mockClient) ListPullRequestFiles(ctx context.Context, owner, repo string, prNumber int, opts *googlegithub.ListOptions) ([]*googlegithub.CommitFile, *googlegithub.Response, error) { + return nil, nil, nil +} + func TestGetCodeScanningAlerts(t *testing.T) { var ( ctx = context.Background() diff --git a/pkg/github/commit_files.go b/pkg/github/commit_files.go new file mode 100644 index 00000000..5bc31aa8 --- /dev/null +++ b/pkg/github/commit_files.go @@ -0,0 +1,144 @@ +package github + +import ( + "context" + "fmt" + "time" + + googlegithub "github.com/google/go-github/v81/github" + "github.com/grafana/grafana-plugin-sdk-go/data" + + "github.com/grafana/github-datasource/pkg/models" +) + +// CommitFilesWrapper is a list of commit files returned by the GitHub API +type CommitFilesWrapper []*googlegithub.CommitFile + +// Frames converts the list of commit files to a Grafana DataFrame +func (files CommitFilesWrapper) Frames() data.Frames { + frame := data.NewFrame( + "commit_files", + data.NewField("path", nil, []string{}), + data.NewField("additions", nil, []int64{}), + data.NewField("deletions", nil, []int64{}), + data.NewField("changes", nil, []int64{}), + data.NewField("status", nil, []string{}), + data.NewField("previous_filename", nil, []string{}), + ) + + for _, f := range files { + frame.AppendRow( + f.GetFilename(), + int64(f.GetAdditions()), + int64(f.GetDeletions()), + int64(f.GetChanges()), + f.GetStatus(), + f.GetPreviousFilename(), + ) + } + + frame.Meta = &data.FrameMeta{PreferredVisualization: data.VisTypeTable} + return data.Frames{frame} +} + +// GetCommitFiles fetches the files changed in a specific commit. +// The GitHub REST API returns at most 300 files for a single commit. +func GetCommitFiles(ctx context.Context, client models.Client, opts models.CommitFilesOptions) (CommitFilesWrapper, error) { + if opts.Owner == "" || opts.Repository == "" || opts.Ref == "" { + return nil, nil + } + + files, _, err := client.GetCommitFiles(ctx, opts.Owner, opts.Repository, opts.Ref, &googlegithub.ListOptions{ + PerPage: 300, + }) + if err != nil { + return nil, fmt.Errorf("getting commit files: owner=%s repo=%s sha=%s: %w", opts.Owner, opts.Repository, opts.Ref, err) + } + + return CommitFilesWrapper(files), nil +} + +// GetPullRequestFiles fetches all files changed in a pull request, handling pagination. +func GetPullRequestFiles(ctx context.Context, client models.Client, opts models.PullRequestFilesOptions) (CommitFilesWrapper, error) { + if opts.Owner == "" || opts.Repository == "" || opts.PRNumber == 0 { + return nil, nil + } + + var allFiles []*googlegithub.CommitFile + page := 1 + + for { + files, resp, err := client.ListPullRequestFiles(ctx, opts.Owner, opts.Repository, int(opts.PRNumber), &googlegithub.ListOptions{ + Page: page, + PerPage: 100, + }) + if err != nil { + return nil, fmt.Errorf("listing PR files: owner=%s repo=%s pr=%d page=%d: %w", opts.Owner, opts.Repository, opts.PRNumber, page, err) + } + + allFiles = append(allFiles, files...) + + if resp == nil || resp.NextPage == 0 { + break + } + page = resp.NextPage + } + + return CommitFilesWrapper(allFiles), nil +} + +// CommitWithFiles holds a commit and the files changed in it +type CommitWithFiles struct { + Commit Commit + Files []*googlegithub.CommitFile +} + +// CommitsWithFiles is a list of commits each paired with their changed files +type CommitsWithFiles []CommitWithFiles + +// Frames converts the list of commits-with-files to a flattened Grafana DataFrame. +// Each row represents one file change within a commit (one row per commit × file). +func (c CommitsWithFiles) Frames() data.Frames { + frame := data.NewFrame( + "commits", + data.NewField("id", nil, []string{}), + data.NewField("author", nil, []string{}), + data.NewField("author_login", nil, []string{}), + data.NewField("author_email", nil, []string{}), + data.NewField("author_company", nil, []string{}), + data.NewField("committed_at", nil, []time.Time{}), + data.NewField("pushed_at", nil, []time.Time{}), + data.NewField("message", nil, []string{}), + data.NewField("file_path", nil, []string{}), + data.NewField("file_additions", nil, []int64{}), + data.NewField("file_deletions", nil, []int64{}), + data.NewField("file_changes", nil, []int64{}), + data.NewField("file_status", nil, []string{}), + data.NewField("previous_filename", nil, []string{}), + ) + + for _, cwf := range c { + for _, f := range cwf.Files { + frame.AppendRow( + cwf.Commit.OID, + cwf.Commit.Author.Name, + cwf.Commit.Author.User.Login, + cwf.Commit.Author.Email, + cwf.Commit.Author.User.Company, + cwf.Commit.CommittedDate.Time, + cwf.Commit.PushedDate.Time, + string(cwf.Commit.Message), + f.GetFilename(), + int64(f.GetAdditions()), + int64(f.GetDeletions()), + int64(f.GetChanges()), + f.GetStatus(), + f.GetPreviousFilename(), + ) + } + } + + frame.Meta = &data.FrameMeta{PreferredVisualization: data.VisTypeTable} + return data.Frames{frame} +} + diff --git a/pkg/github/commit_files_handler.go b/pkg/github/commit_files_handler.go new file mode 100644 index 00000000..f7d15b0d --- /dev/null +++ b/pkg/github/commit_files_handler.go @@ -0,0 +1,40 @@ +package github + +import ( + "context" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + + "github.com/grafana/github-datasource/pkg/dfutil" + "github.com/grafana/github-datasource/pkg/models" +) + +func (s *QueryHandler) handleCommitFilesQuery(ctx context.Context, q backend.DataQuery) backend.DataResponse { + query := &models.CommitFilesQuery{} + if err := UnmarshalQuery(q.JSON, query); err != nil { + return *err + } + return dfutil.FrameResponseWithError(s.Datasource.HandleCommitFilesQuery(ctx, query, q)) +} + +// HandleCommitFiles handles the plugin query for files changed in a GitHub commit +func (s *QueryHandler) HandleCommitFiles(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + return &backend.QueryDataResponse{ + Responses: processQueries(ctx, req, s.handleCommitFilesQuery), + }, nil +} + +func (s *QueryHandler) handlePullRequestFilesQuery(ctx context.Context, q backend.DataQuery) backend.DataResponse { + query := &models.PullRequestFilesQuery{} + if err := UnmarshalQuery(q.JSON, query); err != nil { + return *err + } + return dfutil.FrameResponseWithError(s.Datasource.HandlePullRequestFilesQuery(ctx, query, q)) +} + +// HandlePullRequestFiles handles the plugin query for files changed in a GitHub pull request +func (s *QueryHandler) HandlePullRequestFiles(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + return &backend.QueryDataResponse{ + Responses: processQueries(ctx, req, s.handlePullRequestFilesQuery), + }, nil +} diff --git a/pkg/github/commit_files_test.go b/pkg/github/commit_files_test.go new file mode 100644 index 00000000..dfd9ffad --- /dev/null +++ b/pkg/github/commit_files_test.go @@ -0,0 +1,198 @@ +package github + +import ( + "context" + "testing" + + googlegithub "github.com/google/go-github/v81/github" + "github.com/grafana/grafana-plugin-sdk-go/backend" + + "github.com/grafana/github-datasource/pkg/models" + "github.com/grafana/github-datasource/pkg/testutil" +) + +// commitFilesMockClient satisfies models.Client for commit file tests +type commitFilesMockClient struct { + commitFiles []*googlegithub.CommitFile + prFiles []*googlegithub.CommitFile + nextPage int + expectedOwner string + expectedRepo string + t *testing.T +} + +func (m *commitFilesMockClient) Query(_ context.Context, _ interface{}, _ map[string]interface{}) error { + return nil +} +func (m *commitFilesMockClient) ListWorkflows(_ context.Context, _, _ string, _ *googlegithub.ListOptions) (*googlegithub.Workflows, *googlegithub.Response, error) { + return nil, nil, nil +} +func (m *commitFilesMockClient) GetWorkflowUsage(_ context.Context, _, _, _ string, _ backend.TimeRange) (models.WorkflowUsage, error) { + return models.WorkflowUsage{}, nil +} +func (m *commitFilesMockClient) GetWorkflowRuns(_ context.Context, _, _, _, _ string, _ backend.TimeRange) ([]*googlegithub.WorkflowRun, error) { + return nil, nil +} +func (m *commitFilesMockClient) ListAlertsForRepo(_ context.Context, _, _ string, _ *googlegithub.AlertListOptions) ([]*googlegithub.Alert, *googlegithub.Response, error) { + return nil, nil, nil +} +func (m *commitFilesMockClient) ListAlertsForOrg(_ context.Context, _ string, _ *googlegithub.AlertListOptions) ([]*googlegithub.Alert, *googlegithub.Response, error) { + return nil, nil, nil +} + +func (m *commitFilesMockClient) GetCommitFiles(_ context.Context, owner, repo, _ string, _ *googlegithub.ListOptions) ([]*googlegithub.CommitFile, *googlegithub.Response, error) { + if owner != m.expectedOwner || repo != m.expectedRepo { + m.t.Errorf("GetCommitFiles: expected owner/repo=%s/%s got=%s/%s", m.expectedOwner, m.expectedRepo, owner, repo) + } + return m.commitFiles, &googlegithub.Response{}, nil +} + +func (m *commitFilesMockClient) ListPullRequestFiles(_ context.Context, owner, repo string, _ int, _ *googlegithub.ListOptions) ([]*googlegithub.CommitFile, *googlegithub.Response, error) { + if owner != m.expectedOwner || repo != m.expectedRepo { + m.t.Errorf("ListPullRequestFiles: expected owner/repo=%s/%s got=%s/%s", m.expectedOwner, m.expectedRepo, owner, repo) + } + resp := &googlegithub.Response{} + resp.NextPage = m.nextPage + m.nextPage = 0 // only one page after the first call + return m.prFiles, resp, nil +} + +func TestGetCommitFiles(t *testing.T) { + ctx := context.Background() + opts := models.CommitFilesOptions{ + Owner: "grafana", + Repository: "grafana", + Ref: "abc123def456", + } + + filename := "pkg/server/server.go" + additions, deletions, changes := 10, 5, 15 + status := "modified" + + client := &commitFilesMockClient{ + commitFiles: []*googlegithub.CommitFile{ + { + Filename: &filename, + Additions: &additions, + Deletions: &deletions, + Changes: &changes, + Status: &status, + }, + }, + expectedOwner: "grafana", + expectedRepo: "grafana", + t: t, + } + + result, err := GetCommitFiles(ctx, client, opts) + if err != nil { + t.Fatal(err) + } + if len(result) != 1 { + t.Errorf("expected 1 file, got %d", len(result)) + } +} + +func TestGetCommitFilesEmptyRef(t *testing.T) { + ctx := context.Background() + opts := models.CommitFilesOptions{ + Owner: "grafana", + Repository: "grafana", + Ref: "", + } + + client := &commitFilesMockClient{t: t} + result, err := GetCommitFiles(ctx, client, opts) + if err != nil { + t.Fatal(err) + } + if result != nil { + t.Errorf("expected nil result for empty ref, got %v", result) + } +} + +func TestGetPullRequestFiles(t *testing.T) { + ctx := context.Background() + opts := models.PullRequestFilesOptions{ + Owner: "grafana", + Repository: "grafana", + PRNumber: 42, + } + + filename := "pkg/server/server.go" + additions, deletions, changes := 20, 3, 23 + status := "modified" + + client := &commitFilesMockClient{ + prFiles: []*googlegithub.CommitFile{ + { + Filename: &filename, + Additions: &additions, + Deletions: &deletions, + Changes: &changes, + Status: &status, + }, + }, + nextPage: 0, + expectedOwner: "grafana", + expectedRepo: "grafana", + t: t, + } + + result, err := GetPullRequestFiles(ctx, client, opts) + if err != nil { + t.Fatal(err) + } + if len(result) != 1 { + t.Errorf("expected 1 file, got %d", len(result)) + } +} + +func TestGetPullRequestFilesZeroPR(t *testing.T) { + ctx := context.Background() + opts := models.PullRequestFilesOptions{ + Owner: "grafana", + Repository: "grafana", + PRNumber: 0, + } + + client := &commitFilesMockClient{t: t} + result, err := GetPullRequestFiles(ctx, client, opts) + if err != nil { + t.Fatal(err) + } + if result != nil { + t.Errorf("expected nil result for zero PR number, got %v", result) + } +} + +func TestCommitFilesFrames(t *testing.T) { + filename1 := "src/main.go" + additions1, deletions1, changes1 := 10, 2, 12 + status1 := "modified" + + filename2new := "src/renamed.go" + filename2old := "src/old.go" + additions2, deletions2, changes2 := 0, 0, 0 + status2 := "renamed" + + files := CommitFilesWrapper([]*googlegithub.CommitFile{ + { + Filename: &filename1, + Additions: &additions1, + Deletions: &deletions1, + Changes: &changes1, + Status: &status1, + }, + { + Filename: &filename2new, + Additions: &additions2, + Deletions: &deletions2, + Changes: &changes2, + Status: &status2, + PreviousFilename: &filename2old, + }, + }) + + testutil.CheckGoldenFramer(t, "commit_files", files) +} diff --git a/pkg/github/commits.go b/pkg/github/commits.go index a7119c26..02f90323 100644 --- a/pkg/github/commits.go +++ b/pkg/github/commits.go @@ -2,8 +2,10 @@ package github import ( "context" + "fmt" "time" + googlegithub "github.com/google/go-github/v81/github" "github.com/grafana/github-datasource/pkg/models" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/shurcooL/githubv4" @@ -155,3 +157,26 @@ func GetCommitsInRange(ctx context.Context, client models.Client, opts models.Li return commits, nil } + +// GetCommitsWithFilesInRange fetches commits in a time range and enriches each +// with its changed files via one REST call per commit. This can be slow and +// rate-limit-heavy for large time ranges. +func GetCommitsWithFilesInRange(ctx context.Context, client models.Client, opts models.ListCommitsOptions, from time.Time, to time.Time) (CommitsWithFiles, error) { + commits, err := GetCommitsInRange(ctx, client, opts, from, to) + if err != nil { + return nil, err + } + + result := make(CommitsWithFiles, 0, len(commits)) + for _, c := range commits { + files, _, err := client.GetCommitFiles(ctx, opts.Owner, opts.Repository, c.OID, &googlegithub.ListOptions{PerPage: 300}) + if err != nil { + return nil, fmt.Errorf("getting files for commit %s: %w", c.OID, err) + } + if len(files) > 0 { + result = append(result, CommitWithFiles{Commit: c, Files: files}) + } + } + + return result, nil +} diff --git a/pkg/github/datasource.go b/pkg/github/datasource.go index bd8a4196..a57acb97 100644 --- a/pkg/github/datasource.go +++ b/pkg/github/datasource.go @@ -43,9 +43,24 @@ func (d *Datasource) HandleIssuesQuery(ctx context.Context, query *models.Issues // HandleCommitsQuery is the query handler for listing GitHub Commits func (d *Datasource) HandleCommitsQuery(ctx context.Context, query *models.CommitsQuery, req backend.DataQuery) (dfutil.Framer, error) { opt := models.CommitsOptionsWithRepo(query.Options, query.Owner, query.Repository) + if opt.IncludeFiles { + return GetCommitsWithFilesInRange(ctx, d.client, opt, req.TimeRange.From, req.TimeRange.To) + } return GetCommitsInRange(ctx, d.client, opt, req.TimeRange.From, req.TimeRange.To) } +// HandleCommitFilesQuery is the query handler for listing files changed in a GitHub commit +func (d *Datasource) HandleCommitFilesQuery(ctx context.Context, query *models.CommitFilesQuery, req backend.DataQuery) (dfutil.Framer, error) { + opt := models.CommitFilesOptionsWithRepo(query.Options, query.Owner, query.Repository) + return GetCommitFiles(ctx, d.client, opt) +} + +// HandlePullRequestFilesQuery is the query handler for listing files changed in a GitHub pull request +func (d *Datasource) HandlePullRequestFilesQuery(ctx context.Context, query *models.PullRequestFilesQuery, req backend.DataQuery) (dfutil.Framer, error) { + opt := models.PullRequestFilesOptionsWithRepo(query.Options, query.Owner, query.Repository) + return GetPullRequestFiles(ctx, d.client, opt) +} + // HandleCodeScanningQuery is the query handler for listing code scanning alerts of a GitHub repository func (d *Datasource) HandleCodeScanningQuery(ctx context.Context, query *models.CodeScanningQuery, req backend.DataQuery) (dfutil.Framer, error) { opt := models.CodeScanningOptionsWithRepo(query.Options, query.Owner, query.Repository) diff --git a/pkg/github/query_handler.go b/pkg/github/query_handler.go index 0cf972c5..fe8da029 100644 --- a/pkg/github/query_handler.go +++ b/pkg/github/query_handler.go @@ -62,6 +62,8 @@ func GetQueryHandlers(s *QueryHandler) *datasource.QueryTypeMux { mux.HandleFunc(models.QueryTypeWorkflowUsage, s.HandleWorkflowUsage) mux.HandleFunc(models.QueryTypeWorkflowRuns, s.HandleWorkflowRuns) mux.HandleFunc(models.QueryTypeCodeScanning, s.HandleCodeScanning) + mux.HandleFunc(models.QueryTypeCommitFiles, s.HandleCommitFiles) + mux.HandleFunc(models.QueryTypePullRequestFiles, s.HandlePullRequestFiles) return mux } diff --git a/pkg/github/testdata/commit_files.golden.jsonc b/pkg/github/testdata/commit_files.golden.jsonc new file mode 100644 index 00000000..485cbcc7 --- /dev/null +++ b/pkg/github/testdata/commit_files.golden.jsonc @@ -0,0 +1,111 @@ +// 🌟 This was machine generated. Do not edit. 🌟 +// +// Frame[0] { +// "typeVersion": [ +// 0, +// 0 +// ], +// "preferredVisualisationType": "table" +// } +// Name: commit_files +// Dimensions: 6 Fields by 2 Rows +// +----------------+-----------------+-----------------+---------------+----------------+-------------------------+ +// | Name: path | Name: additions | Name: deletions | Name: changes | Name: status | Name: previous_filename | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []int64 | Type: []int64 | Type: []int64 | Type: []string | Type: []string | +// +----------------+-----------------+-----------------+---------------+----------------+-------------------------+ +// | src/main.go | 10 | 2 | 12 | modified | | +// | src/renamed.go | 0 | 0 | 0 | renamed | src/old.go | +// +----------------+-----------------+-----------------+---------------+----------------+-------------------------+ +// +// +// 🌟 This was machine generated. Do not edit. 🌟 +{ + "status": 200, + "frames": [ + { + "schema": { + "name": "commit_files", + "meta": { + "typeVersion": [ + 0, + 0 + ], + "preferredVisualisationType": "table" + }, + "fields": [ + { + "name": "path", + "type": "string", + "typeInfo": { + "frame": "string" + } + }, + { + "name": "additions", + "type": "number", + "typeInfo": { + "frame": "int64" + } + }, + { + "name": "deletions", + "type": "number", + "typeInfo": { + "frame": "int64" + } + }, + { + "name": "changes", + "type": "number", + "typeInfo": { + "frame": "int64" + } + }, + { + "name": "status", + "type": "string", + "typeInfo": { + "frame": "string" + } + }, + { + "name": "previous_filename", + "type": "string", + "typeInfo": { + "frame": "string" + } + } + ] + }, + "data": { + "values": [ + [ + "src/main.go", + "src/renamed.go" + ], + [ + 10, + 0 + ], + [ + 2, + 0 + ], + [ + 12, + 0 + ], + [ + "modified", + "renamed" + ], + [ + "", + "src/old.go" + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/pkg/github/testdata/commits.golden.jsonc b/pkg/github/testdata/commits.golden.jsonc index 6bfcce5c..f341b555 100644 --- a/pkg/github/testdata/commits.golden.jsonc +++ b/pkg/github/testdata/commits.golden.jsonc @@ -3,14 +3,14 @@ // Frame[0] // Name: commits // Dimensions: 8 Fields by 2 Rows -// +----------------+-----------------+--------------------+--------------------+----------------------+-------------------------------+-------------------------------+----------------+ -// | Name: id | Name: author | Name: author_login | Name: author_email | Name: author_company | Name: committed_at | Name: pushed_at | Name: message | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []time.Time | Type: []time.Time | Type: []string | -// +----------------+-----------------+--------------------+--------------------+----------------------+-------------------------------+-------------------------------+----------------+ -// | | firstCommitter | firstCommitter | first@example.com | ACME Corp | 2020-08-25 16:21:56 +0000 UTC | 2020-08-25 16:23:56 +0000 UTC | commit #1 | -// | | secondCommitter | secondCommitter | second@example.com | ACME Corp | 2020-08-25 17:21:56 +0000 UTC | 2020-08-25 18:21:56 +0000 UTC | commit #2 | -// +----------------+-----------------+--------------------+--------------------+----------------------+-------------------------------+-------------------------------+----------------+ +// +----------------+-----------------+--------------------+--------------------+----------------------+---------------------------------+---------------------------------+----------------+ +// | Name: id | Name: author | Name: author_login | Name: author_email | Name: author_company | Name: committed_at | Name: pushed_at | Name: message | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []time.Time | Type: []time.Time | Type: []string | +// +----------------+-----------------+--------------------+--------------------+----------------------+---------------------------------+---------------------------------+----------------+ +// | | firstCommitter | firstCommitter | first@example.com | ACME Corp | 2020-08-25 16:21:56 +0000 +0000 | 2020-08-25 16:23:56 +0000 +0000 | commit #1 | +// | | secondCommitter | secondCommitter | second@example.com | ACME Corp | 2020-08-25 17:21:56 +0000 +0000 | 2020-08-25 18:21:56 +0000 +0000 | commit #2 | +// +----------------+-----------------+--------------------+--------------------+----------------------+---------------------------------+---------------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 diff --git a/pkg/github/testdata/issues.golden.jsonc b/pkg/github/testdata/issues.golden.jsonc index 1dc3671a..c652263d 100644 --- a/pkg/github/testdata/issues.golden.jsonc +++ b/pkg/github/testdata/issues.golden.jsonc @@ -3,15 +3,15 @@ // Frame[0] // Name: issues // Dimensions: 11 Fields by 3 Rows -// +----------------+----------------+----------------------+-----------------+---------------+--------------+-------------------------------+-------------------------------+-------------------------------+-------------------------+----------------------------+ -// | Name: title | Name: author | Name: author_company | Name: repo | Name: number | Name: closed | Name: created_at | Name: closed_at | Name: updated_at | Name: labels | Name: assignees | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []int64 | Type: []bool | Type: []time.Time | Type: []*time.Time | Type: []time.Time | Type: []json.RawMessage | Type: []json.RawMessage | -// +----------------+----------------+----------------------+-----------------+---------------+--------------+-------------------------------+-------------------------------+-------------------------------+-------------------------+----------------------------+ -// | Issue #1 | firstUser | ACME Corp | grafana/grafana | 1 | false | 2020-08-25 16:21:56 +0000 UTC | null | 2020-08-25 16:21:56 +0000 UTC | ["bug","help wanted"] | ["firstUser","secondUser"] | -// | Issue #2 | secondUser | ACME Corp | grafana/grafana | 2 | true | 2020-08-25 16:21:56 +0000 UTC | 2020-08-25 22:21:56 +0000 UTC | 2020-08-25 22:21:56 +0000 UTC | ["enhancement"] | ["firstUser"] | -// | Issue #3 | firstUser | ACME Corp | grafana/grafana | 3 | false | 2020-08-25 16:21:56 +0000 UTC | null | 2020-08-25 16:21:56 +0000 UTC | [] | [] | -// +----------------+----------------+----------------------+-----------------+---------------+--------------+-------------------------------+-------------------------------+-------------------------------+-------------------------+----------------------------+ +// +----------------+----------------+----------------------+-----------------+---------------+--------------+---------------------------------+---------------------------------+---------------------------------+-------------------------+----------------------------+ +// | Name: title | Name: author | Name: author_company | Name: repo | Name: number | Name: closed | Name: created_at | Name: closed_at | Name: updated_at | Name: labels | Name: assignees | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []int64 | Type: []bool | Type: []time.Time | Type: []*time.Time | Type: []time.Time | Type: []json.RawMessage | Type: []json.RawMessage | +// +----------------+----------------+----------------------+-----------------+---------------+--------------+---------------------------------+---------------------------------+---------------------------------+-------------------------+----------------------------+ +// | Issue #1 | firstUser | ACME Corp | grafana/grafana | 1 | false | 2020-08-25 16:21:56 +0000 +0000 | null | 2020-08-25 16:21:56 +0000 +0000 | ["bug","help wanted"] | ["firstUser","secondUser"] | +// | Issue #2 | secondUser | ACME Corp | grafana/grafana | 2 | true | 2020-08-25 16:21:56 +0000 +0000 | 2020-08-25 22:21:56 +0000 +0000 | 2020-08-25 22:21:56 +0000 +0000 | ["enhancement"] | ["firstUser"] | +// | Issue #3 | firstUser | ACME Corp | grafana/grafana | 3 | false | 2020-08-25 16:21:56 +0000 +0000 | null | 2020-08-25 16:21:56 +0000 +0000 | [] | [] | +// +----------------+----------------+----------------------+-----------------+---------------+--------------+---------------------------------+---------------------------------+---------------------------------+-------------------------+----------------------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 diff --git a/pkg/github/testdata/milestones.golden.jsonc b/pkg/github/testdata/milestones.golden.jsonc index a145267c..2a4ec5db 100644 --- a/pkg/github/testdata/milestones.golden.jsonc +++ b/pkg/github/testdata/milestones.golden.jsonc @@ -3,15 +3,15 @@ // Frame[0] // Name: milestones // Dimensions: 7 Fields by 3 Rows -// +------------------+----------------+--------------+----------------+-------------------------------+-------------------------------+-------------------------------+ -// | Name: title | Name: author | Name: closed | Name: state | Name: created_at | Name: closed_at | Name: due_at | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []bool | Type: []string | Type: []time.Time | Type: []*time.Time | Type: []*time.Time | -// +------------------+----------------+--------------+----------------+-------------------------------+-------------------------------+-------------------------------+ -// | first milestone | testUser | false | OPEN | 2020-08-25 16:21:56 +0000 UTC | null | 2020-08-29 20:21:56 +0000 UTC | -// | second milestone | testUser2 | true | CLOSED | 2020-08-25 16:21:56 +0000 UTC | 2020-08-26 02:21:56 +0000 UTC | 2020-08-29 20:21:56 +0000 UTC | -// | third milestone | testUser2 | false | OPEN | 2020-08-25 16:21:56 +0000 UTC | null | 2020-08-30 16:21:56 +0000 UTC | -// +------------------+----------------+--------------+----------------+-------------------------------+-------------------------------+-------------------------------+ +// +------------------+----------------+--------------+----------------+---------------------------------+---------------------------------+---------------------------------+ +// | Name: title | Name: author | Name: closed | Name: state | Name: created_at | Name: closed_at | Name: due_at | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []bool | Type: []string | Type: []time.Time | Type: []*time.Time | Type: []*time.Time | +// +------------------+----------------+--------------+----------------+---------------------------------+---------------------------------+---------------------------------+ +// | first milestone | testUser | false | OPEN | 2020-08-25 16:21:56 +0000 +0000 | null | 2020-08-29 20:21:56 +0000 +0000 | +// | second milestone | testUser2 | true | CLOSED | 2020-08-25 16:21:56 +0000 +0000 | 2020-08-26 02:21:56 +0000 +0000 | 2020-08-29 20:21:56 +0000 +0000 | +// | third milestone | testUser2 | false | OPEN | 2020-08-25 16:21:56 +0000 +0000 | null | 2020-08-30 16:21:56 +0000 +0000 | +// +------------------+----------------+--------------+----------------+---------------------------------+---------------------------------+---------------------------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 diff --git a/pkg/github/testdata/pull_request_reviews.golden.jsonc b/pkg/github/testdata/pull_request_reviews.golden.jsonc index be0d2886..b8264c69 100644 --- a/pkg/github/testdata/pull_request_reviews.golden.jsonc +++ b/pkg/github/testdata/pull_request_reviews.golden.jsonc @@ -3,16 +3,16 @@ // 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 UTC | 2020-08-25 16:21:56 +0000 UTC | -// | 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 UTC | 2020-08-25 16:21:56 +0000 UTC | -// | 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 UTC | 2020-08-25 16:21:56 +0000 UTC | -// | 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 UTC | 2020-08-25 16:21:56 +0000 UTC | -// +---------------------------+--------------------------+--------------------------+------------------------------------------------------+--------------------------------+---------------------------------+---------------------------------+-----------------------------------+---------------------------+--------------------------+---------------------------+---------------------------+-----------------------------+----------------------------------------------------------------------------------+--------------------+----------------------------+-------------------------------+-------------------------------+ +// +---------------------------+--------------------------+--------------------------+------------------------------------------------------+--------------------------------+---------------------------------+---------------------------------+-----------------------------------+---------------------------+--------------------------+---------------------------+---------------------------+-----------------------------+----------------------------------------------------------------------------------+--------------------+----------------------------+---------------------------------+---------------------------------+ +// | 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. 🌟 diff --git a/pkg/github/testdata/pull_requests.golden.jsonc b/pkg/github/testdata/pull_requests.golden.jsonc index f9d96967..3dc085d9 100644 --- a/pkg/github/testdata/pull_requests.golden.jsonc +++ b/pkg/github/testdata/pull_requests.golden.jsonc @@ -3,15 +3,15 @@ // Frame[0] // Name: pull_requests // Dimensions: 26 Fields by 3 Rows -// +---------------+----------------+------------------------------------------------------+-----------------+-----------------+---------------------------+----------------+-------------------+--------------------+--------------------+----------------------+--------------+----------------+--------------+--------------+-----------------+-------------------------------+-------------------------------+----------------------+-----------------------+-----------------------+-------------------------+-------------------------------+-------------------------------+-----------------+-------------------------+ -// | Name: number | Name: title | Name: url | Name: additions | Name: deletions | Name: repository | Name: state | Name: author_name | Name: author_login | Name: author_email | Name: author_company | Name: closed | Name: is_draft | Name: locked | Name: merged | Name: mergeable | Name: closed_at | Name: merged_at | Name: merged_by_name | Name: merged_by_login | Name: merged_by_email | Name: merged_by_company | Name: updated_at | Name: created_at | Name: open_time | Name: labels | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | 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: []int64 | Type: []int64 | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []bool | Type: []bool | Type: []bool | Type: []bool | Type: []string | Type: []*time.Time | Type: []*time.Time | Type: []*string | Type: []*string | Type: []*string | Type: []*string | Type: []time.Time | Type: []time.Time | Type: []float64 | Type: []json.RawMessage | -// +---------------+----------------+------------------------------------------------------+-----------------+-----------------+---------------------------+----------------+-------------------+--------------------+--------------------+----------------------+--------------+----------------+--------------+--------------+-----------------+-------------------------------+-------------------------------+----------------------+-----------------------+-----------------------+-------------------------+-------------------------------+-------------------------------+-----------------+-------------------------+ -// | 1 | PullRequest #1 | https://github.com/grafana/github-datasource/pulls/1 | 5 | 1 | grafana/github-datasource | OPEN | Test User | testUser | user@example.com | ACME corp | true | false | false | true | MERGEABLE | 2020-08-25 18:01:56 +0000 UTC | 2020-08-25 18:01:56 +0000 UTC | null | null | null | null | 2020-08-25 16:21:56 +0000 UTC | 2020-08-25 16:21:56 +0000 UTC | 6000 | ["bug","enhancement"] | -// | 2 | PullRequest #2 | https://github.com/grafana/github-datasource/pulls/2 | 0 | 0 | grafana/github-datasource | OPEN | Second User | testUser2 | user2@example.com | ACME corp | true | false | false | true | MERGEABLE | 2020-08-25 18:01:56 +0000 UTC | 2020-08-25 18:01:56 +0000 UTC | Test User | testUser | user@example.com | ACME corp | 2020-08-25 18:21:56 +0000 UTC | 2020-08-25 16:21:56 +0000 UTC | 6000 | ["documentation"] | -// | 3 | PullRequest #2 | https://github.com/grafana/github-datasource/pulls/3 | 0 | 0 | grafana/github-datasource | OPEN | Second User | testUser2 | user2@example.com | ACME corp | false | false | false | false | MERGEABLE | null | 2020-08-25 18:01:56 +0000 UTC | null | null | null | null | 2020-08-25 18:21:56 +0000 UTC | 2020-08-25 16:21:56 +0000 UTC | 6000 | [] | -// +---------------+----------------+------------------------------------------------------+-----------------+-----------------+---------------------------+----------------+-------------------+--------------------+--------------------+----------------------+--------------+----------------+--------------+--------------+-----------------+-------------------------------+-------------------------------+----------------------+-----------------------+-----------------------+-------------------------+-------------------------------+-------------------------------+-----------------+-------------------------+ +// +---------------+----------------+------------------------------------------------------+-----------------+-----------------+---------------------------+----------------+-------------------+--------------------+--------------------+----------------------+--------------+----------------+--------------+--------------+-----------------+---------------------------------+---------------------------------+----------------------+-----------------------+-----------------------+-------------------------+---------------------------------+---------------------------------+-----------------+-------------------------+ +// | Name: number | Name: title | Name: url | Name: additions | Name: deletions | Name: repository | Name: state | Name: author_name | Name: author_login | Name: author_email | Name: author_company | Name: closed | Name: is_draft | Name: locked | Name: merged | Name: mergeable | Name: closed_at | Name: merged_at | Name: merged_by_name | Name: merged_by_login | Name: merged_by_email | Name: merged_by_company | Name: updated_at | Name: created_at | Name: open_time | Name: labels | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | 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: []int64 | Type: []int64 | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []bool | Type: []bool | Type: []bool | Type: []bool | Type: []string | Type: []*time.Time | Type: []*time.Time | Type: []*string | Type: []*string | Type: []*string | Type: []*string | Type: []time.Time | Type: []time.Time | Type: []float64 | Type: []json.RawMessage | +// +---------------+----------------+------------------------------------------------------+-----------------+-----------------+---------------------------+----------------+-------------------+--------------------+--------------------+----------------------+--------------+----------------+--------------+--------------+-----------------+---------------------------------+---------------------------------+----------------------+-----------------------+-----------------------+-------------------------+---------------------------------+---------------------------------+-----------------+-------------------------+ +// | 1 | PullRequest #1 | https://github.com/grafana/github-datasource/pulls/1 | 5 | 1 | grafana/github-datasource | OPEN | Test User | testUser | user@example.com | ACME corp | true | false | false | true | MERGEABLE | 2020-08-25 18:01:56 +0000 +0000 | 2020-08-25 18:01:56 +0000 +0000 | null | null | null | null | 2020-08-25 16:21:56 +0000 +0000 | 2020-08-25 16:21:56 +0000 +0000 | 6000 | ["bug","enhancement"] | +// | 2 | PullRequest #2 | https://github.com/grafana/github-datasource/pulls/2 | 0 | 0 | grafana/github-datasource | OPEN | Second User | testUser2 | user2@example.com | ACME corp | true | false | false | true | MERGEABLE | 2020-08-25 18:01:56 +0000 +0000 | 2020-08-25 18:01:56 +0000 +0000 | Test User | testUser | user@example.com | ACME corp | 2020-08-25 18:21:56 +0000 +0000 | 2020-08-25 16:21:56 +0000 +0000 | 6000 | ["documentation"] | +// | 3 | PullRequest #2 | https://github.com/grafana/github-datasource/pulls/3 | 0 | 0 | grafana/github-datasource | OPEN | Second User | testUser2 | user2@example.com | ACME corp | false | false | false | false | MERGEABLE | null | 2020-08-25 18:01:56 +0000 +0000 | null | null | null | null | 2020-08-25 18:21:56 +0000 +0000 | 2020-08-25 16:21:56 +0000 +0000 | 6000 | [] | +// +---------------+----------------+------------------------------------------------------+-----------------+-----------------+---------------------------+----------------+-------------------+--------------------+--------------------+----------------------+--------------+----------------+--------------+--------------+-----------------+---------------------------------+---------------------------------+----------------------+-----------------------+-----------------------+-------------------------+---------------------------------+---------------------------------+-----------------+-------------------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 diff --git a/pkg/github/testdata/releases.golden.jsonc b/pkg/github/testdata/releases.golden.jsonc index ab36f5e7..315cfdc9 100644 --- a/pkg/github/testdata/releases.golden.jsonc +++ b/pkg/github/testdata/releases.golden.jsonc @@ -3,14 +3,14 @@ // Frame[0] // Name: releases // Dimensions: 8 Fields by 2 Rows -// +----------------+------------------+----------------+---------------------+----------------+----------------------------+-------------------------------+-------------------------------+ -// | Name: name | Name: created_by | Name: is_draft | Name: is_prerelease | Name: tag | Name: url | Name: created_at | Name: published_at | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []bool | Type: []bool | Type: []string | Type: []string | Type: []time.Time | Type: []*time.Time | -// +----------------+------------------+----------------+---------------------+----------------+----------------------------+-------------------------------+-------------------------------+ -// | Release #1 | exampleUser | true | false | v1.0.0 | https://example.com/v1.0.0 | 2020-08-25 16:21:56 +0000 UTC | null | -// | Release #2 | exampleUser | true | false | v1.1.0 | https://example.com/v1.1.0 | 2020-08-25 16:21:56 +0000 UTC | 2020-08-25 17:21:56 +0000 UTC | -// +----------------+------------------+----------------+---------------------+----------------+----------------------------+-------------------------------+-------------------------------+ +// +----------------+------------------+----------------+---------------------+----------------+----------------------------+---------------------------------+---------------------------------+ +// | Name: name | Name: created_by | Name: is_draft | Name: is_prerelease | Name: tag | Name: url | Name: created_at | Name: published_at | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []bool | Type: []bool | Type: []string | Type: []string | Type: []time.Time | Type: []*time.Time | +// +----------------+------------------+----------------+---------------------+----------------+----------------------------+---------------------------------+---------------------------------+ +// | Release #1 | exampleUser | true | false | v1.0.0 | https://example.com/v1.0.0 | 2020-08-25 16:21:56 +0000 +0000 | null | +// | Release #2 | exampleUser | true | false | v1.1.0 | https://example.com/v1.1.0 | 2020-08-25 16:21:56 +0000 +0000 | 2020-08-25 17:21:56 +0000 +0000 | +// +----------------+------------------+----------------+---------------------+----------------+----------------------------+---------------------------------+---------------------------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 diff --git a/pkg/github/testdata/repositories.golden.jsonc b/pkg/github/testdata/repositories.golden.jsonc index 2742c235..061d7d87 100644 --- a/pkg/github/testdata/repositories.golden.jsonc +++ b/pkg/github/testdata/repositories.golden.jsonc @@ -3,14 +3,14 @@ // Frame[0] // Name: repositories // Dimensions: 9 Fields by 2 Rows -// +----------------+----------------+-----------------------+----------------------------+---------------+---------------+-----------------+------------------+-------------------------------+ -// | Name: name | Name: owner | Name: name_with_owner | Name: url | Name: forks | Name: is_fork | Name: is_mirror | Name: is_private | Name: created_at | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []int64 | Type: []bool | Type: []bool | Type: []bool | Type: []time.Time | -// +----------------+----------------+-----------------------+----------------------------+---------------+---------------+-----------------+------------------+-------------------------------+ -// | grafana | grafana | grafana/grafana | github.com/grafana/grafana | 10 | true | true | false | 2020-08-25 16:21:56 +0000 UTC | -// | loki | grafana | grafana/loki | github.com/grafana/loki | 12 | true | true | false | 2020-08-25 16:21:56 +0000 UTC | -// +----------------+----------------+-----------------------+----------------------------+---------------+---------------+-----------------+------------------+-------------------------------+ +// +----------------+----------------+-----------------------+----------------------------+---------------+---------------+-----------------+------------------+---------------------------------+ +// | Name: name | Name: owner | Name: name_with_owner | Name: url | Name: forks | Name: is_fork | Name: is_mirror | Name: is_private | Name: created_at | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []int64 | Type: []bool | Type: []bool | Type: []bool | Type: []time.Time | +// +----------------+----------------+-----------------------+----------------------------+---------------+---------------+-----------------+------------------+---------------------------------+ +// | grafana | grafana | grafana/grafana | github.com/grafana/grafana | 10 | true | true | false | 2020-08-25 16:21:56 +0000 +0000 | +// | loki | grafana | grafana/loki | github.com/grafana/loki | 12 | true | true | false | 2020-08-25 16:21:56 +0000 +0000 | +// +----------------+----------------+-----------------------+----------------------------+---------------+---------------+-----------------+------------------+---------------------------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 diff --git a/pkg/github/testdata/stargazers.golden.jsonc b/pkg/github/testdata/stargazers.golden.jsonc index c681abe3..40f8a583 100644 --- a/pkg/github/testdata/stargazers.golden.jsonc +++ b/pkg/github/testdata/stargazers.golden.jsonc @@ -9,15 +9,15 @@ // } // Name: stargazers // Dimensions: 8 Fields by 3 Rows -// +-------------------------------+------------------+----------------+----------------+----------------+----------------+----------------------------+---------------------------------------------+ -// | Name: starred_at | Name: star_count | Name: id | Name: login | Name: git_name | Name: company | Name: email | Name: url | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []time.Time | Type: []int64 | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | -// +-------------------------------+------------------+----------------+----------------+----------------+----------------+----------------------------+---------------------------------------------+ -// | 2023-01-14 10:21:41 +0000 UTC | 3 | NEVER | gonna | Run | Around | and_desert_you@example.org | https://www.youtube.com/watch?v=dQw4w9WgXcQ | -// | 2023-01-14 10:23:41 +0000 UTC | 2 | NEVER | gonna | Let | You | down@example.org | | -// | 2023-01-14 10:25:41 +0000 UTC | 1 | NEVER | gonna | Give | You | up@example.org | | -// +-------------------------------+------------------+----------------+----------------+----------------+----------------+----------------------------+---------------------------------------------+ +// +---------------------------------+------------------+----------------+----------------+----------------+----------------+----------------------------+---------------------------------------------+ +// | Name: starred_at | Name: star_count | Name: id | Name: login | Name: git_name | Name: company | Name: email | Name: url | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []time.Time | Type: []int64 | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | +// +---------------------------------+------------------+----------------+----------------+----------------+----------------+----------------------------+---------------------------------------------+ +// | 2023-01-14 10:21:41 +0000 +0000 | 3 | NEVER | gonna | Run | Around | and_desert_you@example.org | https://www.youtube.com/watch?v=dQw4w9WgXcQ | +// | 2023-01-14 10:23:41 +0000 +0000 | 2 | NEVER | gonna | Let | You | down@example.org | | +// | 2023-01-14 10:25:41 +0000 +0000 | 1 | NEVER | gonna | Give | You | up@example.org | | +// +---------------------------------+------------------+----------------+----------------+----------------+----------------+----------------------------+---------------------------------------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 diff --git a/pkg/github/testdata/tags.golden.jsonc b/pkg/github/testdata/tags.golden.jsonc index 7071a1e0..e3e3ac7f 100644 --- a/pkg/github/testdata/tags.golden.jsonc +++ b/pkg/github/testdata/tags.golden.jsonc @@ -3,14 +3,14 @@ // Frame[0] // Name: tags // Dimensions: 7 Fields by 2 Rows -// +----------------+----------------+-----------------+--------------------+--------------------+----------------------+-------------------------------+ -// | Name: name | Name: id | Name: author | Name: author_login | Name: author_email | Name: author_company | Name: date | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []time.Time | -// +----------------+----------------+-----------------+--------------------+--------------------+----------------------+-------------------------------+ -// | v1.0.0 | | First Committer | firstCommitter | first@example.com | ACME Corp | 2020-08-25 16:21:56 +0000 UTC | -// | v1.1.0 | | First Committer | firstCommitter | first@example.com | ACME Corp | 2020-08-25 16:21:56 +0000 UTC | -// +----------------+----------------+-----------------+--------------------+--------------------+----------------------+-------------------------------+ +// +----------------+----------------+-----------------+--------------------+--------------------+----------------------+---------------------------------+ +// | Name: name | Name: id | Name: author | Name: author_login | Name: author_email | Name: author_company | Name: date | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []time.Time | +// +----------------+----------------+-----------------+--------------------+--------------------+----------------------+---------------------------------+ +// | v1.0.0 | | First Committer | firstCommitter | first@example.com | ACME Corp | 2020-08-25 16:21:56 +0000 +0000 | +// | v1.1.0 | | First Committer | firstCommitter | first@example.com | ACME Corp | 2020-08-25 16:21:56 +0000 +0000 | +// +----------------+----------------+-----------------+--------------------+--------------------+----------------------+---------------------------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 diff --git a/pkg/models/client.go b/pkg/models/client.go index 5c27005d..655cd047 100644 --- a/pkg/models/client.go +++ b/pkg/models/client.go @@ -16,4 +16,6 @@ type Client interface { GetWorkflowRuns(ctx context.Context, owner, repo, workflow string, branch string, timeRange backend.TimeRange) ([]*googlegithub.WorkflowRun, error) ListAlertsForRepo(ctx context.Context, owner, repo string, opts *googlegithub.AlertListOptions) ([]*googlegithub.Alert, *googlegithub.Response, error) ListAlertsForOrg(ctx context.Context, owner string, opts *googlegithub.AlertListOptions) ([]*googlegithub.Alert, *googlegithub.Response, error) + GetCommitFiles(ctx context.Context, owner, repo, sha string, opts *googlegithub.ListOptions) ([]*googlegithub.CommitFile, *googlegithub.Response, error) + ListPullRequestFiles(ctx context.Context, owner, repo string, prNumber int, opts *googlegithub.ListOptions) ([]*googlegithub.CommitFile, *googlegithub.Response, error) } diff --git a/pkg/models/commit_files.go b/pkg/models/commit_files.go new file mode 100644 index 00000000..036b297f --- /dev/null +++ b/pkg/models/commit_files.go @@ -0,0 +1,35 @@ +package models + +// CommitFilesOptions provides options when retrieving files changed in a commit +type CommitFilesOptions struct { + Owner string `json:"owner"` + Repository string `json:"repository"` + // Ref is the commit SHA to retrieve changed files for + Ref string `json:"commitSha"` +} + +// CommitFilesOptionsWithRepo adds Owner and Repository to CommitFilesOptions +func CommitFilesOptionsWithRepo(opt CommitFilesOptions, owner, repo string) CommitFilesOptions { + return CommitFilesOptions{ + Owner: owner, + Repository: repo, + Ref: opt.Ref, + } +} + +// PullRequestFilesOptions provides options when retrieving files changed in a pull request +type PullRequestFilesOptions struct { + Owner string `json:"owner"` + Repository string `json:"repository"` + // PRNumber is the pull request number + PRNumber int64 `json:"prNumber"` +} + +// PullRequestFilesOptionsWithRepo adds Owner and Repository to PullRequestFilesOptions +func PullRequestFilesOptionsWithRepo(opt PullRequestFilesOptions, owner, repo string) PullRequestFilesOptions { + return PullRequestFilesOptions{ + Owner: owner, + Repository: repo, + PRNumber: opt.PRNumber, + } +} diff --git a/pkg/models/commits.go b/pkg/models/commits.go index 35faaf21..ad61b42d 100644 --- a/pkg/models/commits.go +++ b/pkg/models/commits.go @@ -2,16 +2,18 @@ package models // ListCommitsOptions provides options when retrieving commits type ListCommitsOptions struct { - Repository string `json:"repository"` - Owner string `json:"owner"` - Ref string `json:"gitRef"` + Repository string `json:"repository"` + Owner string `json:"owner"` + Ref string `json:"gitRef"` + IncludeFiles bool `json:"includeFiles"` } // CommitsOptionsWithRepo adds Owner and Repo to a ListCommitsOptions. This is just for convenience func CommitsOptionsWithRepo(opt ListCommitsOptions, owner string, repo string) ListCommitsOptions { return ListCommitsOptions{ - Owner: owner, - Repository: repo, - Ref: opt.Ref, + Owner: owner, + Repository: repo, + Ref: opt.Ref, + IncludeFiles: opt.IncludeFiles, } } diff --git a/pkg/models/query.go b/pkg/models/query.go index 88292805..1fefa643 100644 --- a/pkg/models/query.go +++ b/pkg/models/query.go @@ -43,6 +43,10 @@ const ( QueryTypeWorkflowRuns = "Workflow_Runs" // QueryTypeCodeScanning is used when querying code scanning alerts for a repository QueryTypeCodeScanning = "Code_Scanning" + // QueryTypeCommitFiles is used when querying files changed in a specific commit + QueryTypeCommitFiles = "Commit_Files" + // QueryTypePullRequestFiles is used when querying files changed in a specific pull request + QueryTypePullRequestFiles = "Pull_Request_Files" ) // Query refers to the structure of a query built using the QueryEditor. @@ -153,3 +157,15 @@ type CodeScanningQuery struct { Query Options CodeScanningOptions `json:"options"` } + +// CommitFilesQuery is used when querying for files changed in a GitHub commit +type CommitFilesQuery struct { + Query + Options CommitFilesOptions `json:"options"` +} + +// PullRequestFilesQuery is used when querying for files changed in a GitHub pull request +type PullRequestFilesQuery struct { + Query + Options PullRequestFilesOptions `json:"options"` +} diff --git a/pkg/plugin/datasource.go b/pkg/plugin/datasource.go index ee7ce469..e1fa195e 100644 --- a/pkg/plugin/datasource.go +++ b/pkg/plugin/datasource.go @@ -15,6 +15,8 @@ type Datasource interface { HandleIssuesQuery(context.Context, *models.IssuesQuery, backend.DataQuery) (dfutil.Framer, error) HandleCommitsQuery(context.Context, *models.CommitsQuery, backend.DataQuery) (dfutil.Framer, error) HandleCodeScanningQuery(context.Context, *models.CodeScanningQuery, backend.DataQuery) (dfutil.Framer, error) + HandleCommitFilesQuery(context.Context, *models.CommitFilesQuery, backend.DataQuery) (dfutil.Framer, error) + HandlePullRequestFilesQuery(context.Context, *models.PullRequestFilesQuery, backend.DataQuery) (dfutil.Framer, error) HandleTagsQuery(context.Context, *models.TagsQuery, backend.DataQuery) (dfutil.Framer, error) HandleReleasesQuery(context.Context, *models.ReleasesQuery, backend.DataQuery) (dfutil.Framer, error) HandleContributorsQuery(context.Context, *models.ContributorsQuery, backend.DataQuery) (dfutil.Framer, error) diff --git a/pkg/plugin/datasource_caching.go b/pkg/plugin/datasource_caching.go index 1c19970f..546ad8fe 100644 --- a/pkg/plugin/datasource_caching.go +++ b/pkg/plugin/datasource_caching.go @@ -262,6 +262,26 @@ func (c *CachedDatasource) HandleWorkflowRunsQuery(ctx context.Context, q *model return c.saveCache(req, f, err) } +// HandleCommitFilesQuery is the cache wrapper for the commit files query handler +func (c *CachedDatasource) HandleCommitFilesQuery(ctx context.Context, q *models.CommitFilesQuery, req backend.DataQuery) (dfutil.Framer, error) { + if value, err := c.getCache(req); err == nil { + return value, err + } + + f, err := c.datasource.HandleCommitFilesQuery(ctx, q, req) + return c.saveCache(req, f, err) +} + +// HandlePullRequestFilesQuery is the cache wrapper for the pull request files query handler +func (c *CachedDatasource) HandlePullRequestFilesQuery(ctx context.Context, q *models.PullRequestFilesQuery, req backend.DataQuery) (dfutil.Framer, error) { + if value, err := c.getCache(req); err == nil { + return value, err + } + + f, err := c.datasource.HandlePullRequestFilesQuery(ctx, q, req) + return c.saveCache(req, f, err) +} + // CheckHealth forwards the request to the datasource and does not perform any caching func (c *CachedDatasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { return c.datasource.CheckHealth(ctx, req) diff --git a/pkg/testutil/client.go b/pkg/testutil/client.go index 1a4cf2da..9f211e74 100644 --- a/pkg/testutil/client.go +++ b/pkg/testutil/client.go @@ -76,3 +76,13 @@ func (c *TestClient) ListAlertsForRepo(ctx context.Context, owner, repo string, func (c *TestClient) ListAlertsForOrg(ctx context.Context, owner string, opts *googlegithub.AlertListOptions) ([]*googlegithub.Alert, *googlegithub.Response, error) { panic("unimplemented") } + +// GetCommitFiles is not implemented because it is not being used in tests at the moment. +func (c *TestClient) GetCommitFiles(ctx context.Context, owner, repo, sha string, opts *googlegithub.ListOptions) ([]*googlegithub.CommitFile, *googlegithub.Response, error) { + panic("unimplemented") +} + +// ListPullRequestFiles is not implemented because it is not being used in tests at the moment. +func (c *TestClient) ListPullRequestFiles(ctx context.Context, owner, repo string, prNumber int, opts *googlegithub.ListOptions) ([]*googlegithub.CommitFile, *googlegithub.Response, error) { + panic("unimplemented") +} diff --git a/src/constants.ts b/src/constants.ts index 4f86d8ef..e92b87eb 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -20,6 +20,8 @@ export enum QueryType { Workflows = 'Workflows', Workflow_Usage = 'Workflow_Usage', Workflow_Runs = 'Workflow_Runs', + Commit_Files = 'Commit_Files', + Pull_Request_Files = 'Pull_Request_Files', } export const DefaultQueryType = QueryType.Issues; diff --git a/src/types/query.ts b/src/types/query.ts index 1694020f..1a6e9bb0 100644 --- a/src/types/query.ts +++ b/src/types/query.ts @@ -20,7 +20,9 @@ export interface GitHubQuery extends Indexable, DataQuery, RepositoryOptions { | ProjectsOptions | WorkflowsOptions | WorkflowUsageOptions - | WorkflowRunsOptions; + | WorkflowRunsOptions + | CommitFilesOptions + | PullRequestFilesOptions; } export interface Label { @@ -54,6 +56,15 @@ export interface CodeScanningOptions extends Indexable { export interface CommitsOptions extends Indexable { gitRef?: string; + includeFiles?: boolean; +} + +export interface CommitFilesOptions extends Indexable { + commitSha?: string; +} + +export interface PullRequestFilesOptions extends Indexable { + prNumber?: number; } export interface ContributorsOptions extends Indexable { diff --git a/src/validation.ts b/src/validation.ts index a06c7e4c..8380ada1 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -10,6 +10,8 @@ export const isValid = (query: GitHubQuery): boolean => { } if ( query.queryType === QueryType.Commits || + query.queryType === QueryType.Commit_Files || + query.queryType === QueryType.Pull_Request_Files || query.queryType === QueryType.Contributors || query.queryType === QueryType.Tags || query.queryType === QueryType.Releases || diff --git a/src/views/QueryEditor.tsx b/src/views/QueryEditor.tsx index f94f60cd..3f71ba85 100644 --- a/src/views/QueryEditor.tsx +++ b/src/views/QueryEditor.tsx @@ -23,6 +23,8 @@ import QueryEditorWorkflows from './QueryEditorWorkflows'; import QueryEditorWorkflowUsage from './QueryEditorWorkflowUsage'; import QueryEditorWorkflowRuns from './QueryEditorWorkflowRuns'; import QueryEditorCodeScanning from './QueryEditorCodeScanning'; +import QueryEditorCommitFiles from './QueryEditorCommitFiles'; +import QueryEditorPullRequestFiles from './QueryEditorPullRequestFiles'; import { QueryType, DefaultQueryType } from '../constants'; import type { GitHubQuery } from '../types/query'; import type { GitHubDataSourceOptions } from '../types/config'; @@ -30,7 +32,7 @@ import type { GitHubDataSourceOptions } from '../types/config'; interface Props extends QueryEditorProps { queryTypes?: string[]; } -export const LeftColumnWidth = 10; +export const LeftColumnWidth = 12; export const RightColumnWidth = 36; /* eslint-disable react/display-name */ @@ -56,6 +58,16 @@ const queryEditors: { [QueryType.Code_Scanning]: { component: (props: Props, onChange: (val: any) => void) => , }, + [QueryType.Commit_Files]: { + component: (props: Props, onChange: (val: any) => void) => ( + + ), + }, + [QueryType.Pull_Request_Files]: { + component: (props: Props, onChange: (val: any) => void) => ( + + ), + }, [QueryType.Releases]: { component: (props: Props, _: (val: any) => void) => , }, diff --git a/src/views/QueryEditorCommitFiles.tsx b/src/views/QueryEditorCommitFiles.tsx new file mode 100644 index 00000000..1e436b18 --- /dev/null +++ b/src/views/QueryEditorCommitFiles.tsx @@ -0,0 +1,31 @@ +import React, { useState } from 'react'; +import { Input, InlineField } from '@grafana/ui'; +import { LeftColumnWidth, RightColumnWidth } from './QueryEditor'; +import type { CommitFilesOptions } from '../types/query'; + +interface Props extends CommitFilesOptions { + onChange: (value: CommitFilesOptions) => void; +} + +const QueryEditorCommitFiles = (props: Props) => { + const [commitSha, setCommitSha] = useState(props.commitSha || ''); + return ( + <> + + setCommitSha(el.currentTarget.value)} + onBlur={(el) => props.onChange({ ...props, commitSha: el.currentTarget.value })} + /> + + + ); +}; + +export default QueryEditorCommitFiles; diff --git a/src/views/QueryEditorCommits.tsx b/src/views/QueryEditorCommits.tsx index 9de38efc..7d3fe5dd 100644 --- a/src/views/QueryEditorCommits.tsx +++ b/src/views/QueryEditorCommits.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Input, InlineField } from '@grafana/ui'; +import { Input, InlineField, InlineSwitch } from '@grafana/ui'; import { RightColumnWidth, LeftColumnWidth } from './QueryEditor'; import { components } from 'components/selectors'; import type { CommitsOptions } from '../types/query'; @@ -21,6 +21,16 @@ const QueryEditorCommits = (props: Props) => { onBlur={(el) => props.onChange({ ...props, gitRef: el.currentTarget.value })} /> + + props.onChange({ ...props, includeFiles: el.currentTarget.checked })} + /> + ); }; diff --git a/src/views/QueryEditorPullRequestFiles.tsx b/src/views/QueryEditorPullRequestFiles.tsx new file mode 100644 index 00000000..e24517f0 --- /dev/null +++ b/src/views/QueryEditorPullRequestFiles.tsx @@ -0,0 +1,37 @@ +import React, { useState } from 'react'; +import { Input, InlineField } from '@grafana/ui'; +import { LeftColumnWidth, RightColumnWidth } from './QueryEditor'; +import type { PullRequestFilesOptions } from '../types/query'; + +interface Props extends PullRequestFilesOptions { + onChange: (value: PullRequestFilesOptions) => void; +} + +const QueryEditorPullRequestFiles = (props: Props) => { + const [prNumber, setPrNumber] = useState( + props.prNumber !== undefined ? String(props.prNumber) : '' + ); + return ( + <> + + setPrNumber(el.currentTarget.value)} + onBlur={(el) => { + const parsed = parseInt(el.currentTarget.value, 10); + props.onChange({ ...props, prNumber: isNaN(parsed) ? undefined : parsed }); + }} + /> + + + ); +}; + +export default QueryEditorPullRequestFiles;