Graphite: Backend tag values autocomplete endpoint (#110773)

* Add lint rules

* Backend decoupling

- Add standalone files
- Add graphite query type
- Add logger to Service
- Create logger in the ProvideService method
- Use a pointer for the HTTP client provider
- Update logger usage everywhere
- Update tracer type
- Replace simplejson with json
- Add dummy CallResource and CheckHealth methods
- Update tests

* Update ConfigEditor imports

* Update types imports

* Update datasource

- Switch to using semver package
- Update imports

* Update store imports

* Update helper imports and notification creation

* Update context import

* Update version numbers and logic

* Copy array_move from core

* Test updates

* Add required files and update plugin.json

* Update core references and packages

* Remove commented code

* Update wire

* Lint

* Fix import

* Copy null type

* More lint

* Update snapshot

* Refactor backend

- Split query logic into separate file
- Move utils to separate file

* Add health-check logic

- Support backend healthcheck if the FF is enabled

* Remove query import support as unneeded

* Add test

* Add util function for decoding responses

* Add events types

* Add resource handler

* Add events handler and generic resource req handler

* Tests

* Update frontend

- Add types
- Update events function to support backend requests

* Lint and typing

* Lint

* Add metrics find endpoint

- Add types
- Add generic response parser
- Add endpoint
- Tests

* Update FE functoin to use backend endpoint

* Lint

* Simplify request

* Update test

* Metrics expand type

* Extract shared logic and add metric expand endpoint

* Update tests

* Call metric expand from backend

* Rename type for clarity

* Add get resource req handler

* Refactor doGraphiteRequest, parseResponse

Update tests

* Migrate functions endpoint to backend

* Support tags autocomplete in backend

- Add tests
- Add types
- Remove unneeded comments

* Support tag values autocomplete

- Remove unused frontend endpoints
- Add types
- Update tests

* Add tests

* Review

* Review

* Fix packages

* Format

* Fix merge issues

* Review

* Fix undefined values

* Extract request creation

- Add method for create requests generically with tests
- Replace usage in query method
- Update usages in resource handlers
- Update tests
- Update types

* Lint
This commit is contained in:
Andreas Christou
2025-09-15 11:35:29 +01:00
committed by GitHub
parent f392bb6f94
commit df2bb6be0a
7 changed files with 243 additions and 109 deletions
+122
View File
@@ -9,6 +9,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
@@ -498,6 +499,127 @@ func TestHandleTagsAutocomplete(t *testing.T) {
})
}
}
func TestHandleTagValuesAutocomplete(t *testing.T) {
tests := []struct {
name string
request GraphiteTagValuesRequest
responseBody string
statusCode int
expectError bool
errorContains string
expectedData []string
}{
{
name: "successful tag values autocomplete request",
request: GraphiteTagValuesRequest{
Expr: []string{"app=*"},
Tag: "environment",
From: "1h",
Until: "now",
Limit: 5,
ValuePrefix: "prod",
},
responseBody: `["production", "prod-eu", "prod-us"]`,
statusCode: 200,
expectedData: []string{"production", "prod-eu", "prod-us"},
},
{
name: "multiple expressions",
request: GraphiteTagValuesRequest{
Expr: []string{"app=*", "region=us-*"},
Tag: "environment",
From: "1h",
Until: "now",
Limit: 5,
ValuePrefix: "prod",
},
responseBody: `["production", "prod-eu", "prod-us"]`,
statusCode: 200,
expectedData: []string{"production", "prod-eu", "prod-us"},
},
{
name: "tag values autocomplete with empty response",
request: GraphiteTagValuesRequest{
Expr: []string{"app=nonexistent"},
Tag: "environment",
ValuePrefix: "staging",
},
responseBody: `[]`,
statusCode: 200,
expectedData: []string{},
},
{
name: "tag values autocomplete server error",
request: GraphiteTagValuesRequest{
Expr: []string{"invalid-expr"},
Tag: "env",
},
responseBody: `invalid json response`,
statusCode: 400,
expectError: true,
errorContains: "tag values autocomplete request failed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockTransport := &mockRoundTripper{
respBody: []byte(tt.responseBody),
status: tt.statusCode,
}
dsInfo := &datasourceInfo{
HTTPClient: &http.Client{Transport: mockTransport},
URL: "http://graphite.example.com",
}
service := &Service{
logger: log.NewNullLogger(),
}
result, statusCode, err := service.handleTagValuesAutocomplete(context.Background(), dsInfo, &tt.request)
if tt.expectError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
} else {
assert.NoError(t, err)
assert.Equal(t, tt.statusCode, statusCode)
var tagValues []string
err = json.Unmarshal(result, &tagValues)
assert.NoError(t, err)
assert.Equal(t, tt.expectedData, tagValues)
}
if !tt.expectError {
expectedURL := "http://graphite.example.com/tags/autoComplete/values"
assert.Contains(t, mockTransport.lastRequest.URL.String(), expectedURL)
for _, expr := range tt.request.Expr {
assert.Contains(t, mockTransport.lastRequest.URL.RawQuery, fmt.Sprintf("expr=%s", url.QueryEscape(expr)))
}
assert.Contains(t, mockTransport.lastRequest.URL.RawQuery, fmt.Sprintf("tag=%s", tt.request.Tag))
if tt.request.From != "" {
assert.Contains(t, mockTransport.lastRequest.URL.RawQuery, fmt.Sprintf("from=%s", tt.request.From))
}
if tt.request.Until != "" {
assert.Contains(t, mockTransport.lastRequest.URL.RawQuery, fmt.Sprintf("until=%s", tt.request.Until))
}
if tt.request.Limit != 0 {
assert.Contains(t, mockTransport.lastRequest.URL.RawQuery, fmt.Sprintf("limit=%d", tt.request.Limit))
}
if tt.request.ValuePrefix != "" {
assert.Contains(t, mockTransport.lastRequest.URL.RawQuery, fmt.Sprintf("valuePrefix=%s", tt.request.ValuePrefix))
}
}
})
}
}
func TestHandleFunctions(t *testing.T) {
tests := []struct {
name string