Query Service: Combine SSE handling in single tenant and multi tenant paths (#108041)

* parse via sse

I need to figure out how to handle the pipeline.execute with our own
client. I think this is important for MT reasons, just like using our
own cache (via legacy) is important.

parsing is done though!

* WIP nonsense

* horrible code but i think it works

* Add support for sql expressions config settings

* Cleanup:
- remove spew from nodes.go
- uncomment out plugin context and use in single tenant flow
- make code more readable and add comments

* Cleanup:
- create separate file for mt ds client builder
- ensure error handling is the same for both expressions and regular queries
- other cleanup

* not working but good thoughts

* WIP, vector not working for non sse

* super hacky but i think vectors work now

* delete delete delete

* Comments for future ref

* break out query handling and start test

* add prom debugger

* clean up: remove comments and commented out bits

* fix query_test

* add prom debugger

* create table-driven tests with testsdata files

* Fix test

* Add test

* go mod??

* idk

* Remove comment

* go enterprise issue maybe

* Fix codeowners

* Delete

* Remove test data

* Clean up

* logger

* Remove go changes hopefully

* idk go man

* sad

* idk i ran go mod tidy and this is what it wants

* Fix readme, with much help from adam

* some linting and testing errors

* lint

* fix lint

* fix lint register.go

* another lint

* address lint in test

* fix dead code and linters for query_test

* Go mod?

* Struggling with go mod

* Fix test

* Fix another test

* Revert headers change

* Its difficult to test this in OSS as it depends on functionality defined in enterprise, let's bring these tests back in some form in enterprise

* Fix codeowners

---------

Co-authored-by: Adam Simpson <adam@adamsimpson.net>
This commit is contained in:
Sarah Zinger
2025-07-17 17:22:55 -04:00
committed by GitHub
co-authored by Adam Simpson
parent 8acdc7e37b
commit 3fad863fd1
34 changed files with 763 additions and 1662 deletions
-167
View File
@@ -1,167 +0,0 @@
package dashboards
import (
"context"
"encoding/json"
"net/http"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/tests/apis"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/tests/testsuite"
)
func TestMain(m *testing.M) {
testsuite.Run(m)
}
func TestIntegrationSimpleQuery(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: false, // dev mode required for datasource connections
EnableFeatureToggles: []string{
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the example service
},
})
// Create a single datasource
ds := helper.CreateDS(&datasources.AddDataSourceCommand{
Name: "test",
Type: datasources.DS_TESTDATA,
UID: "test",
OrgID: int64(1),
})
require.Equal(t, "test", ds.UID)
t.Run("Call query with expression", func(t *testing.T) {
client := helper.Org1.Admin.RESTClient(t, &schema.GroupVersion{
Group: "query.grafana.app",
Version: "v0alpha1",
})
body, err := json.Marshal(&data.QueryDataRequest{
Queries: []data.DataQuery{
data.NewDataQuery(map[string]any{
"refId": "X",
"datasource": data.DataSourceRef{
Type: "grafana-testdata-datasource",
UID: ds.UID,
},
"scenarioId": "csv_content",
"csvContent": "a\n1",
}),
data.NewDataQuery(map[string]any{
"refId": "Y",
"datasource": data.DataSourceRef{
UID: "__expr__",
},
"type": "math",
"expression": "$X + 2",
}),
},
})
//t.Logf("%s", string(body))
require.NoError(t, err)
result := client.Post().
Namespace("default").
Suffix("query").
SetHeader("Content-type", "application/json").
Body(body).
Do(context.Background())
require.NoError(t, result.Error())
contentType := "?"
result.ContentType(&contentType)
require.Equal(t, "application/json", contentType)
body, err = result.Raw()
require.NoError(t, err)
t.Logf("OUT: %s", string(body))
rsp := &backend.QueryDataResponse{}
err = json.Unmarshal(body, rsp)
require.NoError(t, err)
require.Equal(t, 2, len(rsp.Responses))
frameX := rsp.Responses["X"].Frames[0]
frameY := rsp.Responses["Y"].Frames[0]
vX, _ := frameX.Fields[0].ConcreteAt(0)
vY, _ := frameY.Fields[0].ConcreteAt(0)
require.Equal(t, int64(1), vX)
require.Equal(t, float64(3), vY) // 1 + 2, but always float64
})
t.Run("Gets an error with invalid queries", func(t *testing.T) {
client := helper.Org1.Admin.RESTClient(t, &schema.GroupVersion{
Group: "query.grafana.app",
Version: "v0alpha1",
})
body, err := json.Marshal(&data.QueryDataRequest{
Queries: []data.DataQuery{
data.NewDataQuery(map[string]any{
"refId": "Y",
"datasource": data.DataSourceRef{
UID: "__expr__",
},
"type": "math",
"expression": "$X + 2", // invalid X does not exit
}),
},
})
require.NoError(t, err)
result := client.Post().
Namespace("default").
Suffix("query").
SetHeader("Content-type", "application/json").
Body(body).
Do(context.Background())
body, err = result.Raw()
//t.Logf("OUT: %s", string(body))
require.Error(t, err, "expecting a 400")
require.JSONEq(t, `{
"results": {
"A": {
"error": "[sse.dependencyError] did not execute expression [Y] due to a failure of the dependent expression or query [X]",
"status": 400,
"errorSource": ""
}
}
}`, string(body))
// require.JSONEq(t, `{
// "status": "Failure",
// "metadata": {},
// "message": "did not execute expression [Y] due to a failure of the dependent expression or query [X]",
// "reason": "BadRequest",
// "details": { "group": "query.grafana.app" },
// "code": 400,
// "messageId": "sse.dependencyError",
// "extra": { "depRefId": "X", "refId": "Y" }
// }`, string(body))
statusCode := -1
contentType := "?"
result.ContentType(&contentType)
result.StatusCode(&statusCode)
require.Equal(t, "application/json", contentType)
require.Equal(t, http.StatusBadRequest, statusCode)
})
}