[bugs-central] Interface used by the different issue frameworks
Bug: skia:10783
Change-Id: I699d7008a60fcbacb500b8dd1bed82aed2328160
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/328986
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
diff --git a/bugs-central/go/bugs/bugs.go b/bugs-central/go/bugs/bugs.go
new file mode 100644
index 0000000..b2456e7
--- /dev/null
+++ b/bugs-central/go/bugs/bugs.go
@@ -0,0 +1,23 @@
+package bugs
+
+// Defines a generic interface used by the different issue frameworks.
+
+import (
+ "context"
+
+ "go.skia.org/infra/bugs-central/go/db"
+ "go.skia.org/infra/bugs-central/go/types"
+)
+
+type BugFramework interface {
+
+ // Search calls the bug framework and returns standardized issues.
+ Search(ctx context.Context) ([]*types.Issue, *types.IssueCountsData, error)
+
+ // SearchClientAndPersist queries issues and puts results into the DB and into the
+ // OpenIssues in-memory object.
+ SearchClientAndPersist(ctx context.Context, dbClient *db.FirestoreDB, runId string) error
+
+ // GetIssueLink returns a link to the specified issue ID.
+ GetIssueLink(project, id string) string
+}
diff --git a/bugs-central/go/bugs/open_issues.go b/bugs-central/go/bugs/open_issues.go
new file mode 100644
index 0000000..df5d6f3
--- /dev/null
+++ b/bugs-central/go/bugs/open_issues.go
@@ -0,0 +1,66 @@
+package bugs
+
+// Defines an interface used by the different issue frameworks to add open issues to an in-memory object.
+
+import (
+ "sync"
+
+ "go.skia.org/infra/bugs-central/go/types"
+ "go.skia.org/infra/go/sklog"
+)
+
+type OpenIssues struct {
+ // Mapping of client to source to query to issues. Mirrors the DB structure but stores real issues instead of counts.
+ // This will be used mainly by endpoints and emails that require the actual issue IDs.
+ openIssues map[types.RecognizedClient]map[types.IssueSource]map[string][]*types.Issue
+ // Mutex to access the above object.
+ mtx sync.RWMutex
+}
+
+func InitOpenIssues() *OpenIssues {
+ return &OpenIssues{
+ openIssues: map[types.RecognizedClient]map[types.IssueSource]map[string][]*types.Issue{},
+ }
+}
+
+// PrettyPrintOpenIssues pretty prints the open issues in-memory object.
+func (o *OpenIssues) PrettyPrintOpenIssues() {
+ o.mtx.RLock()
+ defer o.mtx.RUnlock()
+
+ sklog.Info("---- open issues ----")
+ for c, sourceToQueries := range o.openIssues {
+ sklog.Infof("%s", c)
+ for s, queriesToIssues := range sourceToQueries {
+ sklog.Infof(" %s", s)
+ for q, issues := range queriesToIssues {
+ sklog.Infof(" \"%s\"", q)
+ sklog.Infof(" Open Issues: %d", len(issues))
+ }
+ }
+ }
+ sklog.Info("---------------------")
+}
+
+// PutOpenIssues adds/removes data from the open issues in-memory object.
+func (o *OpenIssues) PutOpenIssues(client types.RecognizedClient, source types.IssueSource, query string, issues []*types.Issue) {
+ o.mtx.Lock()
+ defer o.mtx.Unlock()
+
+ if sourceToQueries, ok := o.openIssues[client]; ok {
+ if queryToIssues, ok := sourceToQueries[source]; ok {
+ // Replace existing slice with new issues.
+ queryToIssues[query] = issues
+ } else {
+ sourceToQueries[source] = map[string][]*types.Issue{
+ query: issues,
+ }
+ }
+ } else {
+ o.openIssues[client] = map[types.IssueSource]map[string][]*types.Issue{
+ source: {
+ query: issues,
+ },
+ }
+ }
+}
diff --git a/bugs-central/go/bugs/open_issues_test.go b/bugs-central/go/bugs/open_issues_test.go
new file mode 100644
index 0000000..09358e5
--- /dev/null
+++ b/bugs-central/go/bugs/open_issues_test.go
@@ -0,0 +1,59 @@
+package bugs
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "go.skia.org/infra/bugs-central/go/types"
+ "go.skia.org/infra/go/testutils/unittest"
+)
+
+func TestPutOpenIssues(t *testing.T) {
+ unittest.SmallTest(t)
+
+ o := InitOpenIssues()
+ client1 := types.RecognizedClient("client1")
+ client2 := types.RecognizedClient("client2")
+ source1 := types.IssueSource("source1")
+ source2 := types.IssueSource("source2")
+ query1 := "query1"
+ query2 := "query2"
+ issues1 := []*types.Issue{
+ {Id: "id11"},
+ {Id: "id12"},
+ }
+ issues2 := []*types.Issue{
+ {Id: "id21"},
+ {Id: "id22"},
+ {Id: "id32"},
+ }
+ issues3 := []*types.Issue{
+ {Id: "id31"},
+ {Id: "id32"},
+ {Id: "id32"},
+ }
+
+ // Add 1 client+source+query entry with 2 issues.
+ o.PutOpenIssues(client1, source1, query1, issues1)
+ require.Equal(t, 1, len(o.openIssues))
+ require.Equal(t, 1, len(o.openIssues[client1]))
+ require.Equal(t, 1, len(o.openIssues[client1][source1]))
+ require.Equal(t, 2, len(o.openIssues[client1][source1][query1]))
+ require.Equal(t, "id11", o.openIssues[client1][source1][query1][0].Id)
+ require.Equal(t, "id12", o.openIssues[client1][source1][query1][1].Id)
+
+ // Add another client with 2 sources.
+ o.PutOpenIssues(client2, source1, query2, issues2)
+ o.PutOpenIssues(client2, source2, query2, issues3)
+ require.Equal(t, 2, len(o.openIssues))
+ require.Equal(t, 2, len(o.openIssues[client2]))
+ require.Equal(t, "id21", o.openIssues[client2][source1][query2][0].Id)
+ require.Equal(t, "id31", o.openIssues[client2][source2][query2][0].Id)
+
+ // Replace an existing entries with new set of issues.
+ o.PutOpenIssues(client2, source1, query2, issues1)
+ require.Equal(t, 2, len(o.openIssues[client2][source1][query2]))
+ require.Equal(t, "id11", o.openIssues[client2][source1][query2][0].Id)
+ require.Equal(t, "id12", o.openIssues[client2][source1][query2][1].Id)
+}