mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins: Externalize Cloud Monitoring data source (#80181)
This commit is contained in:
parent
de662810cf
commit
2d432d6ff3
@ -102,6 +102,8 @@
|
||||
"public/app/plugins/datasource/grafana-testdata-datasource/**/*.{ts,tsx}",
|
||||
"public/app/plugins/datasource/azuremonitor/*.{ts,tsx}",
|
||||
"public/app/plugins/datasource/azuremonitor/**/*.{ts,tsx}",
|
||||
"public/app/plugins/datasource/cloud-monitoring/*.{ts,tsx}",
|
||||
"public/app/plugins/datasource/cloud-monitoring/**/*.{ts,tsx}",
|
||||
"public/app/plugins/datasource/parca/*.{ts,tsx}",
|
||||
"public/app/plugins/datasource/parca/**/*.{ts,tsx}",
|
||||
"public/app/plugins/datasource/tempo/*.{ts,tsx}",
|
||||
|
@ -61,6 +61,8 @@ files = [
|
||||
"**/pkg/tsdb/grafana-testdata-datasource/**/*",
|
||||
"**/pkg/tsdb/azuremonitor/*",
|
||||
"**/pkg/tsdb/azuremonitor/**/*",
|
||||
"**/pkg/tsdb/cloud-monitoring/*",
|
||||
"**/pkg/tsdb/cloud-monitoring/**/*",
|
||||
"**/pkg/tsdb/parca/*",
|
||||
"**/pkg/tsdb/parca/**/*",
|
||||
"**/pkg/tsdb/tempo/*",
|
||||
|
@ -228,6 +228,7 @@
|
||||
"@floating-ui/react": "0.26.8",
|
||||
"@glideapps/glide-data-grid": "^6.0.0",
|
||||
"@grafana-plugins/grafana-azure-monitor-datasource": "workspace:*",
|
||||
"@grafana-plugins/grafana-cloud-monitoring-datasource": "workspace:*",
|
||||
"@grafana-plugins/grafana-pyroscope-datasource": "workspace:*",
|
||||
"@grafana-plugins/grafana-testdata-datasource": "workspace:*",
|
||||
"@grafana-plugins/parca": "workspace:*",
|
||||
|
@ -4,9 +4,9 @@
|
||||
"declarationDir": "./compiled",
|
||||
"emitDeclarationOnly": true,
|
||||
"isolatedModules": true,
|
||||
"rootDirs": ["."]
|
||||
"rootDirs": ["."],
|
||||
},
|
||||
"exclude": ["dist/**/*"],
|
||||
"extends": "@grafana/tsconfig",
|
||||
"include": ["src/**/*.ts*", "../../public/app/types/*.d.ts", "../grafana-ui/src/types/*.d.ts"]
|
||||
"include": ["src/**/*.ts*", "../../public/app/types/*.d.ts", "../grafana-ui/src/types/*.d.ts"],
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "1.0.0";
|
||||
export const pluginVersion = "%VERSION%";
|
||||
|
||||
export interface CloudMonitoringQuery extends common.DataQuery {
|
||||
/**
|
||||
|
@ -5,9 +5,9 @@
|
||||
"emitDeclarationOnly": true,
|
||||
"isolatedModules": true,
|
||||
"strict": true,
|
||||
"rootDirs": ["."]
|
||||
"rootDirs": ["."],
|
||||
},
|
||||
"exclude": ["dist/**/*"],
|
||||
"extends": "@grafana/tsconfig",
|
||||
"include": ["src/**/*.ts*", "../../public/app/types/*.d.ts", "../grafana-ui/src/types/*.d.ts"]
|
||||
"include": ["src/**/*.ts*", "../../public/app/types/*.d.ts", "../grafana-ui/src/types/*.d.ts"],
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ func TestIntegrationPluginManager(t *testing.T) {
|
||||
hcp := httpclient.NewProvider()
|
||||
am := azuremonitor.ProvideService(hcp)
|
||||
cw := cloudwatch.ProvideService(cfg, hcp, features)
|
||||
cm := cloudmonitoring.ProvideService(hcp, tracer)
|
||||
cm := cloudmonitoring.ProvideService(hcp)
|
||||
es := elasticsearch.ProvideService(hcp, tracer)
|
||||
grap := graphite.ProvideService(hcp, tracer)
|
||||
idb := influxdb.ProvideService(hcp, features)
|
||||
|
@ -642,7 +642,7 @@
|
||||
},
|
||||
"build": {},
|
||||
"screenshots": null,
|
||||
"version": "1.0.0",
|
||||
"version": "",
|
||||
"updated": ""
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
)
|
||||
|
||||
@ -18,10 +19,10 @@ type annotationEvent struct {
|
||||
Text string
|
||||
}
|
||||
|
||||
func (s *Service) executeAnnotationQuery(ctx context.Context, req *backend.QueryDataRequest, dsInfo datasourceInfo, queries []cloudMonitoringQueryExecutor) (
|
||||
func (s *Service) executeAnnotationQuery(ctx context.Context, req *backend.QueryDataRequest, dsInfo datasourceInfo, queries []cloudMonitoringQueryExecutor, logger log.Logger) (
|
||||
*backend.QueryDataResponse, error) {
|
||||
resp := backend.NewQueryDataResponse()
|
||||
queryRes, dr, _, err := queries[0].run(ctx, req, s, dsInfo, s.tracer)
|
||||
queryRes, dr, _, err := queries[0].run(ctx, req, s, dsInfo, logger)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
@ -17,20 +17,15 @@ import (
|
||||
|
||||
"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"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloud-monitoring/kinds/dataquery"
|
||||
)
|
||||
|
||||
var (
|
||||
slog = log.New("tsdb.cloudMonitoring")
|
||||
)
|
||||
|
||||
var (
|
||||
legendKeyFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`)
|
||||
metricNameFormat = regexp.MustCompile(`([\w\d_]+)\.(googleapis\.com|io)/(.+)`)
|
||||
@ -65,11 +60,11 @@ const (
|
||||
perSeriesAlignerDefault = "ALIGN_MEAN"
|
||||
)
|
||||
|
||||
func ProvideService(httpClientProvider httpclient.Provider, tracer tracing.Tracer) *Service {
|
||||
func ProvideService(httpClientProvider *httpclient.Provider) *Service {
|
||||
s := &Service{
|
||||
tracer: tracer,
|
||||
httpClientProvider: httpClientProvider,
|
||||
im: datasource.NewInstanceManager(newInstanceSettings(httpClientProvider)),
|
||||
httpClientProvider: *httpClientProvider,
|
||||
im: datasource.NewInstanceManager(newInstanceSettings(*httpClientProvider)),
|
||||
logger: backend.NewLoggerWith("logger", "tsdb.cloudmonitoring"),
|
||||
|
||||
gceDefaultProjectGetter: utils.GCEDefaultProject,
|
||||
}
|
||||
@ -109,7 +104,7 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
|
||||
}
|
||||
defer func() {
|
||||
if err := res.Body.Close(); err != nil {
|
||||
slog.Warn("Failed to close response body", "err", err)
|
||||
s.logger.Warn("Failed to close response body", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -128,7 +123,7 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
|
||||
type Service struct {
|
||||
httpClientProvider httpclient.Provider
|
||||
im instancemgmt.InstanceManager
|
||||
tracer tracing.Tracer
|
||||
logger log.Logger
|
||||
|
||||
resourceHandler backend.CallResourceHandler
|
||||
|
||||
@ -194,7 +189,7 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst
|
||||
}
|
||||
|
||||
for name, info := range routes {
|
||||
client, err := newHTTPClient(dsInfo, opts, httpClientProvider, name)
|
||||
client, err := newHTTPClient(dsInfo, opts, &httpClientProvider, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -332,7 +327,7 @@ func migrateRequest(req *backend.QueryDataRequest) error {
|
||||
// QueryData takes in the frontend queries, parses them into the CloudMonitoring query format
|
||||
// executes the queries against the CloudMonitoring API and parses the response into data frames
|
||||
func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
logger := slog.FromContext(ctx)
|
||||
logger := s.logger.FromContext(ctx)
|
||||
if len(req.Queries) == 0 {
|
||||
return nil, fmt.Errorf("query contains no queries")
|
||||
}
|
||||
@ -354,21 +349,21 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
|
||||
|
||||
switch req.Queries[0].QueryType {
|
||||
case string(dataquery.QueryTypeAnnotation):
|
||||
return s.executeAnnotationQuery(ctx, req, *dsInfo, queries)
|
||||
return s.executeAnnotationQuery(ctx, req, *dsInfo, queries, logger)
|
||||
default:
|
||||
return s.executeTimeSeriesQuery(ctx, req, *dsInfo, queries)
|
||||
return s.executeTimeSeriesQuery(ctx, req, *dsInfo, queries, logger)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) executeTimeSeriesQuery(ctx context.Context, req *backend.QueryDataRequest, dsInfo datasourceInfo, queries []cloudMonitoringQueryExecutor) (
|
||||
func (s *Service) executeTimeSeriesQuery(ctx context.Context, req *backend.QueryDataRequest, dsInfo datasourceInfo, queries []cloudMonitoringQueryExecutor, logger log.Logger) (
|
||||
*backend.QueryDataResponse, error) {
|
||||
resp := backend.NewQueryDataResponse()
|
||||
for _, queryExecutor := range queries {
|
||||
queryRes, dr, executedQueryString, err := queryExecutor.run(ctx, req, s, dsInfo, s.tracer)
|
||||
queryRes, dr, executedQueryString, err := queryExecutor.run(ctx, req, s, dsInfo, logger)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
err = queryExecutor.parseResponse(queryRes, dr, executedQueryString)
|
||||
err = queryExecutor.parseResponse(queryRes, dr, executedQueryString, logger)
|
||||
if err != nil {
|
||||
queryRes.Error = err
|
||||
}
|
||||
@ -405,7 +400,6 @@ func (s *Service) buildQueryExecutors(logger log.Logger, req *backend.QueryDataR
|
||||
case string(dataquery.QueryTypeTimeSeriesList), string(dataquery.QueryTypeAnnotation):
|
||||
cmtsf := &cloudMonitoringTimeSeriesList{
|
||||
refID: query.RefID,
|
||||
logger: logger,
|
||||
aliasBy: q.AliasBy,
|
||||
}
|
||||
if q.TimeSeriesList.View == nil || *q.TimeSeriesList.View == "" {
|
||||
@ -427,7 +421,6 @@ func (s *Service) buildQueryExecutors(logger log.Logger, req *backend.QueryDataR
|
||||
case string(dataquery.QueryTypeSlo):
|
||||
cmslo := &cloudMonitoringSLO{
|
||||
refID: query.RefID,
|
||||
logger: logger,
|
||||
aliasBy: q.AliasBy,
|
||||
parameters: q.SloQuery,
|
||||
}
|
||||
@ -436,10 +429,10 @@ func (s *Service) buildQueryExecutors(logger log.Logger, req *backend.QueryDataR
|
||||
case string(dataquery.QueryTypePromQL):
|
||||
cmp := &cloudMonitoringProm{
|
||||
refID: query.RefID,
|
||||
logger: logger,
|
||||
aliasBy: q.AliasBy,
|
||||
parameters: q.PromQLQuery,
|
||||
timeRange: req.Queries[0].TimeRange,
|
||||
logger: logger,
|
||||
}
|
||||
queryInterface = cmp
|
||||
default:
|
||||
@ -595,7 +588,7 @@ func (s *Service) getDefaultProject(ctx context.Context, dsInfo datasourceInfo)
|
||||
return dsInfo.defaultProject, nil
|
||||
}
|
||||
|
||||
func unmarshalResponse(logger log.Logger, res *http.Response) (cloudMonitoringResponse, error) {
|
||||
func unmarshalResponse(res *http.Response, logger log.Logger) (cloudMonitoringResponse, error) {
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return cloudMonitoringResponse{}, err
|
||||
@ -646,7 +639,7 @@ func addConfigData(frames data.Frames, dl string, unit string, period *string) d
|
||||
if period != nil && *period != "" {
|
||||
err := addInterval(*period, frames[i].Fields[0])
|
||||
if err != nil {
|
||||
slog.Error("Failed to add interval", "error", err)
|
||||
backend.Logger.Error("Failed to add interval", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,8 @@ import (
|
||||
|
||||
"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/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloud-monitoring/kinds/dataquery"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -23,7 +23,7 @@ import (
|
||||
func TestNewInstanceSettings(t *testing.T) {
|
||||
t.Run("should create a new instance with empty settings", func(t *testing.T) {
|
||||
cli := httpclient.NewProvider()
|
||||
f := newInstanceSettings(cli)
|
||||
f := newInstanceSettings(*cli)
|
||||
dsInfo, err := f(context.Background(), backend.DataSourceInstanceSettings{
|
||||
JSONData: json.RawMessage(`{}`),
|
||||
})
|
||||
@ -34,7 +34,7 @@ func TestNewInstanceSettings(t *testing.T) {
|
||||
|
||||
t.Run("should create a new instance parsing settings", func(t *testing.T) {
|
||||
cli := httpclient.NewProvider()
|
||||
f := newInstanceSettings(cli)
|
||||
f := newInstanceSettings(*cli)
|
||||
dsInfo, err := f(context.Background(), backend.DataSourceInstanceSettings{
|
||||
JSONData: json.RawMessage(`{"authenticationType": "test", "defaultProject": "test", "clientEmail": "test", "tokenUri": "test"}`),
|
||||
})
|
||||
@ -53,7 +53,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
|
||||
t.Run("parses a time series list query", func(t *testing.T) {
|
||||
req := baseTimeSeriesList()
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
|
||||
@ -71,7 +71,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
|
||||
t.Run("parses a time series query", func(t *testing.T) {
|
||||
req := baseTimeSeriesQuery()
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringQueryFromInterface(t, qes)
|
||||
|
||||
@ -95,7 +95,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
"aliasBy": "testalias"
|
||||
}`)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
|
||||
@ -114,7 +114,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
req := deprecatedReq()
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
|
||||
@ -157,7 +157,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(query)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, query)
|
||||
qes, err := service.buildQueryExecutors(service.logger, query)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
assert.Equal(t, 1, len(queries))
|
||||
@ -191,7 +191,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
assert.Equal(t, `+1000s`, queries[0].params["aggregation.alignmentPeriod"][0])
|
||||
@ -221,7 +221,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
assert.Equal(t, `+60s`, queries[0].params["aggregation.alignmentPeriod"][0])
|
||||
@ -257,7 +257,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
assert.Equal(t, `+60s`, queries[0].params["aggregation.alignmentPeriod"][0])
|
||||
@ -274,7 +274,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
assert.Equal(t, `+60s`, queries[0].params["aggregation.alignmentPeriod"][0])
|
||||
@ -291,7 +291,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
assert.Equal(t, `+300s`, queries[0].params["aggregation.alignmentPeriod"][0])
|
||||
@ -308,7 +308,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
assert.Equal(t, `+3600s`, queries[0].params["aggregation.alignmentPeriod"][0])
|
||||
@ -329,7 +329,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
assert.Equal(t, `+60s`, queries[0].params["aggregation.alignmentPeriod"][0])
|
||||
@ -361,7 +361,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
assert.Equal(t, `+60s`, queries[0].params["aggregation.alignmentPeriod"][0])
|
||||
@ -393,7 +393,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
assert.Equal(t, `+300s`, queries[0].params["aggregation.alignmentPeriod"][0])
|
||||
@ -425,7 +425,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
assert.Equal(t, `+3600s`, queries[0].params["aggregation.alignmentPeriod"][0])
|
||||
@ -457,7 +457,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
assert.Equal(t, `+600s`, queries[0].params["aggregation.alignmentPeriod"][0])
|
||||
@ -489,7 +489,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
|
||||
@ -535,7 +535,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
|
||||
@ -598,7 +598,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("and query type is metrics", func(t *testing.T) {
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
|
||||
@ -647,7 +647,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err = migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err = service.buildQueryExecutors(slog, req)
|
||||
qes, err = service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
tqueries := getCloudMonitoringQueryFromInterface(t, qes)
|
||||
@ -675,7 +675,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringSLOFromInterface(t, qes)
|
||||
|
||||
@ -705,7 +705,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err = migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err = service.buildQueryExecutors(slog, req)
|
||||
qes, err = service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
qqueries := getCloudMonitoringSLOFromInterface(t, qes)
|
||||
assert.Equal(t, "ALIGN_NEXT_OLDER", qqueries[0].params["aggregation.perSeriesAligner"][0])
|
||||
@ -730,7 +730,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err = migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err = service.buildQueryExecutors(slog, req)
|
||||
qes, err = service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
qqqueries := getCloudMonitoringSLOFromInterface(t, qes)
|
||||
assert.Equal(t, `aggregation.alignmentPeriod=%2B60s&aggregation.perSeriesAligner=ALIGN_NEXT_OLDER&filter=select_slo_burn_rate%28%22projects%2Ftest-proj%2Fservices%2Ftest-service%2FserviceLevelObjectives%2Ftest-slo%22%2C+%221h%22%29&interval.endTime=2018-03-15T13%3A34%3A00Z&interval.startTime=2018-03-15T13%3A00%3A00Z`, qqqueries[0].params.Encode())
|
||||
@ -812,7 +812,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
|
||||
@ -842,7 +842,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
|
||||
@ -872,7 +872,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
|
||||
@ -900,7 +900,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
|
||||
@ -930,7 +930,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
|
||||
@ -958,7 +958,7 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
err := migrateRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
qes, err := service.buildQueryExecutors(slog, req)
|
||||
qes, err := service.buildQueryExecutors(service.logger, req)
|
||||
require.NoError(t, err)
|
||||
queries := getCloudMonitoringListFromInterface(t, qes)
|
||||
|
||||
|
1173
pkg/tsdb/cloud-monitoring/converter/converter.go
Normal file
1173
pkg/tsdb/cloud-monitoring/converter/converter.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana-google-sdk-go/pkg/tokenprovider"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
infrahttp "github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -59,7 +58,7 @@ func getMiddleware(model *datasourceInfo, routePath string) (httpclient.Middlewa
|
||||
return tokenprovider.AuthMiddleware(provider), nil
|
||||
}
|
||||
|
||||
func newHTTPClient(model *datasourceInfo, opts httpclient.Options, clientProvider infrahttp.Provider, route string) (*http.Client, error) {
|
||||
func newHTTPClient(model *datasourceInfo, opts httpclient.Options, clientProvider *httpclient.Provider, route string) (*http.Client, error) {
|
||||
m, err := getMiddleware(model, route)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
66
pkg/tsdb/cloud-monitoring/jsonitere/jsonitere.go
Normal file
66
pkg/tsdb/cloud-monitoring/jsonitere/jsonitere.go
Normal file
@ -0,0 +1,66 @@
|
||||
// Package jsonitere wraps json-iterator/go's Iterator methods with error returns
|
||||
// so linting can catch unchecked errors.
|
||||
// The underlying iterator's Error property is returned and not reset.
|
||||
// See json-iterator/go for method documentation and additional methods that
|
||||
// can be added to this library.
|
||||
package jsonitere
|
||||
|
||||
import (
|
||||
j "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
type Iterator struct {
|
||||
// named property instead of embedded so there is no
|
||||
// confusion about which method or property is called
|
||||
i *j.Iterator
|
||||
}
|
||||
|
||||
func NewIterator(i *j.Iterator) *Iterator {
|
||||
return &Iterator{i}
|
||||
}
|
||||
|
||||
func (iter *Iterator) Read() (any, error) {
|
||||
return iter.i.Read(), iter.i.Error
|
||||
}
|
||||
|
||||
func (iter *Iterator) ReadAny() (j.Any, error) {
|
||||
return iter.i.ReadAny(), iter.i.Error
|
||||
}
|
||||
|
||||
func (iter *Iterator) ReadArray() (bool, error) {
|
||||
return iter.i.ReadArray(), iter.i.Error
|
||||
}
|
||||
|
||||
func (iter *Iterator) ReadObject() (string, error) {
|
||||
return iter.i.ReadObject(), iter.i.Error
|
||||
}
|
||||
|
||||
func (iter *Iterator) ReadString() (string, error) {
|
||||
return iter.i.ReadString(), iter.i.Error
|
||||
}
|
||||
|
||||
func (iter *Iterator) WhatIsNext() (j.ValueType, error) {
|
||||
return iter.i.WhatIsNext(), iter.i.Error
|
||||
}
|
||||
|
||||
func (iter *Iterator) Skip() error {
|
||||
iter.i.Skip()
|
||||
return iter.i.Error
|
||||
}
|
||||
|
||||
func (iter *Iterator) SkipAndReturnBytes() []byte {
|
||||
return iter.i.SkipAndReturnBytes()
|
||||
}
|
||||
|
||||
func (iter *Iterator) ReadVal(obj any) error {
|
||||
iter.i.ReadVal(obj)
|
||||
return iter.i.Error
|
||||
}
|
||||
|
||||
func (iter *Iterator) ReadFloat64() (float64, error) {
|
||||
return iter.i.ReadFloat64(), iter.i.Error
|
||||
}
|
||||
|
||||
func (iter *Iterator) ReadInt8() (int8, error) {
|
||||
return iter.i.ReadInt8(), iter.i.Error
|
||||
}
|
@ -11,28 +11,27 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloud-monitoring/converter"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/util/converter"
|
||||
)
|
||||
|
||||
func (promQLQ *cloudMonitoringProm) run(ctx context.Context, req *backend.QueryDataRequest,
|
||||
s *Service, dsInfo datasourceInfo, tracer tracing.Tracer) (*backend.DataResponse, any, string, error) {
|
||||
s *Service, dsInfo datasourceInfo, logger log.Logger) (*backend.DataResponse, any, string, error) {
|
||||
dr := &backend.DataResponse{}
|
||||
projectName, err := s.ensureProject(ctx, dsInfo, promQLQ.parameters.ProjectName)
|
||||
if err != nil {
|
||||
dr.Error = err
|
||||
return dr, promResponse{}, "", nil
|
||||
}
|
||||
r, err := createRequest(ctx, promQLQ.logger, &dsInfo, path.Join("/v1/projects", projectName, "location/global/prometheus/api/v1/query_range"), nil)
|
||||
r, err := createRequest(ctx, &dsInfo, path.Join("/v1/projects", projectName, "location/global/prometheus/api/v1/query_range"), nil)
|
||||
if err != nil {
|
||||
dr.Error = err
|
||||
return dr, promResponse{}, "", nil
|
||||
}
|
||||
|
||||
span := traceReq(ctx, tracer, req, dsInfo, r, "")
|
||||
span := traceReq(ctx, req, dsInfo, r, "")
|
||||
defer span.End()
|
||||
|
||||
requestBody := map[string]any{
|
||||
@ -45,7 +44,7 @@ func (promQLQ *cloudMonitoringProm) run(ctx context.Context, req *backend.QueryD
|
||||
res, err := doRequestProm(r, dsInfo, requestBody)
|
||||
defer func() {
|
||||
if err := res.Body.Close(); err != nil {
|
||||
promQLQ.logger.Error("Failed to close response body", "err", err)
|
||||
s.logger.Error("Failed to close response body", "err", err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
@ -83,7 +82,7 @@ func parseProm(res *http.Response) backend.DataResponse {
|
||||
// We are not parsing the response in this function. ReadPrometheusStyleResult needs an open reader and we cannot
|
||||
// pass an open reader to this function because lint complains as it is unsafe
|
||||
func (promQLQ *cloudMonitoringProm) parseResponse(queryRes *backend.DataResponse,
|
||||
response any, executedQueryString string) error {
|
||||
response any, executedQueryString string, logger log.Logger) error {
|
||||
r := response.(backend.DataResponse)
|
||||
// Add frame to attach metadata
|
||||
if len(r.Frames) == 0 {
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPromqlQuery(t *testing.T) {
|
||||
service := &Service{}
|
||||
t.Run("parseResponse is returned", func(t *testing.T) {
|
||||
fileData, err := os.ReadFile("./test-data/11-prom-response.json")
|
||||
reader := strings.NewReader(string(fileData))
|
||||
@ -24,7 +25,7 @@ func TestPromqlQuery(t *testing.T) {
|
||||
dataRes := &backend.DataResponse{}
|
||||
query := &cloudMonitoringProm{}
|
||||
parsedProm := parseProm(&res)
|
||||
err = query.parseResponse(dataRes, parsedProm, "")
|
||||
err = query.parseResponse(dataRes, parsedProm, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frame := dataRes.Frames[0]
|
||||
experimental.CheckGoldenJSONFrame(t, "test-data", "parse-response-is-returned", frame, false)
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
|
||||
)
|
||||
|
||||
@ -199,14 +200,14 @@ func decode(encoding string, original io.ReadCloser) ([]byte, error) {
|
||||
}
|
||||
defer func() {
|
||||
if err := reader.(io.ReadCloser).Close(); err != nil {
|
||||
slog.Warn("Failed to close reader body", "err", err)
|
||||
backend.Logger.Warn("Failed to close reader body", "err", err)
|
||||
}
|
||||
}()
|
||||
case "deflate":
|
||||
reader = flate.NewReader(original)
|
||||
defer func() {
|
||||
if err := reader.(io.ReadCloser).Close(); err != nil {
|
||||
slog.Warn("Failed to close reader body", "err", err)
|
||||
backend.Logger.Warn("Failed to close reader body", "err", err)
|
||||
}
|
||||
}()
|
||||
case "br":
|
||||
@ -246,7 +247,7 @@ func encode(encoding string, body []byte) ([]byte, error) {
|
||||
_, err = writer.Write(body)
|
||||
if writeCloser, ok := writer.(io.WriteCloser); ok {
|
||||
if err := writeCloser.Close(); err != nil {
|
||||
slog.Warn("Failed to close writer body", "err", err)
|
||||
backend.Logger.Warn("Failed to close writer body", "err", err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
@ -284,7 +285,7 @@ func doRequest(req *http.Request, cli *http.Client, responseFn processResponse)
|
||||
}
|
||||
defer func() {
|
||||
if err := res.Body.Close(); err != nil {
|
||||
slog.Warn("Failed to close response body", "err", err)
|
||||
backend.Logger.Warn("Failed to close response body", "err", err)
|
||||
}
|
||||
}()
|
||||
encoding := res.Header.Get("Content-Encoding")
|
||||
@ -346,7 +347,7 @@ func buildResponse(responses []json.RawMessage, encoding string) ([]byte, error)
|
||||
}
|
||||
|
||||
func (s *Service) setRequestVariables(req *http.Request, subDataSource string) (*http.Client, int, error) {
|
||||
slog.Debug("Received resource call", "url", req.URL.String(), "method", req.Method)
|
||||
s.logger.Debug("Received resource call", "url", req.URL.String(), "method", req.Method)
|
||||
|
||||
newPath, err := getTarget(req.URL.Path)
|
||||
if err != nil {
|
||||
@ -386,7 +387,7 @@ func writeResponseBytes(rw http.ResponseWriter, code int, msg []byte) {
|
||||
rw.WriteHeader(code)
|
||||
_, err := rw.Write(msg)
|
||||
if err != nil {
|
||||
slog.Error("Unable to write HTTP response", "error", err)
|
||||
backend.Logger.Error("Unable to write HTTP response", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -115,6 +116,7 @@ func Test_setRequestVariables(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
logger: log.DefaultLogger,
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodGet, "http://foo/cloudmonitoring/v3/projects/bar/metricDescriptors", nil)
|
||||
if err != nil {
|
||||
|
@ -7,18 +7,17 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
)
|
||||
|
||||
func (sloQ *cloudMonitoringSLO) run(ctx context.Context, req *backend.QueryDataRequest,
|
||||
s *Service, dsInfo datasourceInfo, tracer tracing.Tracer) (*backend.DataResponse, any, string, error) {
|
||||
return runTimeSeriesRequest(ctx, sloQ.logger, req, s, dsInfo, tracer, sloQ.parameters.ProjectName, sloQ.params, nil)
|
||||
s *Service, dsInfo datasourceInfo, logger log.Logger) (*backend.DataResponse, any, string, error) {
|
||||
return runTimeSeriesRequest(ctx, req, s, dsInfo, sloQ.parameters.ProjectName, sloQ.params, nil, logger)
|
||||
}
|
||||
|
||||
func (sloQ *cloudMonitoringSLO) parseResponse(queryRes *backend.DataResponse,
|
||||
response any, executedQueryString string) error {
|
||||
return parseTimeSeriesResponse(queryRes, response.(cloudMonitoringResponse), executedQueryString, sloQ, sloQ.params, []string{})
|
||||
response any, executedQueryString string, logger log.Logger) error {
|
||||
return parseTimeSeriesResponse(queryRes, response.(cloudMonitoringResponse), executedQueryString, sloQ, sloQ.params, []string{}, logger)
|
||||
}
|
||||
|
||||
func (sloQ *cloudMonitoringSLO) buildDeepLink() string {
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func SLOQuery(t *testing.T) {
|
||||
service := &Service{}
|
||||
t.Run("when data from query returns slo and alias by is defined", func(t *testing.T) {
|
||||
data, err := loadTestFile("./test-data/6-series-response-slo.json")
|
||||
require.NoError(t, err)
|
||||
@ -29,7 +30,7 @@ func SLOQuery(t *testing.T) {
|
||||
},
|
||||
aliasBy: "{{project}} - {{service}} - {{slo}} - {{selector}}",
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
require.NoError(t, err)
|
||||
@ -53,7 +54,7 @@ func SLOQuery(t *testing.T) {
|
||||
SloId: "test-slo",
|
||||
},
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
require.NoError(t, err)
|
||||
@ -68,7 +69,7 @@ func SLOQuery(t *testing.T) {
|
||||
|
||||
res := &backend.DataResponse{}
|
||||
query := &cloudMonitoringSLO{params: url.Values{}, parameters: &dataquery.SLOQuery{SloId: "yes"}}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
assert.Equal(t, len(frames[0].Fields[1].Config.Links), 0)
|
||||
|
258
pkg/tsdb/cloud-monitoring/time/interval.go
Normal file
258
pkg/tsdb/cloud-monitoring/time/interval.go
Normal file
@ -0,0 +1,258 @@
|
||||
// Copied from https://github.com/grafana/grafana/blob/main/pkg/tsdb/intervalv2/intervalv2.go
|
||||
// We're copying this to not block ourselves from decoupling until the conversation here is resolved
|
||||
// https://raintank-corp.slack.com/archives/C05QFJUHUQ6/p1700064431005089
|
||||
package time
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultRes int64 = 1500
|
||||
defaultMinInterval = time.Millisecond * 1
|
||||
year = time.Hour * 24 * 365
|
||||
day = time.Hour * 24
|
||||
)
|
||||
|
||||
type Interval struct {
|
||||
Text string
|
||||
Value time.Duration
|
||||
}
|
||||
|
||||
type intervalCalculator struct {
|
||||
minInterval time.Duration
|
||||
}
|
||||
|
||||
type Calculator interface {
|
||||
Calculate(timerange backend.TimeRange, minInterval time.Duration, maxDataPoints int64) Interval
|
||||
CalculateSafeInterval(timerange backend.TimeRange, resolution int64) Interval
|
||||
}
|
||||
|
||||
type CalculatorOptions struct {
|
||||
MinInterval time.Duration
|
||||
}
|
||||
|
||||
func NewCalculator(opts ...CalculatorOptions) *intervalCalculator {
|
||||
calc := &intervalCalculator{}
|
||||
|
||||
for _, o := range opts {
|
||||
if o.MinInterval == 0 {
|
||||
calc.minInterval = defaultMinInterval
|
||||
} else {
|
||||
calc.minInterval = o.MinInterval
|
||||
}
|
||||
}
|
||||
|
||||
return calc
|
||||
}
|
||||
|
||||
func (i *Interval) Milliseconds() int64 {
|
||||
return i.Value.Nanoseconds() / int64(time.Millisecond)
|
||||
}
|
||||
|
||||
func (ic *intervalCalculator) Calculate(timerange backend.TimeRange, minInterval time.Duration, maxDataPoints int64) Interval {
|
||||
to := timerange.To.UnixNano()
|
||||
from := timerange.From.UnixNano()
|
||||
resolution := maxDataPoints
|
||||
if resolution == 0 {
|
||||
resolution = DefaultRes
|
||||
}
|
||||
|
||||
calculatedInterval := time.Duration((to - from) / resolution)
|
||||
|
||||
if calculatedInterval < minInterval {
|
||||
return Interval{Text: FormatDuration(minInterval), Value: minInterval}
|
||||
}
|
||||
|
||||
rounded := roundInterval(calculatedInterval)
|
||||
|
||||
return Interval{Text: FormatDuration(rounded), Value: rounded}
|
||||
}
|
||||
|
||||
func (ic *intervalCalculator) CalculateSafeInterval(timerange backend.TimeRange, safeRes int64) Interval {
|
||||
to := timerange.To.UnixNano()
|
||||
from := timerange.From.UnixNano()
|
||||
safeInterval := time.Duration((to - from) / safeRes)
|
||||
|
||||
rounded := roundInterval(safeInterval)
|
||||
return Interval{Text: FormatDuration(rounded), Value: rounded}
|
||||
}
|
||||
|
||||
// GetIntervalFrom returns the minimum interval.
|
||||
// dsInterval is the string representation of data source min interval, if configured.
|
||||
// queryInterval is the string representation of query interval (min interval), e.g. "10ms" or "10s".
|
||||
// queryIntervalMS is a pre-calculated numeric representation of the query interval in milliseconds.
|
||||
func GetIntervalFrom(dsInterval, queryInterval string, queryIntervalMS int64, defaultInterval time.Duration) (time.Duration, error) {
|
||||
// Apparently we are setting default value of queryInterval to 0s now
|
||||
interval := queryInterval
|
||||
if interval == "0s" {
|
||||
interval = ""
|
||||
}
|
||||
if interval == "" {
|
||||
if queryIntervalMS != 0 {
|
||||
return time.Duration(queryIntervalMS) * time.Millisecond, nil
|
||||
}
|
||||
}
|
||||
if interval == "" && dsInterval != "" {
|
||||
interval = dsInterval
|
||||
}
|
||||
if interval == "" {
|
||||
return defaultInterval, nil
|
||||
}
|
||||
|
||||
parsedInterval, err := ParseIntervalStringToTimeDuration(interval)
|
||||
if err != nil {
|
||||
return time.Duration(0), err
|
||||
}
|
||||
|
||||
return parsedInterval, nil
|
||||
}
|
||||
|
||||
func ParseIntervalStringToTimeDuration(interval string) (time.Duration, error) {
|
||||
formattedInterval := strings.Replace(strings.Replace(interval, "<", "", 1), ">", "", 1)
|
||||
isPureNum, err := regexp.MatchString(`^\d+$`, formattedInterval)
|
||||
if err != nil {
|
||||
return time.Duration(0), err
|
||||
}
|
||||
if isPureNum {
|
||||
formattedInterval += "s"
|
||||
}
|
||||
parsedInterval, err := gtime.ParseDuration(formattedInterval)
|
||||
if err != nil {
|
||||
return time.Duration(0), err
|
||||
}
|
||||
return parsedInterval, nil
|
||||
}
|
||||
|
||||
// FormatDuration converts a duration into the kbn format e.g. 1m 2h or 3d
|
||||
func FormatDuration(inter time.Duration) string {
|
||||
if inter >= year {
|
||||
return fmt.Sprintf("%dy", inter/year)
|
||||
}
|
||||
|
||||
if inter >= day {
|
||||
return fmt.Sprintf("%dd", inter/day)
|
||||
}
|
||||
|
||||
if inter >= time.Hour {
|
||||
return fmt.Sprintf("%dh", inter/time.Hour)
|
||||
}
|
||||
|
||||
if inter >= time.Minute {
|
||||
return fmt.Sprintf("%dm", inter/time.Minute)
|
||||
}
|
||||
|
||||
if inter >= time.Second {
|
||||
return fmt.Sprintf("%ds", inter/time.Second)
|
||||
}
|
||||
|
||||
if inter >= time.Millisecond {
|
||||
return fmt.Sprintf("%dms", inter/time.Millisecond)
|
||||
}
|
||||
|
||||
return "1ms"
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func roundInterval(interval time.Duration) time.Duration {
|
||||
switch {
|
||||
// 0.01s
|
||||
case interval <= 10*time.Millisecond:
|
||||
return time.Millisecond * 1 // 0.001s
|
||||
// 0.015s
|
||||
case interval <= 15*time.Millisecond:
|
||||
return time.Millisecond * 10 // 0.01s
|
||||
// 0.035s
|
||||
case interval <= 35*time.Millisecond:
|
||||
return time.Millisecond * 20 // 0.02s
|
||||
// 0.075s
|
||||
case interval <= 75*time.Millisecond:
|
||||
return time.Millisecond * 50 // 0.05s
|
||||
// 0.15s
|
||||
case interval <= 150*time.Millisecond:
|
||||
return time.Millisecond * 100 // 0.1s
|
||||
// 0.35s
|
||||
case interval <= 350*time.Millisecond:
|
||||
return time.Millisecond * 200 // 0.2s
|
||||
// 0.75s
|
||||
case interval <= 750*time.Millisecond:
|
||||
return time.Millisecond * 500 // 0.5s
|
||||
// 1.5s
|
||||
case interval <= 1500*time.Millisecond:
|
||||
return time.Millisecond * 1000 // 1s
|
||||
// 3.5s
|
||||
case interval <= 3500*time.Millisecond:
|
||||
return time.Millisecond * 2000 // 2s
|
||||
// 7.5s
|
||||
case interval <= 7500*time.Millisecond:
|
||||
return time.Millisecond * 5000 // 5s
|
||||
// 12.5s
|
||||
case interval <= 12500*time.Millisecond:
|
||||
return time.Millisecond * 10000 // 10s
|
||||
// 17.5s
|
||||
case interval <= 17500*time.Millisecond:
|
||||
return time.Millisecond * 15000 // 15s
|
||||
// 25s
|
||||
case interval <= 25000*time.Millisecond:
|
||||
return time.Millisecond * 20000 // 20s
|
||||
// 45s
|
||||
case interval <= 45000*time.Millisecond:
|
||||
return time.Millisecond * 30000 // 30s
|
||||
// 1.5m
|
||||
case interval <= 90000*time.Millisecond:
|
||||
return time.Millisecond * 60000 // 1m
|
||||
// 3.5m
|
||||
case interval <= 210000*time.Millisecond:
|
||||
return time.Millisecond * 120000 // 2m
|
||||
// 7.5m
|
||||
case interval <= 450000*time.Millisecond:
|
||||
return time.Millisecond * 300000 // 5m
|
||||
// 12.5m
|
||||
case interval <= 750000*time.Millisecond:
|
||||
return time.Millisecond * 600000 // 10m
|
||||
// 17.5m
|
||||
case interval <= 1050000*time.Millisecond:
|
||||
return time.Millisecond * 900000 // 15m
|
||||
// 25m
|
||||
case interval <= 1500000*time.Millisecond:
|
||||
return time.Millisecond * 1200000 // 20m
|
||||
// 45m
|
||||
case interval <= 2700000*time.Millisecond:
|
||||
return time.Millisecond * 1800000 // 30m
|
||||
// 1.5h
|
||||
case interval <= 5400000*time.Millisecond:
|
||||
return time.Millisecond * 3600000 // 1h
|
||||
// 2.5h
|
||||
case interval <= 9000000*time.Millisecond:
|
||||
return time.Millisecond * 7200000 // 2h
|
||||
// 4.5h
|
||||
case interval <= 16200000*time.Millisecond:
|
||||
return time.Millisecond * 10800000 // 3h
|
||||
// 9h
|
||||
case interval <= 32400000*time.Millisecond:
|
||||
return time.Millisecond * 21600000 // 6h
|
||||
// 24h
|
||||
case interval <= 86400000*time.Millisecond:
|
||||
return time.Millisecond * 43200000 // 12h
|
||||
// 48h
|
||||
case interval <= 172800000*time.Millisecond:
|
||||
return time.Millisecond * 86400000 // 24h
|
||||
// 1w
|
||||
case interval <= 604800000*time.Millisecond:
|
||||
return time.Millisecond * 86400000 // 24h
|
||||
// 3w
|
||||
case interval <= 1814400000*time.Millisecond:
|
||||
return time.Millisecond * 604800000 // 1w
|
||||
// 2y
|
||||
case interval < 3628800000*time.Millisecond:
|
||||
return time.Millisecond * 2592000000 // 30d
|
||||
default:
|
||||
return time.Millisecond * 31536000000 // 1y
|
||||
}
|
||||
}
|
@ -8,20 +8,20 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/huandu/xstrings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloud-monitoring/kinds/dataquery"
|
||||
)
|
||||
|
||||
func (timeSeriesFilter *cloudMonitoringTimeSeriesList) run(ctx context.Context, req *backend.QueryDataRequest,
|
||||
s *Service, dsInfo datasourceInfo, tracer tracing.Tracer) (*backend.DataResponse, any, string, error) {
|
||||
return runTimeSeriesRequest(ctx, timeSeriesFilter.logger, req, s, dsInfo, tracer, timeSeriesFilter.parameters.ProjectName, timeSeriesFilter.params, nil)
|
||||
s *Service, dsInfo datasourceInfo, logger log.Logger) (*backend.DataResponse, any, string, error) {
|
||||
return runTimeSeriesRequest(ctx, req, s, dsInfo, timeSeriesFilter.parameters.ProjectName, timeSeriesFilter.params, nil, logger)
|
||||
}
|
||||
|
||||
func parseTimeSeriesResponse(queryRes *backend.DataResponse,
|
||||
response cloudMonitoringResponse, executedQueryString string, query cloudMonitoringQueryExecutor, params url.Values, groupBys []string) error {
|
||||
response cloudMonitoringResponse, executedQueryString string, query cloudMonitoringQueryExecutor, params url.Values, groupBys []string, logger log.Logger) error {
|
||||
frames := data.Frames{}
|
||||
|
||||
for _, series := range response.TimeSeries {
|
||||
@ -56,8 +56,8 @@ func parseTimeSeriesResponse(queryRes *backend.DataResponse,
|
||||
}
|
||||
|
||||
func (timeSeriesFilter *cloudMonitoringTimeSeriesList) parseResponse(queryRes *backend.DataResponse,
|
||||
response any, executedQueryString string) error {
|
||||
return parseTimeSeriesResponse(queryRes, response.(cloudMonitoringResponse), executedQueryString, timeSeriesFilter, timeSeriesFilter.params, timeSeriesFilter.parameters.GroupBys)
|
||||
response any, executedQueryString string, logger log.Logger) error {
|
||||
return parseTimeSeriesResponse(queryRes, response.(cloudMonitoringResponse), executedQueryString, timeSeriesFilter, timeSeriesFilter.params, timeSeriesFilter.parameters.GroupBys, logger)
|
||||
}
|
||||
|
||||
func (timeSeriesFilter *cloudMonitoringTimeSeriesList) buildDeepLink() string {
|
||||
@ -89,7 +89,7 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesList) buildDeepLink() string {
|
||||
timeSeriesFilter.params.Get("interval.endTime"),
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error(
|
||||
backend.Logger.Error(
|
||||
"Failed to generate deep link: unable to parse metrics explorer URL",
|
||||
"ProjectName", timeSeriesFilter.parameters.ProjectName,
|
||||
"error", err,
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
)
|
||||
|
||||
func TestTimeSeriesFilter(t *testing.T) {
|
||||
service := &Service{}
|
||||
t.Run("parses params", func(t *testing.T) {
|
||||
query := &cloudMonitoringTimeSeriesList{parameters: &dataquery.TimeSeriesList{}}
|
||||
query.setParams(time.Time{}, time.Time{}, 0, 0)
|
||||
@ -63,7 +64,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
|
||||
res := &backend.DataResponse{}
|
||||
query := &cloudMonitoringTimeSeriesList{params: url.Values{}, parameters: &dataquery.TimeSeriesList{}}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
require.Len(t, frames, 1)
|
||||
@ -86,7 +87,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
assert.Equal(t, 3, len(data.TimeSeries))
|
||||
res := &backend.DataResponse{}
|
||||
query := &cloudMonitoringTimeSeriesList{params: url.Values{}, parameters: &dataquery.TimeSeriesList{}}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
field := res.Frames[0].Fields[1]
|
||||
@ -128,7 +129,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
query := &cloudMonitoringTimeSeriesList{params: url.Values{}, parameters: &dataquery.TimeSeriesList{GroupBys: []string{
|
||||
"metric.label.instance_name", "resource.label.zone",
|
||||
}}}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
require.NoError(t, err)
|
||||
@ -153,7 +154,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
},
|
||||
aliasBy: "{{metric.type}} - {{metric.label.instance_name}} - {{resource.label.zone}}",
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
require.NoError(t, err)
|
||||
@ -170,7 +171,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
parameters: &dataquery.TimeSeriesList{GroupBys: []string{"metric.label.instance_name", "resource.label.zone"}},
|
||||
aliasBy: "metric {{metric.name}} service {{metric.service}}",
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
require.NoError(t, err)
|
||||
@ -192,7 +193,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
parameters: &dataquery.TimeSeriesList{},
|
||||
aliasBy: "{{bucket}}",
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
require.NoError(t, err)
|
||||
@ -237,7 +238,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
parameters: &dataquery.TimeSeriesList{},
|
||||
aliasBy: "{{bucket}}",
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
require.NoError(t, err)
|
||||
@ -275,7 +276,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
parameters: &dataquery.TimeSeriesList{},
|
||||
aliasBy: "{{bucket}}",
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 3, len(res.Frames))
|
||||
@ -315,7 +316,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
parameters: &dataquery.TimeSeriesList{},
|
||||
aliasBy: "{{metadata.system_labels.test}}",
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
require.NoError(t, err)
|
||||
@ -333,7 +334,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
parameters: &dataquery.TimeSeriesList{},
|
||||
aliasBy: "{{metadata.system_labels.test2}}",
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
require.NoError(t, err)
|
||||
@ -349,7 +350,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
assert.Equal(t, 1, len(data.TimeSeries))
|
||||
res := &backend.DataResponse{}
|
||||
query := &cloudMonitoringTimeSeriesList{params: url.Values{}, parameters: &dataquery.TimeSeriesList{}}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
require.NoError(t, err)
|
||||
@ -362,7 +363,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
assert.Equal(t, 3, len(data.TimeSeries))
|
||||
res := &backend.DataResponse{}
|
||||
query := &cloudMonitoringTimeSeriesList{params: url.Values{}, parameters: &dataquery.TimeSeriesList{}}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
require.NoError(t, err)
|
||||
@ -391,7 +392,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
To: fromStart.Add(34 * time.Minute),
|
||||
},
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
assert.Equal(t, "test-proj - asia-northeast1-c - 6724404429462225363 - 200", frames[0].Fields[1].Name)
|
||||
@ -412,7 +413,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
GraphPeriod: strPtr("60s"),
|
||||
},
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "value_utilization_sum", res.Frames[0].Fields[1].Name)
|
||||
})
|
||||
@ -423,7 +424,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
assert.Equal(t, 3, len(data.TimeSeries))
|
||||
res := &backend.DataResponse{}
|
||||
query := &cloudMonitoringTimeSeriesList{params: url.Values{}, parameters: &dataquery.TimeSeriesList{}}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
custom, ok := frames[0].Meta.Custom.(map[string]any)
|
||||
@ -441,7 +442,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
query := &cloudMonitoringTimeSeriesList{params: url.Values{
|
||||
"aggregation.alignmentPeriod": []string{"+60s"},
|
||||
}, parameters: &dataquery.TimeSeriesList{}}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
timeField := frames[0].Fields[0]
|
||||
@ -455,7 +456,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
assert.Equal(t, 1, len(data.TimeSeries))
|
||||
|
||||
res := &backend.DataResponse{}
|
||||
require.NoError(t, (&cloudMonitoringTimeSeriesList{parameters: &dataquery.TimeSeriesList{GroupBys: []string{"test_group_by"}}}).parseResponse(res, data, "test_query"))
|
||||
require.NoError(t, (&cloudMonitoringTimeSeriesList{parameters: &dataquery.TimeSeriesList{GroupBys: []string{"test_group_by"}}}).parseResponse(res, data, "test_query", service.logger))
|
||||
|
||||
require.NotNil(t, res.Frames[0].Meta)
|
||||
assert.Equal(t, sdkdata.FrameMeta{
|
||||
@ -478,7 +479,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
assert.Equal(t, 1, len(data.TimeSeries))
|
||||
|
||||
res := &backend.DataResponse{}
|
||||
require.NoError(t, (&cloudMonitoringTimeSeriesList{parameters: &dataquery.TimeSeriesList{GroupBys: []string{"test_group_by"}}}).parseResponse(res, data, "test_query"))
|
||||
require.NoError(t, (&cloudMonitoringTimeSeriesList{parameters: &dataquery.TimeSeriesList{GroupBys: []string{"test_group_by"}}}).parseResponse(res, data, "test_query", service.logger))
|
||||
|
||||
require.NotNil(t, res.Frames[0].Meta)
|
||||
assert.Equal(t, sdkdata.FrameMeta{
|
||||
@ -501,7 +502,7 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
assert.Equal(t, 1, len(data.TimeSeries))
|
||||
|
||||
res := &backend.DataResponse{}
|
||||
require.NoError(t, (&cloudMonitoringTimeSeriesList{parameters: &dataquery.TimeSeriesList{GroupBys: []string{"test_group_by"}}}).parseResponse(res, data, "test_query"))
|
||||
require.NoError(t, (&cloudMonitoringTimeSeriesList{parameters: &dataquery.TimeSeriesList{GroupBys: []string{"test_group_by"}}}).parseResponse(res, data, "test_query", service.logger))
|
||||
|
||||
require.NotNil(t, res.Frames[0].Meta)
|
||||
assert.Equal(t, sdkdata.FrameMeta{
|
||||
|
@ -7,10 +7,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
|
||||
gcmTime "github.com/grafana/grafana/pkg/tsdb/cloud-monitoring/time"
|
||||
)
|
||||
|
||||
func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) appendGraphPeriod(req *backend.QueryDataRequest) string {
|
||||
@ -18,7 +17,7 @@ func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) appendGraphPeriod(req *ba
|
||||
// If not set, the default behavior is to set an automatic value
|
||||
if timeSeriesQuery.parameters.GraphPeriod == nil || *timeSeriesQuery.parameters.GraphPeriod != "disabled" {
|
||||
if timeSeriesQuery.parameters.GraphPeriod == nil || *timeSeriesQuery.parameters.GraphPeriod == "auto" || *timeSeriesQuery.parameters.GraphPeriod == "" {
|
||||
intervalCalculator := intervalv2.NewCalculator(intervalv2.CalculatorOptions{})
|
||||
intervalCalculator := gcmTime.NewCalculator(gcmTime.CalculatorOptions{})
|
||||
interval := intervalCalculator.Calculate(req.Queries[0].TimeRange, time.Duration(timeSeriesQuery.IntervalMS/1000)*time.Second, req.Queries[0].MaxDataPoints)
|
||||
timeSeriesQuery.parameters.GraphPeriod = &interval.Text
|
||||
}
|
||||
@ -28,7 +27,7 @@ func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) appendGraphPeriod(req *ba
|
||||
}
|
||||
|
||||
func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) run(ctx context.Context, req *backend.QueryDataRequest,
|
||||
s *Service, dsInfo datasourceInfo, tracer tracing.Tracer) (*backend.DataResponse, any, string, error) {
|
||||
s *Service, dsInfo datasourceInfo, logger log.Logger) (*backend.DataResponse, any, string, error) {
|
||||
timeSeriesQuery.parameters.Query += timeSeriesQuery.appendGraphPeriod(req)
|
||||
from := req.Queries[0].TimeRange.From
|
||||
to := req.Queries[0].TimeRange.To
|
||||
@ -37,11 +36,11 @@ func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) run(ctx context.Context,
|
||||
requestBody := map[string]any{
|
||||
"query": timeSeriesQuery.parameters.Query,
|
||||
}
|
||||
return runTimeSeriesRequest(ctx, timeSeriesQuery.logger, req, s, dsInfo, tracer, timeSeriesQuery.parameters.ProjectName, nil, requestBody)
|
||||
return runTimeSeriesRequest(ctx, req, s, dsInfo, timeSeriesQuery.parameters.ProjectName, nil, requestBody, logger)
|
||||
}
|
||||
|
||||
func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *backend.DataResponse,
|
||||
res any, executedQueryString string) error {
|
||||
res any, executedQueryString string, logger log.Logger) error {
|
||||
response := res.(cloudMonitoringResponse)
|
||||
frames := data.Frames{}
|
||||
|
||||
@ -103,7 +102,7 @@ func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) buildDeepLink() string {
|
||||
timeSeriesQuery.timeRange.To.Format(time.RFC3339Nano),
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error(
|
||||
backend.Logger.Error(
|
||||
"Failed to generate deep link: unable to parse metrics explorer URL",
|
||||
"ProjectName", timeSeriesQuery.parameters.Query,
|
||||
"error", err,
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func TestTimeSeriesQuery(t *testing.T) {
|
||||
service := &Service{}
|
||||
t.Run("multiple point descriptor is returned", func(t *testing.T) {
|
||||
data, err := loadTestFile("./test-data/8-series-response-mql-multiple-point-descriptors.json")
|
||||
require.NoError(t, err)
|
||||
@ -33,7 +34,7 @@ func TestTimeSeriesQuery(t *testing.T) {
|
||||
To: fromStart.Add(34 * time.Minute),
|
||||
},
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
frames := res.Frames
|
||||
assert.Equal(t, "grafana-prod asia-northeast1-c 6724404429462225363 200", frames[0].Fields[1].Name)
|
||||
assert.Equal(t, 843302441.9, frames[0].Fields[1].At(0))
|
||||
@ -52,7 +53,7 @@ func TestTimeSeriesQuery(t *testing.T) {
|
||||
To: fromStart.Add(34 * time.Minute),
|
||||
},
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
frames := res.Frames
|
||||
assert.Equal(t, "test-proj - asia-northeast1-c - 6724404429462225363 - 200", frames[0].Fields[1].Name)
|
||||
})
|
||||
@ -79,7 +80,7 @@ func TestTimeSeriesQuery(t *testing.T) {
|
||||
To: fromStart.Add(34 * time.Minute),
|
||||
},
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
assert.Equal(t, 1, len(res.Frames))
|
||||
@ -103,7 +104,7 @@ func TestTimeSeriesQuery(t *testing.T) {
|
||||
To: fromStart.Add(34 * time.Minute),
|
||||
},
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
custom, ok := frames[0].Meta.Custom.(map[string]any)
|
||||
@ -131,7 +132,7 @@ func TestTimeSeriesQuery(t *testing.T) {
|
||||
To: fromStart.Add(34 * time.Minute),
|
||||
},
|
||||
}
|
||||
err = query.parseResponse(res, data, "")
|
||||
err = query.parseResponse(res, data, "", service.logger)
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
timeField := frames[0].Fields[0]
|
||||
|
@ -9,19 +9,18 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/huandu/xstrings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloud-monitoring/kinds/dataquery"
|
||||
)
|
||||
|
||||
type (
|
||||
cloudMonitoringQueryExecutor interface {
|
||||
run(ctx context.Context, req *backend.QueryDataRequest, s *Service, dsInfo datasourceInfo, tracer tracing.Tracer) (
|
||||
run(ctx context.Context, req *backend.QueryDataRequest, s *Service, dsInfo datasourceInfo, logger log.Logger) (
|
||||
*backend.DataResponse, any, string, error)
|
||||
parseResponse(dr *backend.DataResponse, data any, executedQueryString string) error
|
||||
parseResponse(dr *backend.DataResponse, data any, executedQueryString string, logger log.Logger) error
|
||||
buildDeepLink() string
|
||||
getRefID() string
|
||||
getAliasBy() string
|
||||
@ -41,7 +40,6 @@ type (
|
||||
cloudMonitoringTimeSeriesList struct {
|
||||
refID string
|
||||
aliasBy string
|
||||
logger log.Logger
|
||||
parameters *dataquery.TimeSeriesList
|
||||
// Processed properties
|
||||
params url.Values
|
||||
@ -50,7 +48,6 @@ type (
|
||||
cloudMonitoringSLO struct {
|
||||
refID string
|
||||
aliasBy string
|
||||
logger log.Logger
|
||||
parameters *dataquery.SLOQuery
|
||||
// Processed properties
|
||||
params url.Values
|
||||
@ -59,8 +56,8 @@ type (
|
||||
// cloudMonitoringProm is used to build a promQL queries
|
||||
cloudMonitoringProm struct {
|
||||
refID string
|
||||
aliasBy string
|
||||
logger log.Logger
|
||||
aliasBy string
|
||||
parameters *dataquery.PromQLQuery
|
||||
timeRange backend.TimeRange
|
||||
IntervalMS int64
|
||||
@ -69,8 +66,8 @@ type (
|
||||
// cloudMonitoringTimeSeriesQuery is used to build MQL queries
|
||||
cloudMonitoringTimeSeriesQuery struct {
|
||||
refID string
|
||||
aliasBy string
|
||||
logger log.Logger
|
||||
aliasBy string
|
||||
parameters *dataquery.TimeSeriesQuery
|
||||
// Processed properties
|
||||
timeRange backend.TimeRange
|
||||
|
@ -14,18 +14,17 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
gcmTime "github.com/grafana/grafana/pkg/tsdb/cloud-monitoring/time"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
|
||||
)
|
||||
|
||||
func addInterval(period string, field *data.Field) error {
|
||||
period = strings.TrimPrefix(period, "+")
|
||||
p, err := intervalv2.ParseIntervalStringToTimeDuration(period)
|
||||
p, err := gcmTime.ParseIntervalStringToTimeDuration(period)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -48,7 +47,7 @@ func toString(v any) string {
|
||||
return v.(string)
|
||||
}
|
||||
|
||||
func createRequest(ctx context.Context, logger log.Logger, dsInfo *datasourceInfo, proxyPass string, body io.Reader) (*http.Request, error) {
|
||||
func createRequest(ctx context.Context, dsInfo *datasourceInfo, proxyPass string, body io.Reader) (*http.Request, error) {
|
||||
u, err := url.Parse(dsInfo.url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -61,7 +60,7 @@ func createRequest(ctx context.Context, logger log.Logger, dsInfo *datasourceInf
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, method, dsInfo.services[cloudMonitor].url, body)
|
||||
if err != nil {
|
||||
logger.Error("Failed to create request", "error", err)
|
||||
backend.Logger.Error("Failed to create request", "error", err)
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
@ -71,7 +70,7 @@ func createRequest(ctx context.Context, logger log.Logger, dsInfo *datasourceInf
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func doRequestPage(ctx context.Context, logger log.Logger, r *http.Request, dsInfo datasourceInfo, params url.Values, body map[string]any) (cloudMonitoringResponse, error) {
|
||||
func doRequestPage(ctx context.Context, r *http.Request, dsInfo datasourceInfo, params url.Values, body map[string]any, logger log.Logger) (cloudMonitoringResponse, error) {
|
||||
if params != nil {
|
||||
r.URL.RawQuery = params.Encode()
|
||||
}
|
||||
@ -90,11 +89,11 @@ func doRequestPage(ctx context.Context, logger log.Logger, r *http.Request, dsIn
|
||||
|
||||
defer func() {
|
||||
if err = res.Body.Close(); err != nil {
|
||||
logger.Warn("Failed to close response body", "error", err)
|
||||
backend.Logger.Warn("Failed to close response body", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
dnext, err := unmarshalResponse(logger, res)
|
||||
dnext, err := unmarshalResponse(res, logger)
|
||||
if err != nil {
|
||||
return cloudMonitoringResponse{}, err
|
||||
}
|
||||
@ -102,8 +101,8 @@ func doRequestPage(ctx context.Context, logger log.Logger, r *http.Request, dsIn
|
||||
return dnext, nil
|
||||
}
|
||||
|
||||
func doRequestWithPagination(ctx context.Context, logger log.Logger, r *http.Request, dsInfo datasourceInfo, params url.Values, body map[string]any) (cloudMonitoringResponse, error) {
|
||||
d, err := doRequestPage(ctx, logger, r, dsInfo, params, body)
|
||||
func doRequestWithPagination(ctx context.Context, r *http.Request, dsInfo datasourceInfo, params url.Values, body map[string]any, logger log.Logger) (cloudMonitoringResponse, error) {
|
||||
d, err := doRequestPage(ctx, r, dsInfo, params, body, logger)
|
||||
if err != nil {
|
||||
return cloudMonitoringResponse{}, err
|
||||
}
|
||||
@ -114,7 +113,7 @@ func doRequestWithPagination(ctx context.Context, logger log.Logger, r *http.Req
|
||||
if body != nil {
|
||||
body["pageToken"] = d.NextPageToken
|
||||
}
|
||||
nextPage, err := doRequestPage(ctx, logger, r, dsInfo, params, body)
|
||||
nextPage, err := doRequestPage(ctx, r, dsInfo, params, body, logger)
|
||||
if err != nil {
|
||||
return cloudMonitoringResponse{}, err
|
||||
}
|
||||
@ -125,20 +124,20 @@ func doRequestWithPagination(ctx context.Context, logger log.Logger, r *http.Req
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func traceReq(ctx context.Context, tracer tracing.Tracer, req *backend.QueryDataRequest, dsInfo datasourceInfo, r *http.Request, target string) trace.Span {
|
||||
ctx, span := tracer.Start(ctx, "cloudMonitoring query", trace.WithAttributes(
|
||||
func traceReq(ctx context.Context, req *backend.QueryDataRequest, dsInfo datasourceInfo, r *http.Request, target string) trace.Span {
|
||||
_, span := tracing.DefaultTracer().Start(ctx, "cloudMonitoring query", trace.WithAttributes(
|
||||
attribute.String("target", target),
|
||||
attribute.String("from", req.Queries[0].TimeRange.From.String()),
|
||||
attribute.String("until", req.Queries[0].TimeRange.To.String()),
|
||||
attribute.Int64("datasource_id", dsInfo.id),
|
||||
attribute.Int64("org_id", req.PluginContext.OrgID),
|
||||
))
|
||||
tracer.Inject(ctx, r.Header, span)
|
||||
defer span.End()
|
||||
return span
|
||||
}
|
||||
|
||||
func runTimeSeriesRequest(ctx context.Context, logger log.Logger, req *backend.QueryDataRequest,
|
||||
s *Service, dsInfo datasourceInfo, tracer tracing.Tracer, projectName string, params url.Values, body map[string]any) (*backend.DataResponse, cloudMonitoringResponse, string, error) {
|
||||
func runTimeSeriesRequest(ctx context.Context, req *backend.QueryDataRequest,
|
||||
s *Service, dsInfo datasourceInfo, projectName string, params url.Values, body map[string]any, logger log.Logger) (*backend.DataResponse, cloudMonitoringResponse, string, error) {
|
||||
dr := &backend.DataResponse{}
|
||||
projectName, err := s.ensureProject(ctx, dsInfo, projectName)
|
||||
if err != nil {
|
||||
@ -149,16 +148,16 @@ func runTimeSeriesRequest(ctx context.Context, logger log.Logger, req *backend.Q
|
||||
if body != nil {
|
||||
timeSeriesMethod += ":query"
|
||||
}
|
||||
r, err := createRequest(ctx, logger, &dsInfo, path.Join("/v3/projects", projectName, timeSeriesMethod), nil)
|
||||
r, err := createRequest(ctx, &dsInfo, path.Join("/v3/projects", projectName, timeSeriesMethod), nil)
|
||||
if err != nil {
|
||||
dr.Error = err
|
||||
return dr, cloudMonitoringResponse{}, "", nil
|
||||
}
|
||||
|
||||
span := traceReq(ctx, tracer, req, dsInfo, r, params.Encode())
|
||||
span := traceReq(ctx, req, dsInfo, r, params.Encode())
|
||||
defer span.End()
|
||||
|
||||
d, err := doRequestWithPagination(ctx, logger, r, dsInfo, params, body)
|
||||
d, err := doRequestWithPagination(ctx, r, dsInfo, params, body, logger)
|
||||
if err != nil {
|
||||
dr.Error = err
|
||||
return dr, cloudMonitoringResponse{}, "", nil
|
||||
|
@ -30,7 +30,9 @@ const mssqlPlugin = async () =>
|
||||
const testDataDSPlugin = async () =>
|
||||
await import(/* webpackChunkName: "testDataDSPlugin" */ '@grafana-plugins/grafana-testdata-datasource/module');
|
||||
const cloudMonitoringPlugin = async () =>
|
||||
await import(/* webpackChunkName: "cloudMonitoringPlugin" */ 'app/plugins/datasource/cloud-monitoring/module');
|
||||
await import(
|
||||
/* webpackChunkName: "cloudMonitoringPlugin" */ '@grafana-plugins/grafana-cloud-monitoring-datasource/module'
|
||||
);
|
||||
const azureMonitorPlugin = async () =>
|
||||
await import(/* webpackChunkName: "azureMonitorPlugin" */ '@grafana-plugins/grafana-azure-monitor-datasource/module');
|
||||
const tempoPlugin = async () => await import(/* webpackChunkName: "tempoPlugin" */ '@grafana-plugins/tempo/module');
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { openMenu } from 'react-select-event';
|
||||
import { TemplateSrvStub } from 'test/specs/helpers';
|
||||
|
||||
import { TemplateSrvStub } from '../../../../../test/specs/helpers';
|
||||
import { MetricKind, ValueTypes } from '../types/query';
|
||||
import { MetricDescriptor } from '../types/types';
|
||||
|
||||
|
55
public/app/plugins/datasource/cloud-monitoring/package.json
Normal file
55
public/app/plugins/datasource/cloud-monitoring/package.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "@grafana-plugins/grafana-cloud-monitoring-datasource",
|
||||
"description": "Grafana data source for Google Cloud Monitoring",
|
||||
"private": true,
|
||||
"version": "10.4.0-pre",
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.11.2",
|
||||
"@grafana/data": "10.4.0-pre",
|
||||
"@grafana/experimental": "1.7.4",
|
||||
"@grafana/google-sdk": "0.1.1",
|
||||
"@grafana/runtime": "10.4.0-pre",
|
||||
"@grafana/schema": "10.4.0-pre",
|
||||
"@grafana/ui": "10.4.0-pre",
|
||||
"@kusto/monaco-kusto": "^7.4.0",
|
||||
"debounce-promise": "3.1.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"i18next": "^22.0.0",
|
||||
"immer": "10.0.2",
|
||||
"lodash": "4.17.21",
|
||||
"monaco-editor": "0.34.0",
|
||||
"prismjs": "1.29.0",
|
||||
"react": "18.2.0",
|
||||
"react-use": "17.4.0",
|
||||
"rxjs": "7.8.1",
|
||||
"tslib": "2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/e2e-selectors": "10.4.0-pre",
|
||||
"@grafana/plugin-configs": "10.4.0-pre",
|
||||
"@testing-library/react": "14.1.2",
|
||||
"@testing-library/user-event": "14.5.2",
|
||||
"@types/debounce-promise": "3.1.6",
|
||||
"@types/jest": "29.5.11",
|
||||
"@types/lodash": "4.14.202",
|
||||
"@types/node": "20.11.7",
|
||||
"@types/prismjs": "1.26.3",
|
||||
"@types/react": "18.2.48",
|
||||
"@types/react-test-renderer": "18.0.0",
|
||||
"@types/testing-library__jest-dom": "5.14.9",
|
||||
"react-select-event": "5.5.1",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.3.3",
|
||||
"webpack": "5.90.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@grafana/runtime": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack -c ./webpack.config.ts --env production",
|
||||
"build:commit": "webpack -c ./webpack.config.ts --env production --env commit=$(git rev-parse --short HEAD)",
|
||||
"dev": "webpack -w -c ./webpack.config.ts --env development"
|
||||
},
|
||||
"packageManager": "yarn@3.6.0"
|
||||
}
|
@ -82,7 +82,7 @@
|
||||
|
||||
"info": {
|
||||
"description": "Data source for Google's monitoring service (formerly named Stackdriver)",
|
||||
"version": "1.0.0",
|
||||
"version": "%VERSION%",
|
||||
"logos": {
|
||||
"small": "img/cloud_monitoring_logo.svg",
|
||||
"large": "img/cloud_monitoring_logo.svg"
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { DataQueryRequest, DataSourceInstanceSettings, toUtc } from '@grafana/data';
|
||||
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; // will use the version in __mocks__
|
||||
|
||||
import { CustomVariableModel } from '../../../../features/variables/types';
|
||||
import CloudMonitoringDataSource from '../datasource';
|
||||
import { CloudMonitoringQuery } from '../types/query';
|
||||
import { CloudMonitoringOptions } from '../types/types';
|
||||
import { CloudMonitoringOptions, CustomVariableModel } from '../types/types';
|
||||
|
||||
let getTempVars = () => [] as CustomVariableModel[];
|
||||
let replace = () => '';
|
||||
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@grafana/plugin-configs/tsconfig.json",
|
||||
"include": ["."],
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { DataQuery, SelectableValue } from '@grafana/data';
|
||||
import { DataQuery, SelectableValue, VariableWithMultiSupport } from '@grafana/data';
|
||||
import { DataSourceOptions, DataSourceSecureJsonData } from '@grafana/google-sdk';
|
||||
|
||||
import { MetricKind } from './query';
|
||||
@ -61,3 +61,7 @@ export interface CustomMetaData {
|
||||
export interface PostResponse {
|
||||
results: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface CustomVariableModel extends VariableWithMultiSupport {
|
||||
type: 'custom';
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
import config from '@grafana/plugin-configs/webpack.config';
|
||||
|
||||
export default config;
|
@ -302,7 +302,6 @@
|
||||
},
|
||||
"toolbar": {
|
||||
"add": "Hinzufügen",
|
||||
"add-panel": "Panel hinzufügen",
|
||||
"mark-favorite": "Als Favorit markieren",
|
||||
"open-original": "Original-Dashboard öffnen",
|
||||
"playlist-next": "Zum nächsten Dashboard",
|
||||
|
@ -302,7 +302,6 @@
|
||||
},
|
||||
"toolbar": {
|
||||
"add": "Add",
|
||||
"add-panel": "Add panel",
|
||||
"mark-favorite": "Mark as favorite",
|
||||
"open-original": "Open original dashboard",
|
||||
"playlist-next": "Go to next dashboard",
|
||||
|
@ -307,7 +307,6 @@
|
||||
},
|
||||
"toolbar": {
|
||||
"add": "Añadir",
|
||||
"add-panel": "Añadir panel",
|
||||
"mark-favorite": "Marcar como favorito",
|
||||
"open-original": "Abrir el panel de control original",
|
||||
"playlist-next": "Ir al siguiente panel de control",
|
||||
|
@ -307,7 +307,6 @@
|
||||
},
|
||||
"toolbar": {
|
||||
"add": "Ajouter",
|
||||
"add-panel": "Ajouter un panneau",
|
||||
"mark-favorite": "Marquer comme favori",
|
||||
"open-original": "Ouvrir le tableau de bord d'origine",
|
||||
"playlist-next": "Accéder au tableau de bord suivant",
|
||||
|
@ -302,7 +302,6 @@
|
||||
},
|
||||
"toolbar": {
|
||||
"add": "Åđđ",
|
||||
"add-panel": "Åđđ päʼnęľ",
|
||||
"mark-favorite": "Mäřĸ äş ƒävőřįŧę",
|
||||
"open-original": "Øpęʼn őřįģįʼnäľ đäşĥþőäřđ",
|
||||
"playlist-next": "Ğő ŧő ʼnęχŧ đäşĥþőäřđ",
|
||||
|
@ -297,7 +297,6 @@
|
||||
},
|
||||
"toolbar": {
|
||||
"add": "添加",
|
||||
"add-panel": "添加面板",
|
||||
"mark-favorite": "标记为收藏",
|
||||
"open-original": "打开原始仪表板",
|
||||
"playlist-next": "前往下一个仪表板",
|
||||
|
110
yarn.lock
110
yarn.lock
@ -2972,6 +2972,51 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana-plugins/grafana-cloud-monitoring-datasource@workspace:*, @grafana-plugins/grafana-cloud-monitoring-datasource@workspace:public/app/plugins/datasource/cloud-monitoring":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana-plugins/grafana-cloud-monitoring-datasource@workspace:public/app/plugins/datasource/cloud-monitoring"
|
||||
dependencies:
|
||||
"@emotion/css": "npm:11.11.2"
|
||||
"@grafana/data": "npm:10.4.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:10.4.0-pre"
|
||||
"@grafana/experimental": "npm:1.7.4"
|
||||
"@grafana/google-sdk": "npm:0.1.1"
|
||||
"@grafana/plugin-configs": "npm:10.4.0-pre"
|
||||
"@grafana/runtime": "npm:10.4.0-pre"
|
||||
"@grafana/schema": "npm:10.4.0-pre"
|
||||
"@grafana/ui": "npm:10.4.0-pre"
|
||||
"@kusto/monaco-kusto": "npm:^7.4.0"
|
||||
"@testing-library/react": "npm:14.1.2"
|
||||
"@testing-library/user-event": "npm:14.5.2"
|
||||
"@types/debounce-promise": "npm:3.1.6"
|
||||
"@types/jest": "npm:29.5.11"
|
||||
"@types/lodash": "npm:4.14.202"
|
||||
"@types/node": "npm:20.11.7"
|
||||
"@types/prismjs": "npm:1.26.3"
|
||||
"@types/react": "npm:18.2.48"
|
||||
"@types/react-test-renderer": "npm:18.0.0"
|
||||
"@types/testing-library__jest-dom": "npm:5.14.9"
|
||||
debounce-promise: "npm:3.1.2"
|
||||
fast-deep-equal: "npm:^3.1.3"
|
||||
i18next: "npm:^22.0.0"
|
||||
immer: "npm:10.0.2"
|
||||
lodash: "npm:4.17.21"
|
||||
monaco-editor: "npm:0.34.0"
|
||||
prismjs: "npm:1.29.0"
|
||||
react: "npm:18.2.0"
|
||||
react-select-event: "npm:5.5.1"
|
||||
react-test-renderer: "npm:18.2.0"
|
||||
react-use: "npm:17.4.0"
|
||||
rxjs: "npm:7.8.1"
|
||||
ts-node: "npm:10.9.2"
|
||||
tslib: "npm:2.6.0"
|
||||
typescript: "npm:5.3.3"
|
||||
webpack: "npm:5.90.0"
|
||||
peerDependencies:
|
||||
"@grafana/runtime": "*"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana-plugins/grafana-pyroscope-datasource@workspace:*, @grafana-plugins/grafana-pyroscope-datasource@workspace:public/app/plugins/datasource/grafana-pyroscope-datasource":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana-plugins/grafana-pyroscope-datasource@workspace:public/app/plugins/datasource/grafana-pyroscope-datasource"
|
||||
@ -3366,6 +3411,26 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/experimental@npm:1.7.4":
|
||||
version: 1.7.4
|
||||
resolution: "@grafana/experimental@npm:1.7.4"
|
||||
dependencies:
|
||||
"@types/uuid": "npm:^8.3.3"
|
||||
semver: "npm:^7.5.4"
|
||||
uuid: "npm:^8.3.2"
|
||||
peerDependencies:
|
||||
"@emotion/css": 11.1.3
|
||||
"@grafana/data": ^10.0.0
|
||||
"@grafana/runtime": ^10.0.0
|
||||
"@grafana/ui": ^10.0.0
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2
|
||||
react-select: ^5.2.1
|
||||
rxjs: 7.8.0
|
||||
checksum: e62e6382396d282281e31011c936fe9164650ea8caea7a09c4ef8615a66bb0ab0f74c4946354276457a2f5d1276016fc4225a08b8afb043c81e11b010f52b26d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/experimental@npm:1.7.8":
|
||||
version: 1.7.8
|
||||
resolution: "@grafana/experimental@npm:1.7.8"
|
||||
@ -3458,6 +3523,18 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/google-sdk@npm:0.1.1":
|
||||
version: 0.1.1
|
||||
resolution: "@grafana/google-sdk@npm:0.1.1"
|
||||
peerDependencies:
|
||||
"@grafana/data": 9.4.1
|
||||
"@grafana/ui": 9.4.1
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2
|
||||
checksum: a21544b624f432e19bad05c5dcb2441581453ab34d5a5709cf63191a59f442f8dcd60a935a39bfa7074e554e1761dfa50b4871729dec6b5f2f0485627208ba16
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/google-sdk@npm:0.1.2":
|
||||
version: 0.1.2
|
||||
resolution: "@grafana/google-sdk@npm:0.1.2"
|
||||
@ -8394,6 +8471,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/debounce-promise@npm:3.1.6":
|
||||
version: 3.1.6
|
||||
resolution: "@types/debounce-promise@npm:3.1.6"
|
||||
checksum: db1e5bf86215dfcdd0b0f94497269b0be5b27e0b9622bbad53c7a24de078229b97927f848549bb661795575d80615fd935f8357d8f23928c0526b158b51fa122
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/debounce-promise@npm:3.1.9":
|
||||
version: 3.1.9
|
||||
resolution: "@types/debounce-promise@npm:3.1.9"
|
||||
@ -8898,6 +8982,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:20.11.7":
|
||||
version: 20.11.7
|
||||
resolution: "@types/node@npm:20.11.7"
|
||||
dependencies:
|
||||
undici-types: "npm:~5.26.4"
|
||||
checksum: ff0428c093987f1f6e3400b91450077cf006d7f0e4ff316e20636218562c53761565ceb06bd7a161badf46e6884d78b554c7b1c3a7717cf5cb8c97f461ecd754
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:^14.14.31":
|
||||
version: 14.18.36
|
||||
resolution: "@types/node@npm:14.18.36"
|
||||
@ -9099,6 +9192,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-test-renderer@npm:18.0.0":
|
||||
version: 18.0.0
|
||||
resolution: "@types/react-test-renderer@npm:18.0.0"
|
||||
dependencies:
|
||||
"@types/react": "npm:*"
|
||||
checksum: 6afc938a1d7618d88ab8793e251f0bd5981bf3f08c1b600f74df3f8800b92589ea534dc6dcb7c8d683893fcc740bf8d7843a42bf2dae59785cfe88f004bd7b0b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-test-renderer@npm:18.0.7":
|
||||
version: 18.0.7
|
||||
resolution: "@types/react-test-renderer@npm:18.0.7"
|
||||
@ -17307,6 +17409,7 @@ __metadata:
|
||||
"@floating-ui/react": "npm:0.26.8"
|
||||
"@glideapps/glide-data-grid": "npm:^6.0.0"
|
||||
"@grafana-plugins/grafana-azure-monitor-datasource": "workspace:*"
|
||||
"@grafana-plugins/grafana-cloud-monitoring-datasource": "workspace:*"
|
||||
"@grafana-plugins/grafana-pyroscope-datasource": "workspace:*"
|
||||
"@grafana-plugins/grafana-testdata-datasource": "workspace:*"
|
||||
"@grafana-plugins/parca": "workspace:*"
|
||||
@ -18446,6 +18549,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"immer@npm:10.0.2":
|
||||
version: 10.0.2
|
||||
resolution: "immer@npm:10.0.2"
|
||||
checksum: 5fcddbbc036428bb3db1af66d6f6c3aaf9dfb21ab3e476894f45e3b60e35fb64af67ffab9e626770ab0154d5ca83895038a0af7c25513144e19cba1ab19ec4ef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"immer@npm:10.0.3":
|
||||
version: 10.0.3
|
||||
resolution: "immer@npm:10.0.3"
|
||||
|
Loading…
Reference in New Issue
Block a user