From bd83646bd4220cb90c0a2320f2190208cc7325c1 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Thu, 6 Mar 2025 12:21:10 +0100 Subject: [PATCH] PromLib: Take AdHoc filters into account when requesting suggestions without label (#101555) * Check for adhoc filters, even when no timeserie name is used * Add test --- pkg/promlib/resource/resource.go | 4 +- pkg/promlib/resource/resource_test.go | 84 ++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/pkg/promlib/resource/resource.go b/pkg/promlib/resource/resource.go index 0673a5e0b41..93846c1074f 100644 --- a/pkg/promlib/resource/resource.go +++ b/pkg/promlib/resource/resource.go @@ -173,8 +173,8 @@ func (r *Resource) GetSuggestions(ctx context.Context, req *backend.CallResource values.Add("match[]", vs.String()) } - // if no timeserie name is provided, but scopes are, the scope is still rendered and passed as match param. - if len(selectorList) == 0 && len(sugReq.Scopes) > 0 { + // if no timeserie name is provided, but scopes or adhoc filters are, the scope is still rendered and passed as match param. + if len(selectorList) == 0 && len(matchers) > 0 { vs := parser.VectorSelector{LabelMatchers: matchers} values.Add("match[]", vs.String()) } diff --git a/pkg/promlib/resource/resource_test.go b/pkg/promlib/resource/resource_test.go index a9b7ed21b60..b569e0004c9 100644 --- a/pkg/promlib/resource/resource_test.go +++ b/pkg/promlib/resource/resource_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "io" "net/http" + "net/url" "testing" "github.com/grafana/grafana-plugin-sdk-go/backend" @@ -13,15 +14,20 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/promlib/models" "github.com/grafana/grafana/pkg/promlib/resource" ) type mockRoundTripper struct { - Response *http.Response - Err error + Response *http.Response + Err error + customRoundTrip func(req *http.Request) (*http.Response, error) } func (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + if m.customRoundTrip != nil { + return m.customRoundTrip(req) + } return m.Response, m.Err } @@ -107,3 +113,77 @@ func TestResource_GetSuggestions(t *testing.T) { require.NoError(t, err) assert.NotNil(t, resp) } + +func TestResource_GetSuggestionsWithEmptyQueriesButFilters(t *testing.T) { + var capturedURL string + + // Create a mock transport that captures the request URL + mockTransport := &mockRoundTripper{ + Response: &http.Response{ + StatusCode: 200, + Body: io.NopCloser(bytes.NewReader([]byte(`{"status":"success","data":[]}`))), + Header: make(http.Header), + }, + customRoundTrip: func(req *http.Request) (*http.Response, error) { + capturedURL = req.URL.String() + return &http.Response{ + StatusCode: 200, + Body: io.NopCloser(bytes.NewReader([]byte(`{"status":"success","data":[]}`))), + Header: make(http.Header), + }, nil + }, + } + + // Create a client with the mock transport + mockClient := &http.Client{ + Transport: mockTransport, + } + + settings := backend.DataSourceInstanceSettings{ + ID: 1, + URL: "http://localhost:9090", + JSONData: []byte(`{"httpMethod": "GET"}`), + } + + res, err := resource.New(mockClient, settings, log.DefaultLogger) + require.NoError(t, err) + + // Create a request with empty queries but with filters + suggestionReq := resource.SuggestionRequest{ + Queries: []string{}, // Empty queries + Scopes: []models.ScopeFilter{ + {Key: "job", Operator: models.FilterOperatorEquals, Value: "testjob"}, + }, + AdhocFilters: []models.ScopeFilter{ + {Key: "instance", Operator: models.FilterOperatorEquals, Value: "localhost:9090"}, + }, + } + + body, err := json.Marshal(suggestionReq) + require.NoError(t, err) + + req := &backend.CallResourceRequest{ + Body: body, + } + ctx := context.Background() + + resp, err := res.GetSuggestions(ctx, req) + require.NoError(t, err) + assert.NotNil(t, resp) + + // Parse the captured URL to get the query parameters + parsedURL, err := url.Parse(capturedURL) + require.NoError(t, err) + + // Get the match[] parameter + matchValues := parsedURL.Query()["match[]"] + require.Len(t, matchValues, 1, "Expected exactly one match[] parameter") + + // The actual filter expression should match our expectation, regardless of URL encoding + decodedMatch, err := url.QueryUnescape(matchValues[0]) + require.NoError(t, err) + + // Check that both label matchers are present with their correct values + assert.Contains(t, decodedMatch, `job="testjob"`) + assert.Contains(t, decodedMatch, `instance="localhost:9090"`) +}