Skip to content

Commit

Permalink
Add table github_search_issue. Closes #102 (#109)
Browse files Browse the repository at this point in the history
  • Loading branch information
c0d3r-arnab committed Dec 7, 2021
1 parent 97fbaa2 commit 77e69e8
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 0 deletions.
115 changes: 115 additions & 0 deletions docs/tables/github_search_issue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Table: github_search_issue

The `github_search_issue` table helps to find issues by state and keyword. You can search for issues globally across all of GitHub, or search for issues within a particular organization.

**You must always include at least one search term when searching source code** in the where or join clause using the `query` column. You can narrow the results using these search qualifiers in any combination.

## Examples

### List issues by the title, body, or comments

```sql
select
title,
id,
state,
created_at,
html_url
from
github_search_issue
where
query = 'github_search_commit in:title in:body in:comments';
```

### List issues in open state assigned to a specific user

```sql
select
title,
id,
state,
created_at,
html_url
from
github_search_issue
where
query = 'is:open assignee:c0d3r-arnab repo:turbot/steampipe-plugin-github';
```

### List issues with public visibility assigned to a specific user

```sql
select
title,
id,
state,
created_at,
html_url
from
github_search_issue
where
query = 'is:public assignee:c0d3r-arnab repo:turbot/steampipe-plugin-github';
```

### List issues not linked to a pull request

```sql
select
title,
id,
state,
created_at,
html_url
from
github_search_issue
where
query = 'is:open -linked:pr repo:turbot/steampipe-plugin-github';
```

### List blocked issues

```sql
select
title,
id,
state,
created_at,
html_url
from
github_search_issue
where
query = 'label:blocked repo:turbot/steampipe-plugin-github';
```

### List issues with over 10 comments

```sql
select
title,
id,
comments,
state,
created_at,
html_url
from
github_search_issue
where
query = 'org:turbot comments:>10';
```

### List issues that took more than 30 days to close

```sql
select
title,
id,
state,
created_at,
closed_at,
html_url
from
github_search_issue
where
query = 'org:turbot state:closed'
and closed_at > (created_at + interval '30' day);
```
1 change: 1 addition & 0 deletions github/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func Plugin(ctx context.Context) *plugin.Plugin {
"github_repository": tableGitHubRepository(),
"github_search_code": tableGitHubSearchCode(ctx),
"github_search_commit": tableGitHubSearchCommit(ctx),
"github_search_issue": tableGitHubSearchIssue(ctx),
"github_search_label": tableGitHubSearchLable(ctx),
"github_search_pull_request": tableGitHubSearchPullRequest(ctx),
"github_search_repository": tableGitHubSearchRepository(ctx),
Expand Down
134 changes: 134 additions & 0 deletions github/table_github_search_issue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package github

import (
"context"

"github.com/google/go-github/v33/github"
"github.com/turbot/steampipe-plugin-sdk/grpc/proto"
"github.com/turbot/steampipe-plugin-sdk/plugin"
"github.com/turbot/steampipe-plugin-sdk/plugin/transform"
)

//// TABLE DEFINITION

func tableGitHubSearchIssue(ctx context.Context) *plugin.Table {
return &plugin.Table{
Name: "github_search_issue",
Description: "Find issues by state and keyword.",
List: &plugin.ListConfig{
KeyColumns: plugin.SingleColumn("query"),
Hydrate: tableGitHubSearchIssueList,
},
Columns: []*plugin.Column{
{Name: "title", Type: proto.ColumnType_STRING, Description: "The title of the issue."},
{Name: "id", Type: proto.ColumnType_INT, Transform: transform.FromField("ID"), Description: "The ID of the issue."},
{Name: "query", Type: proto.ColumnType_STRING, Transform: transform.FromQual("query"), Description: "The query used to match the issue."},
{Name: "state", Type: proto.ColumnType_STRING, Description: "The state of the issue."},
{Name: "active_lock_reason", Type: proto.ColumnType_STRING, Description: "The active lock reason of the issue."},
{Name: "author_association", Type: proto.ColumnType_STRING, Description: "The author association of the issue."},
{Name: "body", Type: proto.ColumnType_STRING, Description: "The body of the issue."},
{Name: "closed_at", Type: proto.ColumnType_TIMESTAMP, Description: "The timestamp the issue closed at."},
{Name: "comments", Type: proto.ColumnType_INT, Description: "The number of comments on the issue."},
{Name: "comments_url", Type: proto.ColumnType_STRING, Description: "The API URL of the comments for the issue."},
{Name: "created_at", Type: proto.ColumnType_TIMESTAMP, Description: "The timestamp the issue created at."},
{Name: "events_url", Type: proto.ColumnType_STRING, Description: "The API URL of the events for the issue."},
{Name: "html_url", Type: proto.ColumnType_STRING, Description: "The complete URL of the issue."},
{Name: "labels_url", Type: proto.ColumnType_STRING, Description: "The API URL of the labels for the issue."},
{Name: "locked", Type: proto.ColumnType_BOOL, Default: false, Description: "Whether the issue is locked."},
{Name: "node_id", Type: proto.ColumnType_STRING, Description: "The node ID of the issue."},
{Name: "number", Type: proto.ColumnType_INT, Description: "The number of the issue."},
{Name: "repository_url", Type: proto.ColumnType_STRING, Description: "The API URL of the repository for the issue."},
{Name: "updated_at", Type: proto.ColumnType_TIMESTAMP, Description: "The timestamp the issue updated at."},
{Name: "url", Type: proto.ColumnType_STRING, Transform: transform.FromField("URL"), Description: "The API URL of the issue."},
{Name: "assignee", Type: proto.ColumnType_JSON, Description: "The assignee details."},
{Name: "assignees", Type: proto.ColumnType_JSON, Description: "The assignees details."},
{Name: "closed_by", Type: proto.ColumnType_JSON, Description: "The details of the user that closed the issue."},
{Name: "labels", Type: proto.ColumnType_JSON, Description: "The label details."},
{Name: "milestone", Type: proto.ColumnType_JSON, Description: "The milestone details."},
{Name: "reactions", Type: proto.ColumnType_JSON, Description: "The reaction details."},
{Name: "repository", Type: proto.ColumnType_JSON, Description: "The repository details."},
{Name: "text_matches", Type: proto.ColumnType_JSON, Description: "The text match details."},
{Name: "user", Type: proto.ColumnType_JSON, Description: "The user details."},
},
}
}

//// LIST FUNCTION

func tableGitHubSearchIssueList(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
logger := plugin.Logger(ctx)
logger.Trace("tableGitHubSearchIssueList")

quals := d.KeyColumnQuals
query := quals["query"].GetStringValue()

if query == "" {
return nil, nil
}

query = query + " is:issue"

opt := &github.SearchOptions{
ListOptions: github.ListOptions{PerPage: 100},
TextMatch: true,
}

type ListPageResponse struct {
result *github.IssuesSearchResult
resp *github.Response
}

client := connect(ctx, d)

// Reduce the basic request limit down if the user has only requested a small number of rows
limit := d.QueryContext.Limit
if limit != nil {
if *limit < int64(opt.ListOptions.PerPage) {
opt.ListOptions.PerPage = int(*limit)
}
}

listPage := func(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
result, resp, err := client.Search.Issues(ctx, query, opt)

if err != nil {
logger.Error("tableGitHubSearchIssueList", "error_Search.Issues", err)
return nil, err
}

return ListPageResponse{
result: result,
resp: resp,
}, nil
}

for {
listPageResponse, err := plugin.RetryHydrate(ctx, d, h, listPage, &plugin.RetryConfig{ShouldRetryError: shouldRetryError})

if err != nil {
logger.Error("tableGitHubSearchIssueList", "error_RetryHydrate", err)
return nil, err
}

listResponse := listPageResponse.(ListPageResponse)
issues := listResponse.result.Issues
resp := listResponse.resp

for _, i := range issues {
d.StreamListItem(ctx, i)

// Context can be cancelled due to manual cancellation or the limit has been hit
if d.QueryStatus.RowsRemaining(ctx) == 0 {
return nil, nil
}
}

if resp.NextPage == 0 {
break
}

opt.Page = resp.NextPage
}

return nil, nil
}

1 comment on commit 77e69e8

@judell
Copy link
Contributor

@judell judell commented on 77e69e8 Dec 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay!

Please sign in to comment.