Files
grafana/pkg/tsdb/graphite/graphite.go
Andreas Christou df2bb6be0a 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
2025-09-15 11:35:29 +01:00

137 lines
3.3 KiB
Go

package graphite
import (
"context"
"fmt"
"net/http"
"net/url"
"path"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
"go.opentelemetry.io/otel/trace"
)
type Service struct {
im instancemgmt.InstanceManager
tracer trace.Tracer
logger log.Logger
resourceHandler backend.CallResourceHandler
HTTPClient *http.Client
}
const (
TargetFullModelField = "targetFull"
TargetModelField = "target"
)
func ProvideService(httpClientProvider *httpclient.Provider, tracer trace.Tracer) *Service {
logger := backend.NewLoggerWith("logger", "graphite")
s := &Service{
im: datasource.NewInstanceManager(newInstanceSettings(httpClientProvider)),
tracer: tracer,
logger: logger,
}
s.resourceHandler = httpadapter.New(s.newResourceMux())
return s
}
type datasourceInfo struct {
HTTPClient *http.Client
URL string
Id int64
}
func newInstanceSettings(httpClientProvider *httpclient.Provider) datasource.InstanceFactoryFunc {
return func(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
opts, err := settings.HTTPClientOptions(ctx)
if err != nil {
return nil, err
}
client, err := httpClientProvider.New(opts)
if err != nil {
return nil, err
}
model := datasourceInfo{
HTTPClient: client,
URL: settings.URL,
Id: settings.ID,
}
return model, nil
}
}
func (s *Service) getDSInfo(ctx context.Context, pluginCtx backend.PluginContext) (*datasourceInfo, error) {
i, err := s.im.Get(ctx, pluginCtx)
if err != nil {
return nil, err
}
instance := i.(datasourceInfo)
return &instance, nil
}
func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
if len(req.Queries) == 0 {
return nil, fmt.Errorf("query contains no queries")
}
// get datasource info from context
dsInfo, err := s.getDSInfo(ctx, req.PluginContext)
if err != nil {
return nil, err
}
return s.RunQuery(ctx, req, dsInfo)
}
func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
return s.resourceHandler.CallResource(ctx, req, sender)
}
func (s *Service) createRequest(ctx context.Context, dsInfo *datasourceInfo, params URLParams) (*http.Request, error) {
u, err := url.Parse(dsInfo.URL)
if err != nil {
return nil, err
}
if params.SubPath != "" {
u.Path = path.Join(u.Path, params.SubPath)
}
if params.QueryParams != nil {
queryValues := u.Query()
for key, values := range params.QueryParams {
for _, value := range values {
queryValues.Add(key, value)
}
}
u.RawQuery = queryValues.Encode()
}
method := params.Method
if method == "" {
method = http.MethodGet
}
req, err := http.NewRequestWithContext(ctx, method, u.String(), params.Body)
if err != nil {
s.logger.Info("Failed to create request", "error", err)
return nil, fmt.Errorf("failed to create request: %w", err)
}
for k, v := range params.Headers {
req.Header.Add(k, v)
}
return req, err
}