Skip to content

Commit 28e8a40

Browse files
authored
Merge pull request #1 from flanksource/check-suite
add support for check runs
2 parents 8adfe39 + a75b9c9 commit 28e8a40

File tree

2 files changed

+284
-0
lines changed

2 files changed

+284
-0
lines changed
+283
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"io"
6+
"time"
7+
8+
"github.com/augmentable-dev/vtab"
9+
"github.com/shurcooL/githubv4"
10+
"go.riyazali.net/sqlite"
11+
"go.uber.org/zap"
12+
)
13+
14+
// fetch checkRun from the checkSuiteConnection
15+
type checkSuite struct {
16+
App struct {
17+
Name githubv4.String
18+
LogoUrl githubv4.URI
19+
}
20+
Branch struct {
21+
Name githubv4.String
22+
}
23+
Commit struct {
24+
Oid githubv4.GitObjectID
25+
}
26+
Creator struct {
27+
Login githubv4.String
28+
}
29+
Conclusion githubv4.CheckConclusionState
30+
CreatedAt githubv4.DateTime
31+
Repository struct {
32+
NameWithOwner string
33+
}
34+
ResourcePath githubv4.URI
35+
UpdatedAt githubv4.DateTime
36+
Url githubv4.URI
37+
WorkflowRun struct {
38+
Workflow struct {
39+
Name githubv4.String
40+
}
41+
}
42+
CheckRuns struct {
43+
Nodes []*checkRun
44+
} `graphql:"checkRuns(first: 50)"`
45+
}
46+
47+
type checkRunRow struct {
48+
checkRun
49+
AppName string
50+
AppLogoUrl string
51+
BranchName string
52+
CommitId string
53+
User string
54+
WorkflowName string
55+
WorkflowConclusion string
56+
Repository string
57+
}
58+
type checkRun struct {
59+
Name githubv4.String
60+
Title githubv4.String
61+
Conclusion githubv4.CheckConclusionState
62+
Summary githubv4.String
63+
Status githubv4.String
64+
StartedAt githubv4.DateTime
65+
CompletedAt githubv4.DateTime
66+
Url githubv4.URI
67+
}
68+
69+
type ref struct {
70+
Name string
71+
Target struct {
72+
Commit struct {
73+
CheckSuites struct {
74+
Nodes []*checkSuite
75+
PageInfo struct {
76+
EndCursor githubv4.String
77+
HasNextPage bool
78+
}
79+
} `graphql:"checkSuites(first: 50)"`
80+
} `graphql:"... on Commit"`
81+
}
82+
}
83+
84+
type fetchCheckSuiteResults struct {
85+
Edges []*checkRunRow
86+
HasNextPage bool
87+
EndCursor *githubv4.String
88+
}
89+
90+
type iterCheckSuites struct {
91+
*Options
92+
owner string
93+
name string
94+
current int
95+
results *fetchCheckSuiteResults
96+
}
97+
98+
func (i *iterCheckSuites) fetchCheckSuiteResults(ctx context.Context, startCursor *githubv4.String) (*fetchCheckSuiteResults, error) {
99+
var CheckSuiteQuery struct {
100+
Repository struct {
101+
Owner struct {
102+
Login string
103+
}
104+
Name string
105+
Refs struct {
106+
Nodes []*ref
107+
PageInfo struct {
108+
EndCursor githubv4.String
109+
HasNextPage bool
110+
}
111+
} `graphql:"refs(first: $perpage, after: $checkCursor, refPrefix: \"refs/heads/\")"`
112+
} `graphql:"repository(owner: $owner, name: $name)"`
113+
}
114+
115+
variables := map[string]interface{}{
116+
"owner": githubv4.String(i.owner),
117+
"name": githubv4.String(i.name),
118+
"perpage": githubv4.Int(i.PerPage),
119+
"checkCursor": startCursor,
120+
}
121+
err := i.Client().Query(ctx, &CheckSuiteQuery, variables)
122+
if err != nil {
123+
return nil, err
124+
}
125+
rows := getCheckRowsFromRefs(CheckSuiteQuery.Repository.Refs.Nodes)
126+
return &fetchCheckSuiteResults{rows, CheckSuiteQuery.Repository.Refs.PageInfo.HasNextPage, &CheckSuiteQuery.Repository.Refs.PageInfo.EndCursor}, nil
127+
}
128+
129+
func (i *iterCheckSuites) logger() *zap.SugaredLogger {
130+
logger := i.Logger.Sugar().With("per-page", i.PerPage, "owner", i.owner, "name", i.name)
131+
return logger
132+
}
133+
134+
func (i *iterCheckSuites) Next() (vtab.Row, error) {
135+
i.current += 1
136+
137+
if i.results == nil || i.current >= len(i.results.Edges) {
138+
if i.results == nil || i.results.HasNextPage {
139+
err := i.RateLimiter.Wait(context.Background())
140+
if err != nil {
141+
return nil, err
142+
}
143+
144+
var cursor *githubv4.String
145+
if i.results != nil {
146+
cursor = i.results.EndCursor
147+
}
148+
149+
i.logger().With("cursor", cursor).Infof("fetching page of repo_check_runs for %s/%s", i.owner, i.name)
150+
results, err := i.fetchCheckSuiteResults(context.Background(), cursor)
151+
if err != nil {
152+
return nil, err
153+
}
154+
155+
i.results = results
156+
i.current = 0
157+
158+
} else {
159+
return nil, io.EOF
160+
}
161+
}
162+
163+
return i, nil
164+
}
165+
166+
var checkCols = []vtab.Column{
167+
{Name: "owner", Type: "TEXT", NotNull: true, Hidden: true, Filters: []*vtab.ColumnFilter{{Op: sqlite.INDEX_CONSTRAINT_EQ, Required: true, OmitCheck: true}}},
168+
{Name: "reponame", Type: "TEXT", NotNull: true, Hidden: true, Filters: []*vtab.ColumnFilter{{Op: sqlite.INDEX_CONSTRAINT_EQ, OmitCheck: true}}},
169+
{Name: "name", Type: "TEXT"},
170+
{Name: "workflow_name", Type: "TEXT"},
171+
{Name: "repository", Type: "TEXT"},
172+
{Name: "branch", Type: "TEXT"},
173+
{Name: "commitId", Type: "TEXT"},
174+
{Name: "conclusion", Type: "TEXT"},
175+
{Name: "workflow_conclusion", Type: "TEXT"},
176+
{Name: "status", Type: "TEXT"},
177+
{Name: "summary", Type: "TEXT"},
178+
{Name: "user", Type: "TEXT"},
179+
{Name: "started_at", Type: "DATETIME"},
180+
{Name: "completed_at", Type: "DATETIME"},
181+
{Name: "url", Type: "TEXT"},
182+
{Name: "app_name", Type: "TEXT"},
183+
{Name: "app_logo_url", Type: "TEXT"},
184+
}
185+
186+
func (i *iterCheckSuites) Column(ctx *sqlite.Context, c int) error {
187+
current := i.results.Edges[i.current]
188+
col := checkCols[c]
189+
190+
switch col.Name {
191+
case "branch":
192+
ctx.ResultText(current.BranchName)
193+
case "commitId":
194+
ctx.ResultText(current.CommitId)
195+
case "name":
196+
ctx.ResultText(string(current.Name))
197+
case "repository":
198+
ctx.ResultText(current.Repository)
199+
case "workflow_name":
200+
ctx.ResultText(current.WorkflowName)
201+
case "conclusion":
202+
ctx.ResultText(string(current.Conclusion))
203+
case "summary":
204+
ctx.ResultText(string(current.Summary))
205+
case "status":
206+
ctx.ResultText(string(current.Status))
207+
case "user":
208+
ctx.ResultText(current.User)
209+
case "url":
210+
ctx.ResultText(current.Url.String())
211+
case "app_name":
212+
ctx.ResultText(current.AppName)
213+
case "app_logo_url":
214+
ctx.ResultText(current.AppLogoUrl)
215+
case "workflow_conclusion":
216+
ctx.ResultText(current.WorkflowConclusion)
217+
case "started_at":
218+
t := current.StartedAt
219+
if t.IsZero() {
220+
ctx.ResultNull()
221+
} else {
222+
ctx.ResultText(t.Format(time.RFC3339Nano))
223+
}
224+
case "completed_at":
225+
t := current.CompletedAt
226+
if t.IsZero() {
227+
ctx.ResultNull()
228+
} else {
229+
ctx.ResultText(t.Format(time.RFC3339Nano))
230+
}
231+
}
232+
return nil
233+
}
234+
235+
func NewCheckModule(opts *Options) sqlite.Module {
236+
return vtab.NewTableFunc("github_repo_checks", checkCols, func(constraints []*vtab.Constraint, orders []*sqlite.OrderBy) (vtab.Iterator, error) {
237+
var fullNameOrOwner, name string
238+
for _, constraint := range constraints {
239+
if constraint.Op == sqlite.INDEX_CONSTRAINT_EQ {
240+
switch constraint.ColIndex {
241+
case 0:
242+
fullNameOrOwner = constraint.Value.Text()
243+
case 1:
244+
name = constraint.Value.Text()
245+
}
246+
}
247+
}
248+
249+
owner, name, err := repoOwnerAndName(name, fullNameOrOwner)
250+
if err != nil {
251+
return nil, err
252+
}
253+
254+
iter := &iterCheckSuites{opts, owner, name, -1, nil}
255+
iter.logger().Infof("starting GitHub check iterator for %s/%s", owner, name)
256+
return iter, nil
257+
})
258+
}
259+
260+
func getCheckRowsFromRefs(refs []*ref) []*checkRunRow {
261+
var rows []*checkRunRow
262+
for _, r := range refs {
263+
for _, suite := range r.Target.Commit.CheckSuites.Nodes {
264+
for _, check := range suite.CheckRuns.Nodes {
265+
if check.Name != "" {
266+
var row = checkRunRow{
267+
checkRun: *check,
268+
AppName: string(suite.App.Name),
269+
AppLogoUrl: suite.App.LogoUrl.String(),
270+
BranchName: string(suite.Branch.Name),
271+
CommitId: string(suite.Commit.Oid),
272+
User: string(suite.Creator.Login),
273+
WorkflowName: string(suite.WorkflowRun.Workflow.Name),
274+
WorkflowConclusion: string(suite.Conclusion),
275+
Repository: suite.Repository.NameWithOwner,
276+
}
277+
rows = append(rows, &row)
278+
}
279+
}
280+
}
281+
}
282+
return rows
283+
}

extensions/internal/github/github.go

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func Register(ext *sqlite.ExtensionApi, opt *options.Options) (_ sqlite.ErrorCod
4848
"github_org_repos": NewOrgReposModule(githubOpts),
4949
"github_repo_issues": NewIssuesModule(githubOpts),
5050
"github_repo_pull_requests": NewPRModule(githubOpts),
51+
"github_repo_checks": NewCheckModule(githubOpts),
5152
}
5253

5354
modules["github_issues"] = modules["github_repo_issues"]

0 commit comments

Comments
 (0)