From ac8bc4e856c586b8cc76e1b954dc4847e5f99d82 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Fri, 13 Mar 2026 18:08:00 +0000 Subject: [PATCH 1/9] Remove project items --- pkg/github/schema.go | 12 ------------ pkg/github/sql.go | 1 - pkg/github/sql_handler_test.go | 2 +- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/pkg/github/schema.go b/pkg/github/schema.go index 9f672588..c1fbf843 100644 --- a/pkg/github/schema.go +++ b/pkg/github/schema.go @@ -408,18 +408,6 @@ func getAllTables() []schemas.Table { {Name: "short_description", Type: schemas.ColumnTypeString}, }, }, - { - Name: normalizeTableNames(models.QueryTypeProjectItems), - TableParameters: projectTableParameters, - Columns: []schemas.Column{ - {Name: "id", Type: schemas.ColumnTypeString}, - {Name: "archived", Type: schemas.ColumnTypeBoolean}, - {Name: "type", Type: schemas.ColumnTypeString}, - {Name: "updated_at", Type: schemas.ColumnTypeDatetime}, - {Name: "created_at", Type: schemas.ColumnTypeDatetime}, - {Name: "closed_at", Type: schemas.ColumnTypeDatetime}, - }, - }, { Name: normalizeTableNames(models.QueryTypeStargazers), TableParameters: repoScopedTableParameters, diff --git a/pkg/github/sql.go b/pkg/github/sql.go index 3ad86c59..a8e0bc22 100644 --- a/pkg/github/sql.go +++ b/pkg/github/sql.go @@ -26,7 +26,6 @@ var tableToQueryType = func() map[string]string { models.QueryTypePackages, models.QueryTypeVulnerabilities, models.QueryTypeProjects, - models.QueryTypeProjectItems, models.QueryTypeStargazers, models.QueryTypeWorkflows, models.QueryTypeWorkflowUsage, diff --git a/pkg/github/sql_handler_test.go b/pkg/github/sql_handler_test.go index 33a07e25..9817f5e8 100644 --- a/pkg/github/sql_handler_test.go +++ b/pkg/github/sql_handler_test.go @@ -25,7 +25,7 @@ func TestTableToQueryTypeCoversAllTypes(t *testing.T) { models.QueryTypeTags, models.QueryTypeReleases, models.QueryTypeLabels, models.QueryTypeMilestones, models.QueryTypePackages, models.QueryTypeVulnerabilities, - models.QueryTypeProjects, models.QueryTypeProjectItems, + models.QueryTypeProjects, models.QueryTypeStargazers, models.QueryTypeWorkflows, models.QueryTypeWorkflowUsage, models.QueryTypeWorkflowRuns, models.QueryTypeCodeScanning, models.QueryTypeOrganizations, From f8643176df7748b80b8193897ca12a709a11711d Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Sat, 14 Mar 2026 14:31:02 +0000 Subject: [PATCH 2/9] Fix parameters --- pkg/github/schema.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/github/schema.go b/pkg/github/schema.go index c1fbf843..a4d540dd 100644 --- a/pkg/github/schema.go +++ b/pkg/github/schema.go @@ -33,10 +33,6 @@ var ( {Name: "organization", Root: true, Required: true}, } - projectTableParameters = []schemas.TableParameter{ - {Name: "organization", Root: true, Required: false}, - } - workflowUsageTableParameters = []schemas.TableParameter{ {Name: "organization", Root: true, Required: true}, {Name: "repository", DependsOn: []string{"organization"}, Required: true}, @@ -395,7 +391,7 @@ func getAllTables() []schemas.Table { }, { Name: normalizeTableNames(models.QueryTypeProjects), - TableParameters: projectTableParameters, + TableParameters: orgOnlyTableParameters, Columns: []schemas.Column{ {Name: "number", Type: schemas.ColumnTypeInt64}, {Name: "title", Type: schemas.ColumnTypeString}, From 9daa1eefa1169ede62aae3d9eb9d470e4ee79a5e Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Sat, 14 Mar 2026 14:31:13 +0000 Subject: [PATCH 3/9] Update schemads --- go.mod | 2 +- go.sum | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9593a4fd..8a2d0903 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 github.com/google/go-github/v81 v81.0.0 github.com/grafana/grafana-plugin-sdk-go v0.290.0 - github.com/grafana/schemads v0.0.1 + github.com/grafana/schemads v0.0.2 github.com/influxdata/tdigest v0.0.1 github.com/pkg/errors v0.9.1 github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed diff --git a/go.sum b/go.sum index aee3df1c..5ffe24a5 100644 --- a/go.sum +++ b/go.sum @@ -98,8 +98,6 @@ github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= -github.com/grafana/schemads v0.0.1 h1:hw+8zJlZG/dFPbAlXs+PU86pwqG2tPO1mL8ws0S8/Do= -github.com/grafana/schemads v0.0.1/go.mod h1:i/iRKic1c9i/ZjApKe7+BDQjPhlQO7gWycVRu7x6c1U= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= From 71025346c68e982acfd48c6a650ddea465420738 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Sat, 14 Mar 2026 14:31:35 +0000 Subject: [PATCH 4/9] Use schemads types and simplify sql handling --- pkg/github/sql.go | 328 ++++++++++++++------------------- pkg/github/sql_handler_test.go | 178 ++++++++++++++++-- 2 files changed, 306 insertions(+), 200 deletions(-) diff --git a/pkg/github/sql.go b/pkg/github/sql.go index a8e0bc22..2f74b1df 100644 --- a/pkg/github/sql.go +++ b/pkg/github/sql.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/github-datasource/pkg/models" "github.com/grafana/grafana-plugin-sdk-go/backend" + schemas "github.com/grafana/schemads" ) // tableToQueryType maps normalized table names to their QueryType constants. @@ -53,207 +54,152 @@ func parseJSONStringValues(s string) []string { return []string{s} } +func extractFilterValues(condition schemas.FilterCondition) []string { + out := make([]string, 0, len(condition.Values)+1) + for _, v := range condition.Values { + if v != "" { + out = append(out, v) + } + } + if len(out) == 0 && condition.Value != "" { + out = append(out, condition.Value) + } + return out +} + // applyFilters maps SQL filter predicates to GitHub API query options. // It modifies the options map in-place and returns a list of GitHub search // qualifiers for query types that use the search API. -func applyFilters(queryType string, options map[string]interface{}, filters []map[string]interface{}) []string { +func applyFilters(queryType string, options map[string]interface{}, filters []schemas.ColumnFilter) []string { var searchQualifiers []string - for _, f := range filters { - column, _ := f["column"].(string) - op, _ := f["op"].(string) - value, _ := f["value"].(string) - if column == "" || value == "" { - continue - } + opts, _ := options["options"].(map[string]interface{}) + if opts == nil { + opts = make(map[string]interface{}) + options["options"] = opts + } - switch queryType { - case models.QueryTypeIssues: - switch column { - case "state": - if op == "==" || op == "=" { - searchQualifiers = append(searchQualifiers, "state:"+value) - } - case "author": - if op == "==" || op == "=" { - searchQualifiers = append(searchQualifiers, "author:"+value) - } - case "labels": - if op == "==" || op == "=" { + appendEqualitySearchQualifier := func(name, operator string, values []string, isJSON bool) { + if operator == "==" || operator == "=" || operator == "in" { + for _, value := range values { + if isJSON { for _, v := range parseJSONStringValues(value) { - searchQualifiers = append(searchQualifiers, "label:"+v) + searchQualifiers = append(searchQualifiers, name+":"+v) } - } - case "assignees": - if op == "==" || op == "=" { - for _, v := range parseJSONStringValues(value) { - searchQualifiers = append(searchQualifiers, "assignee:"+v) - } - } - case "milestone": - if op == "==" || op == "=" { - searchQualifiers = append(searchQualifiers, "milestone:"+value) + } else { + searchQualifiers = append(searchQualifiers, name+":"+value) } } + } + } + setOption := func(name, op, value string) { + if op == "==" || op == "=" || op == "in" { + opts[name] = value + } + } - case models.QueryTypePullRequests, models.QueryTypePullRequestReviews: - switch column { - case "state": - if op == "==" || op == "=" { - searchQualifiers = append(searchQualifiers, "state:"+value) - } - case "author_login": - if op == "==" || op == "=" { - searchQualifiers = append(searchQualifiers, "author:"+value) - } - case "labels": - if op == "==" || op == "=" { - for _, v := range parseJSONStringValues(value) { - searchQualifiers = append(searchQualifiers, "label:"+v) - } - } - case "is_draft": - if op == "==" || op == "=" { - if value == "true" { - searchQualifiers = append(searchQualifiers, "draft:true") - } else { - searchQualifiers = append(searchQualifiers, "draft:false") - } - } - } + for _, f := range filters { + if f.Name == "" || len(f.Conditions) == 0 { + continue + } - case models.QueryTypeCodeScanning: - switch column { - case "state": - if op == "==" || op == "=" { - opts, _ := options["options"].(map[string]interface{}) - if opts == nil { - opts = make(map[string]interface{}) - options["options"] = opts - } - opts["state"] = value - } - case "rule_severity": - if op == "==" || op == "=" { - opts, _ := options["options"].(map[string]interface{}) - if opts == nil { - opts = make(map[string]interface{}) - options["options"] = opts - } - opts["severity"] = value - } - case "tool_name": - if op == "==" || op == "=" { - opts, _ := options["options"].(map[string]interface{}) - if opts == nil { - opts = make(map[string]interface{}) - options["options"] = opts - } - opts["toolName"] = value - } + for _, condition := range f.Conditions { + op := strings.ToLower(strings.TrimSpace(condition.Operator)) + values := extractFilterValues(condition) + if len(values) == 0 { + continue } - case models.QueryTypeWorkflowRuns: - switch column { - case "head_branch": - if op == "==" || op == "=" { - opts, _ := options["options"].(map[string]interface{}) - if opts == nil { - opts = make(map[string]interface{}) - options["options"] = opts - } - opts["branch"] = value + switch queryType { + case models.QueryTypeIssues: + switch f.Name { + case "state": + appendEqualitySearchQualifier(f.Name, op, values, false) + case "author": + appendEqualitySearchQualifier(f.Name, op, values, false) + case "labels": + appendEqualitySearchQualifier("label", op, values, true) + case "assignees": + appendEqualitySearchQualifier("assignee", op, values, true) + case "milestone": + appendEqualitySearchQualifier("milestone", op, values, false) } - case "status": - if op == "==" || op == "=" { - opts, _ := options["options"].(map[string]interface{}) - if opts == nil { - opts = make(map[string]interface{}) - options["options"] = opts + case models.QueryTypePullRequests, models.QueryTypePullRequestReviews: + switch f.Name { + case "state": + appendEqualitySearchQualifier("state", op, values, false) + case "author_login": + appendEqualitySearchQualifier("author", op, values, false) + case "labels": + appendEqualitySearchQualifier("label", op, values, true) + case "is_draft": + if op == "==" || op == "=" || op == "in" { + for _, value := range values { + if value == "true" { + searchQualifiers = append(searchQualifiers, "draft:true") + } else { + searchQualifiers = append(searchQualifiers, "draft:false") + } + } } - opts["status"] = value } - case "event": - if op == "==" || op == "=" { - opts, _ := options["options"].(map[string]interface{}) - if opts == nil { - opts = make(map[string]interface{}) - options["options"] = opts - } - opts["event"] = value + case models.QueryTypeCodeScanning: + switch f.Name { + case "state": + setOption("state", op, values[0]) + case "rule_severity": + setOption("severity", op, values[0]) + case "tool_name": + setOption("toolName", op, values[0]) } - } - - case models.QueryTypeContributors: - if column == "name" && (op == "like" || op == "==" || op == "=") { - opts, _ := options["options"].(map[string]interface{}) - if opts == nil { - opts = make(map[string]interface{}) - options["options"] = opts + case models.QueryTypeWorkflowRuns: + switch f.Name { + case "head_branch": + setOption("branch", op, values[0]) + case "status": + setOption("status", op, values[0]) + case "event": + setOption("event", op, values[0]) } - opts["query"] = value - } - - case models.QueryTypeLabels: - if column == "name" && (op == "like" || op == "==" || op == "=") { - opts, _ := options["options"].(map[string]interface{}) - if opts == nil { - opts = make(map[string]interface{}) - options["options"] = opts + case models.QueryTypeContributors: + if f.Name == "name" && (op == "like" || op == "==" || op == "=" || op == "in") { + opts["query"] = values[0] } - opts["query"] = value - } - - case models.QueryTypeMilestones: - if column == "title" && (op == "like" || op == "==" || op == "=") { - opts, _ := options["options"].(map[string]interface{}) - if opts == nil { - opts = make(map[string]interface{}) - options["options"] = opts + case models.QueryTypeLabels: + if f.Name == "name" && (op == "like" || op == "==" || op == "=" || op == "in") { + opts["query"] = values[0] } - opts["query"] = value - } - - case models.QueryTypePackages: - switch column { - case "name": - if op == "==" || op == "=" { - opts, _ := options["options"].(map[string]interface{}) - if opts == nil { - opts = make(map[string]interface{}) - options["options"] = opts + case models.QueryTypeMilestones: + if f.Name == "title" && (op == "like" || op == "==" || op == "=" || op == "in") { + opts["query"] = values[0] + } + case models.QueryTypePackages: + switch f.Name { + case "name": + if op == "==" || op == "=" || op == "in" { + for _, value := range values { + existing, _ := opts["names"].(string) + if existing != "" { + opts["names"] = existing + "," + value + } else { + opts["names"] = value + } + } } - existing, _ := opts["names"].(string) - if existing != "" { - opts["names"] = existing + "," + value - } else { - opts["names"] = value + case "type": + if op == "==" || op == "=" || op == "in" { + opts["packageType"] = values[0] } } - case "type": - if op == "==" || op == "=" { - opts, _ := options["options"].(map[string]interface{}) - if opts == nil { - opts = make(map[string]interface{}) - options["options"] = opts - } - opts["packageType"] = value + case models.QueryTypeRepositories: + if f.Name == "name" && (op == "like" || op == "==" || op == "=" || op == "in") { + options["repository"] = values[0] } } - - case models.QueryTypeRepositories: - if column == "name" && (op == "like" || op == "==" || op == "=") { - options["repository"] = value - } } } if len(searchQualifiers) > 0 { - opts, _ := options["options"].(map[string]interface{}) - if opts == nil { - opts = make(map[string]interface{}) - options["options"] = opts - } existing, _ := opts["query"].(string) combined := strings.Join(searchQualifiers, " ") if existing != "" { @@ -274,19 +220,17 @@ func normalizeGrafanaSQLRequest(req *backend.QueryDataRequest) *backend.QueryDat grafanaConfig := req.PluginContext.GrafanaConfig queries := make([]backend.DataQuery, 0, len(req.Queries)) for _, q := range req.Queries { - var raw map[string]interface{} - if err := json.Unmarshal(q.JSON, &raw); err != nil { + var query schemas.GenericQuery + if err := json.Unmarshal(q.JSON, &query); err != nil { queries = append(queries, q) continue } - grafanaSql, _ := raw["grafanaSql"].(bool) - table, _ := raw["table"].(string) - if !grafanaSql || table == "" { + if !query.GrafanaSql || query.Table == "" { queries = append(queries, q) continue } - if grafanaSql { + if query.GrafanaSql { if grafanaConfig == nil { backend.Logger.Warn("grafanaConfig is not set, skipping query") continue @@ -300,7 +244,7 @@ func normalizeGrafanaSQLRequest(req *backend.QueryDataRequest) *backend.QueryDat // Table names use hyphens only (never underscores), so the first // underscore unambiguously separates the table name from the // owner/repo suffix: "issues_grafana_grafana" -> "issues" + "grafana_grafana". - parts := strings.SplitN(table, "_", 2) + parts := strings.SplitN(query.Table, "_", 2) queryType, ok := tableToQueryType[parts[0]] if !ok { queries = append(queries, q) @@ -315,24 +259,32 @@ func normalizeGrafanaSQLRequest(req *backend.QueryDataRequest) *backend.QueryDat repo = ownerRepo[1] } } + if v := strings.TrimSpace(query.TableParameterValues["organization"]); v != "" { + owner = v + } + if v := strings.TrimSpace(query.TableParameterValues["repository"]); v != "" { + repo = v + } normalized := map[string]interface{}{ - "refId": raw["refId"], - "datasource": raw["datasource"], + "refId": query.RefID, + "datasource": query.Datasource, "queryType": queryType, "owner": owner, "repository": repo, "options": map[string]interface{}{}, } + if queryType == models.QueryTypeProjects && owner != "" { + opts, _ := normalized["options"].(map[string]interface{}) + opts["organization"] = owner + } + if v := strings.TrimSpace(query.TableParameterValues["workflow"]); v != "" { + opts, _ := normalized["options"].(map[string]interface{}) + opts["workflow"] = v + } - if filters, ok := raw["filters"].([]interface{}); ok && len(filters) > 0 { - filterMaps := make([]map[string]interface{}, 0, len(filters)) - for _, f := range filters { - if fm, ok := f.(map[string]interface{}); ok { - filterMaps = append(filterMaps, fm) - } - } - applyFilters(queryType, normalized, filterMaps) + if len(query.Filters) > 0 { + applyFilters(queryType, normalized, query.Filters) } jsonBytes, err := json.Marshal(normalized) diff --git a/pkg/github/sql_handler_test.go b/pkg/github/sql_handler_test.go index 9817f5e8..234595e5 100644 --- a/pkg/github/sql_handler_test.go +++ b/pkg/github/sql_handler_test.go @@ -239,6 +239,120 @@ func TestNormalizeGrafanaSQLRequest(t *testing.T) { } }) + t.Run("maps projects owner to options.organization", func(t *testing.T) { + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"projects_grafana"}`) + req := &backend.QueryDataRequest{ + PluginContext: pluginCtxWithFeatureToggle(), + Queries: []backend.DataQuery{ + {RefID: "A", JSON: queryJSON}, + }, + } + out := normalizeGrafanaSQLRequest(req) + if out == nil || len(out.Queries) != 1 { + t.Fatalf("expected one query") + } + var raw map[string]interface{} + if err := json.Unmarshal(out.Queries[0].JSON, &raw); err != nil { + t.Fatal(err) + } + opts, _ := raw["options"].(map[string]interface{}) + org, _ := opts["organization"].(string) + if org != "grafana" { + t.Errorf("expected options.organization 'grafana', got %q", org) + } + }) + + t.Run("uses tableParameterValues for owner and repository", func(t *testing.T) { + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues","tableParameterValues":{"organization":"grafana","repository":"grafana"}}`) + req := &backend.QueryDataRequest{ + PluginContext: pluginCtxWithFeatureToggle(), + Queries: []backend.DataQuery{ + {RefID: "A", JSON: queryJSON}, + }, + } + out := normalizeGrafanaSQLRequest(req) + if out == nil || len(out.Queries) != 1 { + t.Fatalf("expected one query") + } + var raw map[string]interface{} + if err := json.Unmarshal(out.Queries[0].JSON, &raw); err != nil { + t.Fatal(err) + } + if raw["owner"] != "grafana" || raw["repository"] != "grafana" { + t.Errorf("owner/repository: got %v / %v", raw["owner"], raw["repository"]) + } + }) + + t.Run("tableParameterValues override owner and repository from table suffix", func(t *testing.T) { + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_oldorg_oldrepo","tableParameterValues":{"organization":"grafana","repository":"grafana"}}`) + req := &backend.QueryDataRequest{ + PluginContext: pluginCtxWithFeatureToggle(), + Queries: []backend.DataQuery{ + {RefID: "A", JSON: queryJSON}, + }, + } + out := normalizeGrafanaSQLRequest(req) + if out == nil || len(out.Queries) != 1 { + t.Fatalf("expected one query") + } + var raw map[string]interface{} + if err := json.Unmarshal(out.Queries[0].JSON, &raw); err != nil { + t.Fatal(err) + } + if raw["owner"] != "grafana" || raw["repository"] != "grafana" { + t.Errorf("owner/repository: got %v / %v", raw["owner"], raw["repository"]) + } + }) + + t.Run("maps workflow table parameter to options.workflow", func(t *testing.T) { + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"workflow-usage","tableParameterValues":{"organization":"grafana","repository":"grafana","workflow":"build.yml"}}`) + req := &backend.QueryDataRequest{ + PluginContext: pluginCtxWithFeatureToggle(), + Queries: []backend.DataQuery{ + {RefID: "A", JSON: queryJSON}, + }, + } + out := normalizeGrafanaSQLRequest(req) + if out == nil || len(out.Queries) != 1 { + t.Fatalf("expected one query") + } + var raw map[string]interface{} + if err := json.Unmarshal(out.Queries[0].JSON, &raw); err != nil { + t.Fatal(err) + } + if raw["owner"] != "grafana" || raw["repository"] != "grafana" { + t.Errorf("owner/repository: got %v / %v", raw["owner"], raw["repository"]) + } + opts, _ := raw["options"].(map[string]interface{}) + workflow, _ := opts["workflow"].(string) + if workflow != "build.yml" { + t.Errorf("expected options.workflow 'build.yml', got %q", workflow) + } + }) + + t.Run("maps projects organization from tableParameterValues", func(t *testing.T) { + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"projects","tableParameterValues":{"organization":"grafana"}}`) + req := &backend.QueryDataRequest{ + PluginContext: pluginCtxWithFeatureToggle(), + Queries: []backend.DataQuery{ + {RefID: "A", JSON: queryJSON}, + }, + } + out := normalizeGrafanaSQLRequest(req) + if out == nil || len(out.Queries) != 1 { + t.Fatalf("expected one query") + } + var raw map[string]interface{} + if err := json.Unmarshal(out.Queries[0].JSON, &raw); err != nil { + t.Fatal(err) + } + opts, _ := raw["options"].(map[string]interface{}) + org, _ := opts["organization"].(string) + if org != "grafana" { + t.Errorf("expected options.organization 'grafana', got %q", org) + } + }) + t.Run("leaves non-grafanaSql query unchanged", func(t *testing.T) { queryJSON := []byte(`{"refId":"A","queryType":"Pull_Requests","owner":"grafana","repository":"grafana"}`) req := &backend.QueryDataRequest{ @@ -315,7 +429,7 @@ func TestNormalizeGrafanaSQLRequest(t *testing.T) { func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { t.Run("pushes down state filter for issues", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"column":"state","op":"==","value":"open"}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"state","conditions":[{"operator":"==","value":"open"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -341,7 +455,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down author filter for issues", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"column":"author","op":"==","value":"octocat"}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"author","conditions":[{"operator":"==","value":"octocat"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -361,7 +475,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down multiple filters for issues", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"column":"state","op":"==","value":"open"},{"column":"labels","op":"==","value":"bug"}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"state","conditions":[{"operator":"==","value":"open"}]},{"name":"labels","conditions":[{"operator":"==","value":"bug"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -380,8 +494,48 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { } }) + t.Run("pushes down JSON array value for issues labels", func(t *testing.T) { + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"labels","conditions":[{"operator":"in","values":["bug","triage"]}]}]}`) + req := &backend.QueryDataRequest{ + PluginContext: pluginCtxWithFeatureToggle(), + Queries: []backend.DataQuery{ + {RefID: "A", JSON: queryJSON}, + }, + } + out := normalizeGrafanaSQLRequest(req) + var raw map[string]interface{} + if err := json.Unmarshal(out.Queries[0].JSON, &raw); err != nil { + t.Fatal(err) + } + opts := raw["options"].(map[string]interface{}) + query := opts["query"].(string) + if query != "label:bug label:triage" { + t.Errorf("expected query 'label:bug label:triage', got %q", query) + } + }) + + t.Run("pushes down IN values for assignees", func(t *testing.T) { + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"assignees","conditions":[{"operator":"in","values":["alice","bob"]}]}]}`) + req := &backend.QueryDataRequest{ + PluginContext: pluginCtxWithFeatureToggle(), + Queries: []backend.DataQuery{ + {RefID: "A", JSON: queryJSON}, + }, + } + out := normalizeGrafanaSQLRequest(req) + var raw map[string]interface{} + if err := json.Unmarshal(out.Queries[0].JSON, &raw); err != nil { + t.Fatal(err) + } + opts := raw["options"].(map[string]interface{}) + query := opts["query"].(string) + if query != "assignee:alice assignee:bob" { + t.Errorf("expected query 'assignee:alice assignee:bob', got %q", query) + } + }) + t.Run("pushes down state filter for code-scanning", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"code-scanning_grafana_grafana","filters":[{"column":"state","op":"==","value":"open"}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"code-scanning_grafana_grafana","filters":[{"name":"state","conditions":[{"operator":"==","value":"open"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -401,7 +555,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down branch filter for workflow-runs", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"workflow-runs_grafana_grafana","filters":[{"column":"head_branch","op":"==","value":"main"}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"workflow-runs_grafana_grafana","filters":[{"name":"head_branch","conditions":[{"operator":"==","value":"main"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -421,7 +575,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down status filter for workflow-runs", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"workflow-runs_grafana_grafana","filters":[{"column":"status","op":"==","value":"completed"}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"workflow-runs_grafana_grafana","filters":[{"name":"status","conditions":[{"operator":"==","value":"completed"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -441,7 +595,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down draft filter for pull-requests", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"pull-requests_grafana_grafana","filters":[{"column":"is_draft","op":"==","value":"true"}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"pull-requests_grafana_grafana","filters":[{"name":"is_draft","conditions":[{"operator":"==","value":"true"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -461,7 +615,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down name filter for labels (like)", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"labels_grafana_grafana","filters":[{"column":"name","op":"like","value":"bug"}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"labels_grafana_grafana","filters":[{"name":"name","conditions":[{"operator":"like","value":"bug"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -481,7 +635,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down package type filter", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"packages_grafana_grafana","filters":[{"column":"type","op":"==","value":"DOCKER"}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"packages_grafana_grafana","filters":[{"name":"type","conditions":[{"operator":"==","value":"DOCKER"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -501,7 +655,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down name filter for repositories", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"repositories_grafana","filters":[{"column":"name","op":"like","value":"grafana"}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"repositories_grafana","filters":[{"name":"name","conditions":[{"operator":"like","value":"grafana"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -519,7 +673,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down tool_name filter for code-scanning", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"code-scanning_grafana_grafana","filters":[{"column":"tool_name","op":"==","value":"CodeQL"}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"code-scanning_grafana_grafana","filters":[{"name":"tool_name","conditions":[{"operator":"==","value":"CodeQL"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -539,7 +693,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("ignores filters with empty values", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"column":"state","op":"==","value":""}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"state","conditions":[{"operator":"==","value":""}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ From 7c74e0b8e2397d47a09872bf578094c19e7a0bf0 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Mon, 16 Mar 2026 12:15:09 +0000 Subject: [PATCH 5/9] Upgrade schemads --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 8a2d0903..939e2a58 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 github.com/google/go-github/v81 v81.0.0 github.com/grafana/grafana-plugin-sdk-go v0.290.0 - github.com/grafana/schemads v0.0.2 + github.com/grafana/schemads v0.0.3 github.com/influxdata/tdigest v0.0.1 github.com/pkg/errors v0.9.1 github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed diff --git a/go.sum b/go.sum index 5ffe24a5..5fc9f4ee 100644 --- a/go.sum +++ b/go.sum @@ -98,6 +98,8 @@ github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grafana/schemads v0.0.3 h1:vh8q6QcaCLtRhsZn+xlu5X663u4VK/yfDiqD8HwXIKE= +github.com/grafana/schemads v0.0.3/go.mod h1:i/iRKic1c9i/ZjApKe7+BDQjPhlQO7gWycVRu7x6c1U= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= From 2e1b4c63233fa50bd188b87ca08c462cdd21b22d Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Mon, 16 Mar 2026 12:23:31 +0000 Subject: [PATCH 6/9] Spellcheck --- cspell.config.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cspell.config.json b/cspell.config.json index c45504e7..b6f51196 100644 --- a/cspell.config.json +++ b/cspell.config.json @@ -62,6 +62,8 @@ "confg", "octocat", "schemads", - "featuretoggles" + "featuretoggles", + "oldorg", + "oldrepo" ] } From 398bdfc6cdfefb3b309c356d74a2f7964da474d5 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Wed, 18 Mar 2026 13:45:36 +0000 Subject: [PATCH 7/9] Update version and changelog --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dac7032f..ae36e64f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 2.7.1 + +⚙️ Updating experimental schemas. + ## 2.7.0 🚀 Adding support for experimental schemas. diff --git a/package.json b/package.json index b3a27745..1796d1f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grafana-github-datasource", - "version": "2.7.0", + "version": "2.7.1", "private": true, "description": "Grafana data source plugin for Github", "repository": "github:grafana/github-datasource", From 0ee0848ea842ce4923e055aaec46fe0f67015ac2 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Wed, 18 Mar 2026 17:26:48 +0000 Subject: [PATCH 8/9] Update schemads and operator uses --- go.mod | 2 +- go.sum | 4 ++-- pkg/github/sql.go | 51 +++++++++++++++++++++++------------------------ 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index 939e2a58..88224197 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 github.com/google/go-github/v81 v81.0.0 github.com/grafana/grafana-plugin-sdk-go v0.290.0 - github.com/grafana/schemads v0.0.3 + github.com/grafana/schemads v0.0.5 github.com/influxdata/tdigest v0.0.1 github.com/pkg/errors v0.9.1 github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed diff --git a/go.sum b/go.sum index 5fc9f4ee..d5d4c343 100644 --- a/go.sum +++ b/go.sum @@ -98,8 +98,8 @@ github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= -github.com/grafana/schemads v0.0.3 h1:vh8q6QcaCLtRhsZn+xlu5X663u4VK/yfDiqD8HwXIKE= -github.com/grafana/schemads v0.0.3/go.mod h1:i/iRKic1c9i/ZjApKe7+BDQjPhlQO7gWycVRu7x6c1U= +github.com/grafana/schemads v0.0.5 h1:nVDpWTb+NPHmUCOaqm38pwg+JDJxb28rd6j4+7kwYD4= +github.com/grafana/schemads v0.0.5/go.mod h1:i/iRKic1c9i/ZjApKe7+BDQjPhlQO7gWycVRu7x6c1U= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= diff --git a/pkg/github/sql.go b/pkg/github/sql.go index 2f74b1df..7dfb5af8 100644 --- a/pkg/github/sql.go +++ b/pkg/github/sql.go @@ -79,8 +79,8 @@ func applyFilters(queryType string, options map[string]interface{}, filters []sc options["options"] = opts } - appendEqualitySearchQualifier := func(name, operator string, values []string, isJSON bool) { - if operator == "==" || operator == "=" || operator == "in" { + appendEqualitySearchQualifier := func(name string, operator schemas.Operator, values []string, isJSON bool) { + if operator == schemas.OperatorEquals || operator == schemas.OperatorIn { for _, value := range values { if isJSON { for _, v := range parseJSONStringValues(value) { @@ -92,8 +92,8 @@ func applyFilters(queryType string, options map[string]interface{}, filters []sc } } } - setOption := func(name, op, value string) { - if op == "==" || op == "=" || op == "in" { + setOption := func(name string, operator schemas.Operator, value string) { + if operator == schemas.OperatorEquals || operator == schemas.OperatorIn { opts[name] = value } } @@ -104,7 +104,6 @@ func applyFilters(queryType string, options map[string]interface{}, filters []sc } for _, condition := range f.Conditions { - op := strings.ToLower(strings.TrimSpace(condition.Operator)) values := extractFilterValues(condition) if len(values) == 0 { continue @@ -114,26 +113,26 @@ func applyFilters(queryType string, options map[string]interface{}, filters []sc case models.QueryTypeIssues: switch f.Name { case "state": - appendEqualitySearchQualifier(f.Name, op, values, false) + appendEqualitySearchQualifier(f.Name, condition.Operator, values, false) case "author": - appendEqualitySearchQualifier(f.Name, op, values, false) + appendEqualitySearchQualifier(f.Name, condition.Operator, values, false) case "labels": - appendEqualitySearchQualifier("label", op, values, true) + appendEqualitySearchQualifier("label", condition.Operator, values, true) case "assignees": - appendEqualitySearchQualifier("assignee", op, values, true) + appendEqualitySearchQualifier("assignee", condition.Operator, values, true) case "milestone": - appendEqualitySearchQualifier("milestone", op, values, false) + appendEqualitySearchQualifier("milestone", condition.Operator, values, false) } case models.QueryTypePullRequests, models.QueryTypePullRequestReviews: switch f.Name { case "state": - appendEqualitySearchQualifier("state", op, values, false) + appendEqualitySearchQualifier("state", condition.Operator, values, false) case "author_login": - appendEqualitySearchQualifier("author", op, values, false) + appendEqualitySearchQualifier("author", condition.Operator, values, false) case "labels": - appendEqualitySearchQualifier("label", op, values, true) + appendEqualitySearchQualifier("label", condition.Operator, values, true) case "is_draft": - if op == "==" || op == "=" || op == "in" { + if condition.Operator == schemas.OperatorEquals || condition.Operator == schemas.OperatorIn { for _, value := range values { if value == "true" { searchQualifiers = append(searchQualifiers, "draft:true") @@ -146,37 +145,37 @@ func applyFilters(queryType string, options map[string]interface{}, filters []sc case models.QueryTypeCodeScanning: switch f.Name { case "state": - setOption("state", op, values[0]) + setOption("state", condition.Operator, values[0]) case "rule_severity": - setOption("severity", op, values[0]) + setOption("severity", condition.Operator, values[0]) case "tool_name": - setOption("toolName", op, values[0]) + setOption("toolName", condition.Operator, values[0]) } case models.QueryTypeWorkflowRuns: switch f.Name { case "head_branch": - setOption("branch", op, values[0]) + setOption("branch", condition.Operator, values[0]) case "status": - setOption("status", op, values[0]) + setOption("status", condition.Operator, values[0]) case "event": - setOption("event", op, values[0]) + setOption("event", condition.Operator, values[0]) } case models.QueryTypeContributors: - if f.Name == "name" && (op == "like" || op == "==" || op == "=" || op == "in") { + if f.Name == "name" && (condition.Operator == schemas.OperatorLike || condition.Operator == schemas.OperatorEquals || condition.Operator == schemas.OperatorIn) { opts["query"] = values[0] } case models.QueryTypeLabels: - if f.Name == "name" && (op == "like" || op == "==" || op == "=" || op == "in") { + if f.Name == "name" && (condition.Operator == schemas.OperatorLike || condition.Operator == schemas.OperatorEquals || condition.Operator == schemas.OperatorIn) { opts["query"] = values[0] } case models.QueryTypeMilestones: - if f.Name == "title" && (op == "like" || op == "==" || op == "=" || op == "in") { + if f.Name == "title" && (condition.Operator == schemas.OperatorLike || condition.Operator == schemas.OperatorEquals || condition.Operator == schemas.OperatorIn) { opts["query"] = values[0] } case models.QueryTypePackages: switch f.Name { case "name": - if op == "==" || op == "=" || op == "in" { + if condition.Operator == schemas.OperatorEquals || condition.Operator == schemas.OperatorIn { for _, value := range values { existing, _ := opts["names"].(string) if existing != "" { @@ -187,12 +186,12 @@ func applyFilters(queryType string, options map[string]interface{}, filters []sc } } case "type": - if op == "==" || op == "=" || op == "in" { + if condition.Operator == schemas.OperatorEquals || condition.Operator == schemas.OperatorIn { opts["packageType"] = values[0] } } case models.QueryTypeRepositories: - if f.Name == "name" && (op == "like" || op == "==" || op == "=" || op == "in") { + if f.Name == "name" && (condition.Operator == schemas.OperatorLike || condition.Operator == schemas.OperatorEquals || condition.Operator == schemas.OperatorIn) { options["repository"] = values[0] } } From 60a8b9fb816a6646765af21efa4cffc85eb78d35 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Thu, 19 Mar 2026 12:50:42 +0000 Subject: [PATCH 9/9] Fix tests --- pkg/github/sql_handler_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/github/sql_handler_test.go b/pkg/github/sql_handler_test.go index 234595e5..e8163f97 100644 --- a/pkg/github/sql_handler_test.go +++ b/pkg/github/sql_handler_test.go @@ -429,7 +429,7 @@ func TestNormalizeGrafanaSQLRequest(t *testing.T) { func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { t.Run("pushes down state filter for issues", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"state","conditions":[{"operator":"==","value":"open"}]}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"state","conditions":[{"operator":"=","value":"open"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -455,7 +455,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down author filter for issues", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"author","conditions":[{"operator":"==","value":"octocat"}]}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"author","conditions":[{"operator":"=","value":"octocat"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -475,7 +475,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down multiple filters for issues", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"state","conditions":[{"operator":"==","value":"open"}]},{"name":"labels","conditions":[{"operator":"==","value":"bug"}]}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"state","conditions":[{"operator":"=","value":"open"}]},{"name":"labels","conditions":[{"operator":"=","value":"bug"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -535,7 +535,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down state filter for code-scanning", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"code-scanning_grafana_grafana","filters":[{"name":"state","conditions":[{"operator":"==","value":"open"}]}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"code-scanning_grafana_grafana","filters":[{"name":"state","conditions":[{"operator":"=","value":"open"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -555,7 +555,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down branch filter for workflow-runs", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"workflow-runs_grafana_grafana","filters":[{"name":"head_branch","conditions":[{"operator":"==","value":"main"}]}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"workflow-runs_grafana_grafana","filters":[{"name":"head_branch","conditions":[{"operator":"=","value":"main"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -575,7 +575,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down status filter for workflow-runs", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"workflow-runs_grafana_grafana","filters":[{"name":"status","conditions":[{"operator":"==","value":"completed"}]}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"workflow-runs_grafana_grafana","filters":[{"name":"status","conditions":[{"operator":"=","value":"completed"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -595,7 +595,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down draft filter for pull-requests", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"pull-requests_grafana_grafana","filters":[{"name":"is_draft","conditions":[{"operator":"==","value":"true"}]}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"pull-requests_grafana_grafana","filters":[{"name":"is_draft","conditions":[{"operator":"=","value":"true"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -635,7 +635,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down package type filter", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"packages_grafana_grafana","filters":[{"name":"type","conditions":[{"operator":"==","value":"DOCKER"}]}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"packages_grafana_grafana","filters":[{"name":"type","conditions":[{"operator":"=","value":"DOCKER"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -673,7 +673,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("pushes down tool_name filter for code-scanning", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"code-scanning_grafana_grafana","filters":[{"name":"tool_name","conditions":[{"operator":"==","value":"CodeQL"}]}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"code-scanning_grafana_grafana","filters":[{"name":"tool_name","conditions":[{"operator":"=","value":"CodeQL"}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{ @@ -693,7 +693,7 @@ func TestNormalizeGrafanaSQLRequestWithFilters(t *testing.T) { }) t.Run("ignores filters with empty values", func(t *testing.T) { - queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"state","conditions":[{"operator":"==","value":""}]}]}`) + queryJSON := []byte(`{"refId":"A","grafanaSql":true,"table":"issues_grafana_grafana","filters":[{"name":"state","conditions":[{"operator":"=","value":""}]}]}`) req := &backend.QueryDataRequest{ PluginContext: pluginCtxWithFeatureToggle(), Queries: []backend.DataQuery{