mirror of
https://github.com/grafana/grafana.git
synced 2026-08-13 06:34:55 -05:00
Prometheus: Configuration page overhaul (#66198)
* organize layout, make design uniform, add doc link * fix e2e test * move overhauled config parts into prometheus code * update tooltips with doc links * clean component styles for section padding, top and bottom 32px * make additional settings subsection headers h6 * use secondary gray for section descriptions * fix merge issues * change inlineswitch to switch only in prom settings because the other components are shared * remove legacy formfield and input, replace with inlinefield and input from grafana-ui * find more formfield and UI design fixes like changing inlineformlabel to inlinefield * remove unused inline form label * replace legacy duration validations with <FieldValidationMessage> * fix styles secondary gray from theme * change language, headings and datasource -> data source * update alert setting styles with new component * update prom url heading and tooltip * update default editor tooltip and set builder as default editor * update interval tooltip * update prom type tooltip * update custom query params tooltip * update exemplar internal link tooltip * fix inline form styling inconsistency * allow for using the DataSourceHTTPSettings component without the connection string * remove overhaul component, re-use dshtttps comp, and use connection input in config editor * make tooltips interactive to click links * consistent label width across the elements we can control for now, fix exemplar switch * make connection url a component * refactor onBlur validation * remove unused component * add tests for config validations * add more meaningful health check * fix e2e test * fix e2e test * fix e2e test * add error handling for more url errors * remove unnecessary conversion * health check tests * Clean up the health check * health check unit tests * health check unit tests improved * make pretty for drone * lint check go * lint check go * add required attr to connection component * Update public/app/plugins/datasource/prometheus/configuration/Connection.tsx Co-authored-by: Torkel Ödegaard <torkel@grafana.com> * fix read only issue for provisioned datasources * validate multiple durations for incremental query setting * use sentence case for headers * use className consistently for styles * add tests for url regex * remove console logs * only use query as healthcheck as the healthy api is not supported by Mimir * fix exemplar e2e test * remove overhaul prop from custom headers setting component * remove connection section and use DatasourceHttpSettings connection with custom label and interactive tooltip * add spaces back * spaces --------- Co-authored-by: ismail simsek <ismailsimsek09@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
co-authored by
Torkel Ödegaard
ismail simsek
parent
5c32925f9f
commit
0a9240aeba
@@ -0,0 +1,93 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/kindsys"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/tsdb/prometheus/kinds/dataquery"
|
||||
"github.com/grafana/grafana/pkg/tsdb/prometheus/models"
|
||||
)
|
||||
|
||||
const (
|
||||
refID = "__healthcheck__"
|
||||
)
|
||||
|
||||
var logger log.Logger = log.New("tsdb.prometheus")
|
||||
|
||||
func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult,
|
||||
error) {
|
||||
logger := logger.FromContext(ctx)
|
||||
ds, err := s.getInstance(req.PluginContext)
|
||||
|
||||
// check that the datasource exists
|
||||
if err != nil {
|
||||
return getHealthCheckMessage(logger, "error getting datasource info", err)
|
||||
}
|
||||
|
||||
if ds == nil {
|
||||
return getHealthCheckMessage(logger, "", errors.New("invalid datasource info received"))
|
||||
}
|
||||
|
||||
return healthcheck(ctx, req, ds)
|
||||
}
|
||||
|
||||
func healthcheck(ctx context.Context, req *backend.CheckHealthRequest, i *instance) (*backend.CheckHealthResult, error) {
|
||||
qm := models.QueryModel{
|
||||
LegendFormat: "",
|
||||
UtcOffsetSec: 0,
|
||||
PrometheusDataQuery: dataquery.PrometheusDataQuery{
|
||||
Expr: "1+1",
|
||||
Instant: kindsys.Ptr(true),
|
||||
RefId: refID,
|
||||
},
|
||||
}
|
||||
b, _ := json.Marshal(&qm)
|
||||
|
||||
query := backend.DataQuery{
|
||||
RefID: refID,
|
||||
TimeRange: backend.TimeRange{
|
||||
From: time.Unix(1, 0).UTC(),
|
||||
To: time.Unix(4, 0).UTC(),
|
||||
},
|
||||
JSON: b,
|
||||
}
|
||||
resp, err := i.queryData.Execute(ctx, &backend.QueryDataRequest{
|
||||
PluginContext: req.PluginContext,
|
||||
Queries: []backend.DataQuery{query},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return getHealthCheckMessage(logger, "Your configuration has been saved but there is an error.", err)
|
||||
}
|
||||
|
||||
if resp.Responses[refID].Error != nil {
|
||||
return getHealthCheckMessage(logger, "Your configuration has been saved but there is an error.",
|
||||
errors.New(resp.Responses[refID].Error.Error()))
|
||||
}
|
||||
|
||||
return getHealthCheckMessage(logger, "Successfully saved the configuration and queried the Prometheus API.", nil)
|
||||
}
|
||||
|
||||
func getHealthCheckMessage(logger log.Logger, message string, err error) (*backend.CheckHealthResult, error) {
|
||||
if err == nil {
|
||||
return &backend.CheckHealthResult{
|
||||
Status: backend.HealthStatusOk,
|
||||
Message: message,
|
||||
}, nil
|
||||
}
|
||||
|
||||
logger.Warn("error performing prometheus healthcheck", "err", err.Error())
|
||||
errorMessage := fmt.Sprintf("%s - %s", err.Error(), message)
|
||||
|
||||
return &backend.CheckHealthResult{
|
||||
Status: backend.HealthStatusError,
|
||||
Message: errorMessage,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
|
||||
sdkHttpClient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type healthCheckProvider[T http.RoundTripper] struct {
|
||||
httpclient.Provider
|
||||
RoundTripper *T
|
||||
}
|
||||
|
||||
type healthCheckSuccessRoundTripper struct {
|
||||
}
|
||||
type healthCheckFailRoundTripper struct {
|
||||
}
|
||||
|
||||
func (rt *healthCheckSuccessRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
Status: "200",
|
||||
StatusCode: 200,
|
||||
Header: nil,
|
||||
Body: nil,
|
||||
ContentLength: 0,
|
||||
Request: req,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rt *healthCheckFailRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
Status: "400",
|
||||
StatusCode: 400,
|
||||
Header: nil,
|
||||
Body: nil,
|
||||
ContentLength: 0,
|
||||
Request: req,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *healthCheckProvider[T]) New(opts ...sdkHttpClient.Options) (*http.Client, error) {
|
||||
client := &http.Client{}
|
||||
provider.RoundTripper = new(T)
|
||||
client.Transport = *provider.RoundTripper
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (provider *healthCheckProvider[T]) GetTransport(opts ...sdkHttpClient.Options) (http.RoundTripper, error) {
|
||||
return *new(T), nil
|
||||
}
|
||||
|
||||
func getMockProvider[T http.RoundTripper]() *healthCheckProvider[T] {
|
||||
return &healthCheckProvider[T]{
|
||||
RoundTripper: new(T),
|
||||
}
|
||||
}
|
||||
|
||||
func Test_healthcheck(t *testing.T) {
|
||||
t.Run("should do a successful health check", func(t *testing.T) {
|
||||
httpProvider := getMockProvider[*healthCheckSuccessRoundTripper]()
|
||||
s := &Service{
|
||||
im: datasource.NewInstanceManager(newInstanceSettings(httpProvider, &setting.Cfg{}, &featuremgmt.FeatureManager{}, nil)),
|
||||
}
|
||||
|
||||
req := &backend.CheckHealthRequest{
|
||||
PluginContext: getPluginContext(),
|
||||
Headers: nil,
|
||||
}
|
||||
|
||||
res, err := s.CheckHealth(context.Background(), req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, backend.HealthStatusOk, res.Status)
|
||||
})
|
||||
|
||||
t.Run("should return an error for an unsuccessful health check", func(t *testing.T) {
|
||||
httpProvider := getMockProvider[*healthCheckFailRoundTripper]()
|
||||
s := &Service{
|
||||
im: datasource.NewInstanceManager(newInstanceSettings(httpProvider, &setting.Cfg{}, &featuremgmt.FeatureManager{}, nil)),
|
||||
}
|
||||
|
||||
req := &backend.CheckHealthRequest{
|
||||
PluginContext: getPluginContext(),
|
||||
Headers: nil,
|
||||
}
|
||||
|
||||
res, err := s.CheckHealth(context.Background(), req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, backend.HealthStatusError, res.Status)
|
||||
})
|
||||
}
|
||||
|
||||
func getPluginContext() backend.PluginContext {
|
||||
return backend.PluginContext{
|
||||
OrgID: 0,
|
||||
PluginID: "prometheus",
|
||||
User: nil,
|
||||
AppInstanceSettings: nil,
|
||||
DataSourceInstanceSettings: getPromInstanceSettings(),
|
||||
}
|
||||
}
|
||||
func getPromInstanceSettings() *backend.DataSourceInstanceSettings {
|
||||
return &backend.DataSourceInstanceSettings{
|
||||
ID: 0,
|
||||
UID: "",
|
||||
Type: "prometheus",
|
||||
Name: "test-prometheus",
|
||||
URL: "http://promurl:9090",
|
||||
User: "",
|
||||
Database: "",
|
||||
BasicAuthEnabled: true,
|
||||
BasicAuthUser: "admin",
|
||||
JSONData: []byte("{}"),
|
||||
DecryptedSecureJSONData: map[string]string{},
|
||||
Updated: time.Time{},
|
||||
}
|
||||
}
|
||||
@@ -179,6 +179,13 @@ func (s *QueryData) instantQuery(ctx context.Context, c *client.Client, q *model
|
||||
}
|
||||
}
|
||||
|
||||
// This is only for health check fall back scenario
|
||||
if res.StatusCode != 200 && q.RefId == "__healthcheck__" {
|
||||
return backend.DataResponse{
|
||||
Error: fmt.Errorf(res.Status),
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := res.Body.Close()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user