mirror of
https://gitea.com/jolheiser/sip
synced 2024-11-22 19:51:58 +01:00
Add search filters (#8)
Exclude Match from gocognit Add search filters Signed-off-by: jolheiser <john.olheiser@gmail.com> Co-authored-by: jolheiser <john.olheiser@gmail.com> Reviewed-on: https://gitea.com/jolheiser/sip/pulls/8
This commit is contained in:
parent
1956062791
commit
df58f223f2
2
Makefile
2
Makefile
@ -24,7 +24,7 @@ build:
|
|||||||
lint:
|
lint:
|
||||||
@hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
export BINARY="golangci-lint"; \
|
export BINARY="golangci-lint"; \
|
||||||
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.23.1; \
|
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.23.6; \
|
||||||
fi
|
fi
|
||||||
golangci-lint run --timeout 5m
|
golangci-lint run --timeout 5m
|
||||||
|
|
||||||
|
13
README.md
13
README.md
@ -27,4 +27,15 @@ If no `upstream` repository is found, `upstream` becomes synonymous with `origin
|
|||||||
* Search pull requests based on keyword(s)
|
* Search pull requests based on keyword(s)
|
||||||
* Create a new pull request `sip pulls create`
|
* Create a new pull request `sip pulls create`
|
||||||
* Check pull request status (default based on current branch) `sip pulls status`
|
* Check pull request status (default based on current branch) `sip pulls status`
|
||||||
* Checkout a pull request to test locally `sip pulls checkout`
|
* Checkout a pull request to test locally `sip pulls checkout`
|
||||||
|
|
||||||
|
### Search filters
|
||||||
|
Sip supports certain search filters for issues/PRs.
|
||||||
|
Anything in the query that doesn't match one of the below filters will be sent as a keyword
|
||||||
|
|
||||||
|
* State `is:open is:closed is:merged` - only the last state in the query will be applied
|
||||||
|
* Author `author:jolheiser` - only the last author in the query will be applied
|
||||||
|
* Labels `label:bug label:feature` - all labels must apply to return results
|
||||||
|
* Milestone `mileston:v1.0.0` - only the last milestone in the query will be applied
|
||||||
|
|
||||||
|
e.g. `test is:open query author:jolheiser milestone:0.2.0` will search for issues/PRs with keywords `test query` that are `open`, authored by `jolheiser`, and in the `0.2.0` milestone.
|
@ -106,8 +106,9 @@ func queryIssues(ctx *cli.Context, client *gitea.Client, pulls bool) ([]*gitea.I
|
|||||||
if err := survey.Ask(questions, &answers); err != nil {
|
if err := survey.Ask(questions, &answers); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
filter := sdk.NewIssueFilter(answers.Query)
|
||||||
ownerRepo := strings.Split(answers.Repo, "/")
|
ownerRepo := strings.Split(answers.Repo, "/")
|
||||||
opts := gitea.ListIssueOption{KeyWord: answers.Query, State: "all"}
|
opts := gitea.ListIssueOption{KeyWord: filter.Query, State: "all"}
|
||||||
issues, err := sdk.GetIssues(client, ownerRepo[0], ownerRepo[1], opts)
|
issues, err := sdk.GetIssues(client, ownerRepo[0], ownerRepo[1], opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -115,10 +116,16 @@ func queryIssues(ctx *cli.Context, client *gitea.Client, pulls bool) ([]*gitea.I
|
|||||||
|
|
||||||
filtered := make([]*gitea.Issue, 0)
|
filtered := make([]*gitea.Issue, 0)
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
|
// Filter out issues if searching PRs and vice-versa
|
||||||
if (pulls && issue.PullRequest == nil) ||
|
if (pulls && issue.PullRequest == nil) ||
|
||||||
(!pulls && issue.PullRequest != nil) {
|
(!pulls && issue.PullRequest != nil) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !filter.Match(issue) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
filtered = append(filtered, issue)
|
filtered = append(filtered, issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package sdk
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetIssues returns all matching Issues from a Gitea instance
|
// GetIssues returns all matching Issues from a Gitea instance
|
||||||
@ -23,3 +24,97 @@ func GetIssues(client *gitea.Client, owner, repo string, opts gitea.ListIssueOpt
|
|||||||
}
|
}
|
||||||
return issues, nil
|
return issues, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type State int
|
||||||
|
|
||||||
|
const (
|
||||||
|
None State = iota
|
||||||
|
Open
|
||||||
|
Closed
|
||||||
|
Merged
|
||||||
|
)
|
||||||
|
|
||||||
|
type IssueFilter struct {
|
||||||
|
Query string
|
||||||
|
State State
|
||||||
|
Author string
|
||||||
|
Labels []string
|
||||||
|
Milestone string
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gocognit
|
||||||
|
// This function is high in cognitive complexity,
|
||||||
|
// however it is hopefully structured to be less painful than gocognit thinks
|
||||||
|
func (f *IssueFilter) Match(issue *gitea.Issue) bool {
|
||||||
|
|
||||||
|
// Filter out state
|
||||||
|
if f.State == Open && issue.State != gitea.StateOpen {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if f.State == Closed && issue.State != gitea.StateClosed {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if f.State == Merged && (issue.PullRequest == nil || !issue.PullRequest.HasMerged) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out author
|
||||||
|
if f.Author != "" && (issue.Poster == nil || !strings.EqualFold(f.Author, issue.Poster.UserName)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out labels
|
||||||
|
for _, fl := range f.Labels {
|
||||||
|
hasLabel := false
|
||||||
|
for _, il := range issue.Labels {
|
||||||
|
if fl == strings.ToLower(il.Name) {
|
||||||
|
hasLabel = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasLabel {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out milestone
|
||||||
|
if f.Milestone != "" && (issue.Milestone == nil || !strings.EqualFold(f.Milestone, issue.Milestone.Title)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIssueFilter(query string) *IssueFilter {
|
||||||
|
filter := &IssueFilter{}
|
||||||
|
for _, q := range strings.Split(query, " ") {
|
||||||
|
kv := strings.Split(q, ":")
|
||||||
|
if len(kv) == 2 {
|
||||||
|
kv[0] = strings.ToLower(kv[0])
|
||||||
|
kv[1] = strings.ToLower(kv[1])
|
||||||
|
switch kv[0] {
|
||||||
|
case "is":
|
||||||
|
switch kv[1] {
|
||||||
|
case "open", "opened":
|
||||||
|
filter.State = Open
|
||||||
|
case "close", "closed":
|
||||||
|
filter.State = Closed
|
||||||
|
case "merge", "merged":
|
||||||
|
filter.State = Merged
|
||||||
|
default:
|
||||||
|
filter.State = None
|
||||||
|
}
|
||||||
|
case "author":
|
||||||
|
filter.Author = kv[1]
|
||||||
|
case "label":
|
||||||
|
filter.Labels = append(filter.Labels, kv[1])
|
||||||
|
case "milestone":
|
||||||
|
filter.Milestone = kv[1]
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filter.Query += " " + q
|
||||||
|
}
|
||||||
|
filter.Query = strings.TrimSpace(filter.Query)
|
||||||
|
return filter
|
||||||
|
}
|
||||||
|
48
modules/sdk/issues_test.go
Normal file
48
modules/sdk/issues_test.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIssueFilter(t *testing.T) {
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
Query string
|
||||||
|
Result *IssueFilter
|
||||||
|
}{
|
||||||
|
// Grouped in fives to make searching failed tests easier
|
||||||
|
{"test query", &IssueFilter{Query: "test query"}},
|
||||||
|
{"test query author:jolheiser", &IssueFilter{Query: "test query", Author: "jolheiser"}},
|
||||||
|
{"test query author:jolheiser author:zeripath", &IssueFilter{Query: "test query", Author: "zeripath"}},
|
||||||
|
{"test is:open query", &IssueFilter{Query: "test query", State: Open}},
|
||||||
|
{"is:closed", &IssueFilter{State: Closed}},
|
||||||
|
|
||||||
|
{"is:closed is:merged", &IssueFilter{State: Merged}},
|
||||||
|
{"is:merged test author:jolheiser query", &IssueFilter{Query: "test query", State: Merged, Author: "jolheiser"}},
|
||||||
|
{"label:bug", &IssueFilter{Labels: []string{"bug"}}},
|
||||||
|
{"label:bug label:feature", &IssueFilter{Labels: []string{"bug", "feature"}}},
|
||||||
|
{"label:bug author:jolheiser test is:open query is:closed", &IssueFilter{Query: "test query", Author: "jolheiser", State: Closed, Labels: []string{"bug"}}},
|
||||||
|
|
||||||
|
{"is:closed milestone:0.1.0", &IssueFilter{State: Closed, Milestone: "0.1.0"}},
|
||||||
|
{"milestone:v1.0.0 keyword", &IssueFilter{Query: "keyword", Milestone: "v1.0.0"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, tc := range tt {
|
||||||
|
t.Run(strconv.Itoa(idx+1), func(t *testing.T) {
|
||||||
|
filter := NewIssueFilter(tc.Query)
|
||||||
|
if tc.Result.Query != filter.Query {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if tc.Result.State != filter.State {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if tc.Result.Author != filter.Author {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if len(tc.Result.Labels) != len(filter.Labels) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
10
modules/sdk/sdk_test.go
Normal file
10
modules/sdk/sdk_test.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user