mirror of https://github.com/grafana/grafana.git
				
				
				
			Cloud Monitoring: Use new annotation API (#49026)
* remove angular code * format annotation on backend * format time with time type instead of string * update annotation query tests * update get alignment data function * update annotation query editor * add annotation query editor test * update struct * add tests * remove extracted function * remove non-null assertion * remove stray commented out console.log * fix jest haste map warning * add alignment period * add AnnotationMetricQuery type
This commit is contained in:
		
							parent
							
								
									26e98a6f1b
								
							
						
					
					
						commit
						0a95d493e3
					
				| 
						 | 
					@ -4,11 +4,19 @@ import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/grafana/grafana-plugin-sdk-go/backend"
 | 
						"github.com/grafana/grafana-plugin-sdk-go/backend"
 | 
				
			||||||
	"github.com/grafana/grafana-plugin-sdk-go/data"
 | 
						"github.com/grafana/grafana-plugin-sdk-go/data"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type annotationEvent struct {
 | 
				
			||||||
 | 
						Title string
 | 
				
			||||||
 | 
						Time  time.Time
 | 
				
			||||||
 | 
						Tags  string
 | 
				
			||||||
 | 
						Text  string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Service) executeAnnotationQuery(ctx context.Context, req *backend.QueryDataRequest, dsInfo datasourceInfo) (
 | 
					func (s *Service) executeAnnotationQuery(ctx context.Context, req *backend.QueryDataRequest, dsInfo datasourceInfo) (
 | 
				
			||||||
	*backend.QueryDataResponse, error) {
 | 
						*backend.QueryDataResponse, error) {
 | 
				
			||||||
	resp := backend.NewQueryDataResponse()
 | 
						resp := backend.NewQueryDataResponse()
 | 
				
			||||||
| 
						 | 
					@ -24,8 +32,10 @@ func (s *Service) executeAnnotationQuery(ctx context.Context, req *backend.Query
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mq := struct {
 | 
						mq := struct {
 | 
				
			||||||
		Title string `json:"title"`
 | 
							MetricQuery struct {
 | 
				
			||||||
		Text  string `json:"text"`
 | 
								Title string `json:"title"`
 | 
				
			||||||
 | 
								Text  string `json:"text"`
 | 
				
			||||||
 | 
							} `json:"metricQuery"`
 | 
				
			||||||
	}{}
 | 
						}{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	firstQuery := req.Queries[0]
 | 
						firstQuery := req.Queries[0]
 | 
				
			||||||
| 
						 | 
					@ -33,32 +43,23 @@ func (s *Service) executeAnnotationQuery(ctx context.Context, req *backend.Query
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return resp, nil
 | 
							return resp, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	err = queries[0].parseToAnnotations(queryRes, dr, mq.Title, mq.Text)
 | 
						err = queries[0].parseToAnnotations(queryRes, dr, mq.MetricQuery.Title, mq.MetricQuery.Text)
 | 
				
			||||||
	resp.Responses[firstQuery.RefID] = *queryRes
 | 
						resp.Responses[firstQuery.RefID] = *queryRes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return resp, err
 | 
						return resp, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) transformAnnotationToFrame(annotations []map[string]string, result *backend.DataResponse) {
 | 
					func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) transformAnnotationToFrame(annotations []*annotationEvent, result *backend.DataResponse) {
 | 
				
			||||||
	frames := data.Frames{}
 | 
						frame := data.NewFrame(timeSeriesQuery.RefID,
 | 
				
			||||||
 | 
							data.NewField("time", nil, []time.Time{}),
 | 
				
			||||||
 | 
							data.NewField("title", nil, []string{}),
 | 
				
			||||||
 | 
							data.NewField("tags", nil, []string{}),
 | 
				
			||||||
 | 
							data.NewField("text", nil, []string{}),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
	for _, a := range annotations {
 | 
						for _, a := range annotations {
 | 
				
			||||||
		frame := &data.Frame{
 | 
							frame.AppendRow(a.Time, a.Title, a.Tags, a.Text)
 | 
				
			||||||
			RefID: timeSeriesQuery.getRefID(),
 | 
					 | 
				
			||||||
			Fields: []*data.Field{
 | 
					 | 
				
			||||||
				data.NewField("time", nil, a["time"]),
 | 
					 | 
				
			||||||
				data.NewField("title", nil, a["title"]),
 | 
					 | 
				
			||||||
				data.NewField("tags", nil, a["tags"]),
 | 
					 | 
				
			||||||
				data.NewField("text", nil, a["text"]),
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			Meta: &data.FrameMeta{
 | 
					 | 
				
			||||||
				Custom: map[string]interface{}{
 | 
					 | 
				
			||||||
					"rowCount": len(a),
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		frames = append(frames, frame)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	result.Frames = frames
 | 
						result.Frames = append(result.Frames, frame)
 | 
				
			||||||
	slog.Info("anno", "len", len(annotations))
 | 
						slog.Info("anno", "len", len(annotations))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,10 +20,15 @@ func TestExecutor_parseToAnnotations(t *testing.T) {
 | 
				
			||||||
		"atext {{resource.label.zone}}")
 | 
							"atext {{resource.label.zone}}")
 | 
				
			||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	require.Len(t, res.Frames, 3)
 | 
						require.Len(t, res.Frames, 1)
 | 
				
			||||||
 | 
						assert.Equal(t, "time", res.Frames[0].Fields[0].Name)
 | 
				
			||||||
	assert.Equal(t, "title", res.Frames[0].Fields[1].Name)
 | 
						assert.Equal(t, "title", res.Frames[0].Fields[1].Name)
 | 
				
			||||||
	assert.Equal(t, "tags", res.Frames[0].Fields[2].Name)
 | 
						assert.Equal(t, "tags", res.Frames[0].Fields[2].Name)
 | 
				
			||||||
	assert.Equal(t, "text", res.Frames[0].Fields[3].Name)
 | 
						assert.Equal(t, "text", res.Frames[0].Fields[3].Name)
 | 
				
			||||||
 | 
						assert.Equal(t, 9, res.Frames[0].Fields[0].Len())
 | 
				
			||||||
 | 
						assert.Equal(t, 9, res.Frames[0].Fields[1].Len())
 | 
				
			||||||
 | 
						assert.Equal(t, 9, res.Frames[0].Fields[2].Len())
 | 
				
			||||||
 | 
						assert.Equal(t, 9, res.Frames[0].Fields[3].Len())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestCloudMonitoringExecutor_parseToAnnotations_emptyTimeSeries(t *testing.T) {
 | 
					func TestCloudMonitoringExecutor_parseToAnnotations_emptyTimeSeries(t *testing.T) {
 | 
				
			||||||
| 
						 | 
					@ -37,7 +42,15 @@ func TestCloudMonitoringExecutor_parseToAnnotations_emptyTimeSeries(t *testing.T
 | 
				
			||||||
	err := query.parseToAnnotations(res, response, "atitle", "atext")
 | 
						err := query.parseToAnnotations(res, response, "atitle", "atext")
 | 
				
			||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	require.Len(t, res.Frames, 0)
 | 
						require.Len(t, res.Frames, 1)
 | 
				
			||||||
 | 
						assert.Equal(t, "time", res.Frames[0].Fields[0].Name)
 | 
				
			||||||
 | 
						assert.Equal(t, "title", res.Frames[0].Fields[1].Name)
 | 
				
			||||||
 | 
						assert.Equal(t, "tags", res.Frames[0].Fields[2].Name)
 | 
				
			||||||
 | 
						assert.Equal(t, "text", res.Frames[0].Fields[3].Name)
 | 
				
			||||||
 | 
						assert.Equal(t, 0, res.Frames[0].Fields[0].Len())
 | 
				
			||||||
 | 
						assert.Equal(t, 0, res.Frames[0].Fields[1].Len())
 | 
				
			||||||
 | 
						assert.Equal(t, 0, res.Frames[0].Fields[2].Len())
 | 
				
			||||||
 | 
						assert.Equal(t, 0, res.Frames[0].Fields[3].Len())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestCloudMonitoringExecutor_parseToAnnotations_noPointsInSeries(t *testing.T) {
 | 
					func TestCloudMonitoringExecutor_parseToAnnotations_noPointsInSeries(t *testing.T) {
 | 
				
			||||||
| 
						 | 
					@ -53,5 +66,13 @@ func TestCloudMonitoringExecutor_parseToAnnotations_noPointsInSeries(t *testing.
 | 
				
			||||||
	err := query.parseToAnnotations(res, response, "atitle", "atext")
 | 
						err := query.parseToAnnotations(res, response, "atitle", "atext")
 | 
				
			||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	require.Len(t, res.Frames, 0)
 | 
						require.Len(t, res.Frames, 1)
 | 
				
			||||||
 | 
						assert.Equal(t, "time", res.Frames[0].Fields[0].Name)
 | 
				
			||||||
 | 
						assert.Equal(t, "title", res.Frames[0].Fields[1].Name)
 | 
				
			||||||
 | 
						assert.Equal(t, "tags", res.Frames[0].Fields[2].Name)
 | 
				
			||||||
 | 
						assert.Equal(t, "text", res.Frames[0].Fields[3].Name)
 | 
				
			||||||
 | 
						assert.Equal(t, 0, res.Frames[0].Fields[0].Len())
 | 
				
			||||||
 | 
						assert.Equal(t, 0, res.Frames[0].Fields[1].Len())
 | 
				
			||||||
 | 
						assert.Equal(t, 0, res.Frames[0].Fields[2].Len())
 | 
				
			||||||
 | 
						assert.Equal(t, 0, res.Frames[0].Fields[3].Len())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -250,33 +250,36 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) handleNonDistributionSe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) parseToAnnotations(dr *backend.DataResponse,
 | 
					func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) parseToAnnotations(dr *backend.DataResponse,
 | 
				
			||||||
	response cloudMonitoringResponse, title, text string) error {
 | 
						response cloudMonitoringResponse, title, text string) error {
 | 
				
			||||||
	frames := data.Frames{}
 | 
						frame := data.NewFrame(timeSeriesFilter.RefID,
 | 
				
			||||||
 | 
							data.NewField("time", nil, []time.Time{}),
 | 
				
			||||||
 | 
							data.NewField("title", nil, []string{}),
 | 
				
			||||||
 | 
							data.NewField("tags", nil, []string{}),
 | 
				
			||||||
 | 
							data.NewField("text", nil, []string{}),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, series := range response.TimeSeries {
 | 
						for _, series := range response.TimeSeries {
 | 
				
			||||||
		if len(series.Points) == 0 {
 | 
							if len(series.Points) == 0 {
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		annotation := make(map[string][]string)
 | 
					
 | 
				
			||||||
		for i := len(series.Points) - 1; i >= 0; i-- {
 | 
							for i := len(series.Points) - 1; i >= 0; i-- {
 | 
				
			||||||
			point := series.Points[i]
 | 
								point := series.Points[i]
 | 
				
			||||||
			value := strconv.FormatFloat(point.Value.DoubleValue, 'f', 6, 64)
 | 
								value := strconv.FormatFloat(point.Value.DoubleValue, 'f', 6, 64)
 | 
				
			||||||
			if series.ValueType == "STRING" {
 | 
								if series.ValueType == "STRING" {
 | 
				
			||||||
				value = point.Value.StringValue
 | 
									value = point.Value.StringValue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			annotation["time"] = append(annotation["time"], point.Interval.EndTime.UTC().Format(time.RFC3339))
 | 
								annotation := &annotationEvent{
 | 
				
			||||||
			annotation["title"] = append(annotation["title"], formatAnnotationText(title, value, series.Metric.Type,
 | 
									Time: point.Interval.EndTime,
 | 
				
			||||||
				series.Metric.Labels, series.Resource.Labels))
 | 
									Title: formatAnnotationText(title, value, series.Metric.Type,
 | 
				
			||||||
			annotation["tags"] = append(annotation["tags"], "")
 | 
										series.Metric.Labels, series.Resource.Labels),
 | 
				
			||||||
			annotation["text"] = append(annotation["text"], formatAnnotationText(text, value, series.Metric.Type,
 | 
									Tags: "",
 | 
				
			||||||
				series.Metric.Labels, series.Resource.Labels))
 | 
									Text: formatAnnotationText(text, value, series.Metric.Type,
 | 
				
			||||||
 | 
										series.Metric.Labels, series.Resource.Labels),
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								frame.AppendRow(annotation.Time, annotation.Title, annotation.Tags, annotation.Text)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		frames = append(frames, data.NewFrame(timeSeriesFilter.getRefID(),
 | 
					 | 
				
			||||||
			data.NewField("time", nil, annotation["time"]),
 | 
					 | 
				
			||||||
			data.NewField("title", nil, annotation["title"]),
 | 
					 | 
				
			||||||
			data.NewField("tags", nil, annotation["tags"]),
 | 
					 | 
				
			||||||
			data.NewField("text", nil, annotation["text"]),
 | 
					 | 
				
			||||||
		))
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	dr.Frames = frames
 | 
						dr.Frames = append(dr.Frames, frame)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -266,7 +266,7 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *ba
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseToAnnotations(queryRes *backend.DataResponse,
 | 
					func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseToAnnotations(queryRes *backend.DataResponse,
 | 
				
			||||||
	data cloudMonitoringResponse, title, text string) error {
 | 
						data cloudMonitoringResponse, title, text string) error {
 | 
				
			||||||
	annotations := make([]map[string]string, 0)
 | 
						annotations := make([]*annotationEvent, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, series := range data.TimeSeriesData {
 | 
						for _, series := range data.TimeSeriesData {
 | 
				
			||||||
		metricLabels := make(map[string]string)
 | 
							metricLabels := make(map[string]string)
 | 
				
			||||||
| 
						 | 
					@ -302,12 +302,12 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseToAnnotations(queryRe
 | 
				
			||||||
				if d.ValueType == "STRING" {
 | 
									if d.ValueType == "STRING" {
 | 
				
			||||||
					value = point.Values[n].StringValue
 | 
										value = point.Values[n].StringValue
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				annotation := make(map[string]string)
 | 
									annotations = append(annotations, &annotationEvent{
 | 
				
			||||||
				annotation["time"] = point.TimeInterval.EndTime.UTC().Format(time.RFC3339)
 | 
										Time:  point.TimeInterval.EndTime,
 | 
				
			||||||
				annotation["title"] = formatAnnotationText(title, value, d.MetricKind, metricLabels, resourceLabels)
 | 
										Title: formatAnnotationText(title, value, d.MetricKind, metricLabels, resourceLabels),
 | 
				
			||||||
				annotation["tags"] = ""
 | 
										Tags:  "",
 | 
				
			||||||
				annotation["text"] = formatAnnotationText(text, value, d.MetricKind, metricLabels, resourceLabels)
 | 
										Text:  formatAnnotationText(text, value, d.MetricKind, metricLabels, resourceLabels),
 | 
				
			||||||
				annotations = append(annotations, annotation)
 | 
									})
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,6 @@ import {
 | 
				
			||||||
import { react2AngularDirective } from 'app/angular/react2angular';
 | 
					import { react2AngularDirective } from 'app/angular/react2angular';
 | 
				
			||||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
 | 
					import { FolderPicker } from 'app/core/components/Select/FolderPicker';
 | 
				
			||||||
import { TimePickerSettings } from 'app/features/dashboard/components/DashboardSettings/TimePickerSettings';
 | 
					import { TimePickerSettings } from 'app/features/dashboard/components/DashboardSettings/TimePickerSettings';
 | 
				
			||||||
import { AnnotationQueryEditor as CloudMonitoringAnnotationQueryEditor } from 'app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor';
 | 
					 | 
				
			||||||
import { QueryEditor as CloudMonitoringQueryEditor } from 'app/plugins/datasource/cloud-monitoring/components/QueryEditor';
 | 
					import { QueryEditor as CloudMonitoringQueryEditor } from 'app/plugins/datasource/cloud-monitoring/components/QueryEditor';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import EmptyListCTA from '../core/components/EmptyListCTA/EmptyListCTA';
 | 
					import EmptyListCTA from '../core/components/EmptyListCTA/EmptyListCTA';
 | 
				
			||||||
| 
						 | 
					@ -117,12 +116,6 @@ export function registerAngularDirectives() {
 | 
				
			||||||
    ['datasource', { watchDepth: 'reference' }],
 | 
					    ['datasource', { watchDepth: 'reference' }],
 | 
				
			||||||
    ['templateSrv', { watchDepth: 'reference' }],
 | 
					    ['templateSrv', { watchDepth: 'reference' }],
 | 
				
			||||||
  ]);
 | 
					  ]);
 | 
				
			||||||
  react2AngularDirective('cloudMonitoringAnnotationQueryEditor', CloudMonitoringAnnotationQueryEditor, [
 | 
					 | 
				
			||||||
    'target',
 | 
					 | 
				
			||||||
    'onQueryChange',
 | 
					 | 
				
			||||||
    ['datasource', { watchDepth: 'reference' }],
 | 
					 | 
				
			||||||
    ['templateSrv', { watchDepth: 'reference' }],
 | 
					 | 
				
			||||||
  ]);
 | 
					 | 
				
			||||||
  react2AngularDirective('secretFormField', SecretFormField, [
 | 
					  react2AngularDirective('secretFormField', SecretFormField, [
 | 
				
			||||||
    'value',
 | 
					    'value',
 | 
				
			||||||
    'isConfigured',
 | 
					    'isConfigured',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					import Datasource from '../datasource';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const createMockDatasource = () => {
 | 
				
			||||||
 | 
					  const datasource: Partial<Datasource> = {
 | 
				
			||||||
 | 
					    intervalMs: 0,
 | 
				
			||||||
 | 
					    getVariables: jest.fn().mockReturnValue([]),
 | 
				
			||||||
 | 
					    getMetricTypes: jest.fn().mockResolvedValue([]),
 | 
				
			||||||
 | 
					    getProjects: jest.fn().mockResolvedValue([]),
 | 
				
			||||||
 | 
					    getDefaultProject: jest.fn().mockReturnValue('cloud-monitoring-default-project'),
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return jest.mocked(datasource as Datasource, true);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					import { CloudMonitoringQuery, EditorMode, MetricQuery, QueryType } from '../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const createMockMetricQuery: () => MetricQuery = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    editorMode: EditorMode.Visual,
 | 
				
			||||||
 | 
					    metricType: '',
 | 
				
			||||||
 | 
					    crossSeriesReducer: 'REDUCE_NONE',
 | 
				
			||||||
 | 
					    query: '',
 | 
				
			||||||
 | 
					    projectName: 'cloud-monitoring-default-project',
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const createMockQuery: () => CloudMonitoringQuery = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    refId: 'cloudMonitoringRefId',
 | 
				
			||||||
 | 
					    queryType: QueryType.METRICS,
 | 
				
			||||||
 | 
					    intervalMs: 0,
 | 
				
			||||||
 | 
					    type: 'timeSeriesQuery',
 | 
				
			||||||
 | 
					    metricQuery: createMockMetricQuery(),
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,114 @@
 | 
				
			||||||
 | 
					import { AnnotationQuery } from '@grafana/data';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { createMockDatasource } from './__mocks__/cloudMonitoringDatasource';
 | 
				
			||||||
 | 
					import { CloudMonitoringAnnotationSupport } from './annotationSupport';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  AlignmentTypes,
 | 
				
			||||||
 | 
					  CloudMonitoringQuery,
 | 
				
			||||||
 | 
					  EditorMode,
 | 
				
			||||||
 | 
					  LegacyCloudMonitoringAnnotationQuery,
 | 
				
			||||||
 | 
					  MetricKind,
 | 
				
			||||||
 | 
					  QueryType,
 | 
				
			||||||
 | 
					} from './types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const query: CloudMonitoringQuery = {
 | 
				
			||||||
 | 
					  refId: 'query',
 | 
				
			||||||
 | 
					  queryType: QueryType.METRICS,
 | 
				
			||||||
 | 
					  type: 'annotationQuery',
 | 
				
			||||||
 | 
					  intervalMs: 0,
 | 
				
			||||||
 | 
					  metricQuery: {
 | 
				
			||||||
 | 
					    editorMode: EditorMode.Visual,
 | 
				
			||||||
 | 
					    projectName: 'project-name',
 | 
				
			||||||
 | 
					    metricType: '',
 | 
				
			||||||
 | 
					    filters: [],
 | 
				
			||||||
 | 
					    metricKind: MetricKind.GAUGE,
 | 
				
			||||||
 | 
					    valueType: '',
 | 
				
			||||||
 | 
					    title: '',
 | 
				
			||||||
 | 
					    text: '',
 | 
				
			||||||
 | 
					    query: '',
 | 
				
			||||||
 | 
					    crossSeriesReducer: 'REDUCE_NONE',
 | 
				
			||||||
 | 
					    perSeriesAligner: AlignmentTypes.ALIGN_NONE,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const legacyQuery: LegacyCloudMonitoringAnnotationQuery = {
 | 
				
			||||||
 | 
					  projectName: 'project-name',
 | 
				
			||||||
 | 
					  metricType: 'metric-type',
 | 
				
			||||||
 | 
					  filters: ['filter1', 'filter2'],
 | 
				
			||||||
 | 
					  metricKind: MetricKind.CUMULATIVE,
 | 
				
			||||||
 | 
					  valueType: 'value-type',
 | 
				
			||||||
 | 
					  refId: 'annotationQuery',
 | 
				
			||||||
 | 
					  title: 'title',
 | 
				
			||||||
 | 
					  text: 'text',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const annotationQuery: AnnotationQuery<CloudMonitoringQuery> = {
 | 
				
			||||||
 | 
					  name: 'Anno',
 | 
				
			||||||
 | 
					  enable: false,
 | 
				
			||||||
 | 
					  iconColor: '',
 | 
				
			||||||
 | 
					  target: query,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const legacyAnnotationQuery: AnnotationQuery<LegacyCloudMonitoringAnnotationQuery> = {
 | 
				
			||||||
 | 
					  name: 'Anno',
 | 
				
			||||||
 | 
					  enable: false,
 | 
				
			||||||
 | 
					  iconColor: '',
 | 
				
			||||||
 | 
					  target: legacyQuery,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ds = createMockDatasource();
 | 
				
			||||||
 | 
					const annotationSupport = CloudMonitoringAnnotationSupport(ds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('CloudMonitoringAnnotationSupport', () => {
 | 
				
			||||||
 | 
					  describe('prepareAnnotation', () => {
 | 
				
			||||||
 | 
					    it('returns query if it is already a Cloud Monitoring annotation query', () => {
 | 
				
			||||||
 | 
					      expect(annotationSupport.prepareAnnotation?.(annotationQuery)).toBe(annotationQuery);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('returns an updated query if it is a legacy Cloud Monitoring annotation query', () => {
 | 
				
			||||||
 | 
					      const expectedQuery = {
 | 
				
			||||||
 | 
					        datasource: undefined,
 | 
				
			||||||
 | 
					        enable: false,
 | 
				
			||||||
 | 
					        iconColor: '',
 | 
				
			||||||
 | 
					        name: 'Anno',
 | 
				
			||||||
 | 
					        target: {
 | 
				
			||||||
 | 
					          intervalMs: 0,
 | 
				
			||||||
 | 
					          metricQuery: {
 | 
				
			||||||
 | 
					            crossSeriesReducer: 'REDUCE_NONE',
 | 
				
			||||||
 | 
					            editorMode: 'visual',
 | 
				
			||||||
 | 
					            filters: ['filter1', 'filter2'],
 | 
				
			||||||
 | 
					            metricKind: 'CUMULATIVE',
 | 
				
			||||||
 | 
					            metricType: 'metric-type',
 | 
				
			||||||
 | 
					            perSeriesAligner: 'ALIGN_NONE',
 | 
				
			||||||
 | 
					            projectName: 'project-name',
 | 
				
			||||||
 | 
					            query: '',
 | 
				
			||||||
 | 
					            text: 'text',
 | 
				
			||||||
 | 
					            title: 'title',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          queryType: 'metrics',
 | 
				
			||||||
 | 
					          refId: 'annotationQuery',
 | 
				
			||||||
 | 
					          type: 'annotationQuery',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      expect(annotationSupport.prepareAnnotation?.(legacyAnnotationQuery)).toEqual(expectedQuery);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('prepareQuery', () => {
 | 
				
			||||||
 | 
					    it('should ensure queryType is set to "metrics"', () => {
 | 
				
			||||||
 | 
					      const queryWithoutMetricsQueryType = { ...annotationQuery, queryType: 'blah' };
 | 
				
			||||||
 | 
					      expect(annotationSupport.prepareQuery?.(queryWithoutMetricsQueryType)).toEqual(
 | 
				
			||||||
 | 
					        expect.objectContaining({ queryType: 'metrics' })
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should ensure type is set "annotationQuery"', () => {
 | 
				
			||||||
 | 
					      const queryWithoutAnnotationQueryType = { ...annotationQuery, type: 'blah' };
 | 
				
			||||||
 | 
					      expect(annotationSupport.prepareQuery?.(queryWithoutAnnotationQueryType)).toEqual(
 | 
				
			||||||
 | 
					        expect.objectContaining({ type: 'annotationQuery' })
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return undefined if there is no query', () => {
 | 
				
			||||||
 | 
					      const queryWithUndefinedTarget = { ...annotationQuery, target: undefined };
 | 
				
			||||||
 | 
					      expect(annotationSupport.prepareQuery?.(queryWithUndefinedTarget)).toBeUndefined();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,78 @@
 | 
				
			||||||
 | 
					import { AnnotationSupport, AnnotationQuery } from '@grafana/data';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { AnnotationQueryEditor } from './components/AnnotationQueryEditor';
 | 
				
			||||||
 | 
					import CloudMonitoringDatasource from './datasource';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  AlignmentTypes,
 | 
				
			||||||
 | 
					  CloudMonitoringQuery,
 | 
				
			||||||
 | 
					  EditorMode,
 | 
				
			||||||
 | 
					  LegacyCloudMonitoringAnnotationQuery,
 | 
				
			||||||
 | 
					  MetricKind,
 | 
				
			||||||
 | 
					  QueryType,
 | 
				
			||||||
 | 
					} from './types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The legacy query format sets the title and text values to empty strings by default.
 | 
				
			||||||
 | 
					// If the title or text is not undefined at the top-level of the annotation target,
 | 
				
			||||||
 | 
					// then it is a legacy query.
 | 
				
			||||||
 | 
					const isLegacyCloudMonitoringAnnotation = (
 | 
				
			||||||
 | 
					  query: unknown
 | 
				
			||||||
 | 
					): query is AnnotationQuery<LegacyCloudMonitoringAnnotationQuery> =>
 | 
				
			||||||
 | 
					  (query as AnnotationQuery<LegacyCloudMonitoringAnnotationQuery>).target?.title !== undefined ||
 | 
				
			||||||
 | 
					  (query as AnnotationQuery<LegacyCloudMonitoringAnnotationQuery>).target?.text !== undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const CloudMonitoringAnnotationSupport: (
 | 
				
			||||||
 | 
					  ds: CloudMonitoringDatasource
 | 
				
			||||||
 | 
					) => AnnotationSupport<CloudMonitoringQuery> = (ds: CloudMonitoringDatasource) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    prepareAnnotation: (
 | 
				
			||||||
 | 
					      query: AnnotationQuery<LegacyCloudMonitoringAnnotationQuery> | AnnotationQuery<CloudMonitoringQuery>
 | 
				
			||||||
 | 
					    ): AnnotationQuery<CloudMonitoringQuery> => {
 | 
				
			||||||
 | 
					      if (!isLegacyCloudMonitoringAnnotation(query)) {
 | 
				
			||||||
 | 
					        return query;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const { enable, name, iconColor } = query;
 | 
				
			||||||
 | 
					      const { target } = query;
 | 
				
			||||||
 | 
					      const result: AnnotationQuery<CloudMonitoringQuery> = {
 | 
				
			||||||
 | 
					        datasource: query.datasource,
 | 
				
			||||||
 | 
					        enable,
 | 
				
			||||||
 | 
					        name,
 | 
				
			||||||
 | 
					        iconColor,
 | 
				
			||||||
 | 
					        target: {
 | 
				
			||||||
 | 
					          intervalMs: ds.intervalMs,
 | 
				
			||||||
 | 
					          refId: target?.refId || 'annotationQuery',
 | 
				
			||||||
 | 
					          type: 'annotationQuery',
 | 
				
			||||||
 | 
					          queryType: QueryType.METRICS,
 | 
				
			||||||
 | 
					          metricQuery: {
 | 
				
			||||||
 | 
					            projectName: target?.projectName || ds.getDefaultProject(),
 | 
				
			||||||
 | 
					            editorMode: EditorMode.Visual,
 | 
				
			||||||
 | 
					            metricType: target?.metricType || '',
 | 
				
			||||||
 | 
					            filters: target?.filters || [],
 | 
				
			||||||
 | 
					            metricKind: target?.metricKind || MetricKind.GAUGE,
 | 
				
			||||||
 | 
					            query: '',
 | 
				
			||||||
 | 
					            crossSeriesReducer: 'REDUCE_NONE',
 | 
				
			||||||
 | 
					            perSeriesAligner: AlignmentTypes.ALIGN_NONE,
 | 
				
			||||||
 | 
					            title: target?.title || '',
 | 
				
			||||||
 | 
					            text: target?.text || '',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      return result;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    prepareQuery: (anno: AnnotationQuery<CloudMonitoringQuery>) => {
 | 
				
			||||||
 | 
					      if (!anno.target) {
 | 
				
			||||||
 | 
					        return undefined;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...anno.target,
 | 
				
			||||||
 | 
					        queryType: QueryType.METRICS,
 | 
				
			||||||
 | 
					        type: 'annotationQuery',
 | 
				
			||||||
 | 
					        metricQuery: {
 | 
				
			||||||
 | 
					          ...anno.target.metricQuery,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    QueryEditor: AnnotationQueryEditor,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -1,18 +0,0 @@
 | 
				
			||||||
import { AnnotationTarget } from './types';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class CloudMonitoringAnnotationsQueryCtrl {
 | 
					 | 
				
			||||||
  static templateUrl = 'partials/annotations.editor.html';
 | 
					 | 
				
			||||||
  declare annotation: any;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /** @ngInject */
 | 
					 | 
				
			||||||
  constructor($scope: any) {
 | 
					 | 
				
			||||||
    this.annotation = $scope.ctrl.annotation || {};
 | 
					 | 
				
			||||||
    this.annotation.target = $scope.ctrl.annotation.target || {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.onQueryChange = this.onQueryChange.bind(this);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onQueryChange(target: AnnotationTarget) {
 | 
					 | 
				
			||||||
    Object.assign(this.annotation.target, target);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					import { render, screen } from '@testing-library/react';
 | 
				
			||||||
 | 
					import userEvent from '@testing-library/user-event';
 | 
				
			||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { createMockDatasource } from '../__mocks__/cloudMonitoringDatasource';
 | 
				
			||||||
 | 
					import { createMockQuery } from '../__mocks__/cloudMonitoringQuery';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { AnnotationQueryEditor } from './AnnotationQueryEditor';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('AnnotationQueryEditor', () => {
 | 
				
			||||||
 | 
					  it('renders correctly', async () => {
 | 
				
			||||||
 | 
					    const onChange = jest.fn();
 | 
				
			||||||
 | 
					    const onRunQuery = jest.fn();
 | 
				
			||||||
 | 
					    const datasource = createMockDatasource();
 | 
				
			||||||
 | 
					    const query = createMockQuery();
 | 
				
			||||||
 | 
					    render(<AnnotationQueryEditor onChange={onChange} onRunQuery={onRunQuery} query={query} datasource={datasource} />);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(await screen.findByLabelText('Project')).toBeInTheDocument();
 | 
				
			||||||
 | 
					    expect(await screen.findByLabelText('Service')).toBeInTheDocument();
 | 
				
			||||||
 | 
					    expect(await screen.findByLabelText('Metric name')).toBeInTheDocument();
 | 
				
			||||||
 | 
					    expect(await screen.findByLabelText('Group by')).toBeInTheDocument();
 | 
				
			||||||
 | 
					    expect(await screen.findByLabelText('Group by function')).toBeInTheDocument();
 | 
				
			||||||
 | 
					    expect(await screen.findByLabelText('Alignment function')).toBeInTheDocument();
 | 
				
			||||||
 | 
					    expect(await screen.findByLabelText('Alignment period')).toBeInTheDocument();
 | 
				
			||||||
 | 
					    expect(await screen.findByLabelText('Alias by')).toBeInTheDocument();
 | 
				
			||||||
 | 
					    expect(await screen.findByLabelText('Title')).toBeInTheDocument();
 | 
				
			||||||
 | 
					    expect(await screen.findByLabelText('Text')).toBeInTheDocument();
 | 
				
			||||||
 | 
					    expect(await screen.findByText('Annotation Query Format')).toBeInTheDocument();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('can set the title', async () => {
 | 
				
			||||||
 | 
					    const onChange = jest.fn();
 | 
				
			||||||
 | 
					    const onRunQuery = jest.fn();
 | 
				
			||||||
 | 
					    const datasource = createMockDatasource();
 | 
				
			||||||
 | 
					    const query = createMockQuery();
 | 
				
			||||||
 | 
					    render(<AnnotationQueryEditor onChange={onChange} onRunQuery={onRunQuery} query={query} datasource={datasource} />);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const title = 'user-title';
 | 
				
			||||||
 | 
					    await userEvent.type(screen.getByLabelText('Title'), title);
 | 
				
			||||||
 | 
					    expect(await screen.findByDisplayValue(title)).toBeInTheDocument();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('can set the text', async () => {
 | 
				
			||||||
 | 
					    const onChange = jest.fn();
 | 
				
			||||||
 | 
					    const onRunQuery = jest.fn();
 | 
				
			||||||
 | 
					    const datasource = createMockDatasource();
 | 
				
			||||||
 | 
					    const query = createMockQuery();
 | 
				
			||||||
 | 
					    render(<AnnotationQueryEditor onChange={onChange} onRunQuery={onRunQuery} query={query} datasource={datasource} />);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const text = 'user-text';
 | 
				
			||||||
 | 
					    await userEvent.type(screen.getByLabelText('Text'), text);
 | 
				
			||||||
 | 
					    expect(await screen.findByDisplayValue(text)).toBeInTheDocument();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -1,34 +1,29 @@
 | 
				
			||||||
import React from 'react';
 | 
					import React, { useState } from 'react';
 | 
				
			||||||
 | 
					import { useDebounce } from 'react-use';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { SelectableValue, toOption } from '@grafana/data';
 | 
					import { QueryEditorProps, toOption } from '@grafana/data';
 | 
				
			||||||
import { TemplateSrv } from '@grafana/runtime';
 | 
					import { Input } from '@grafana/ui';
 | 
				
			||||||
import { LegacyForms } from '@grafana/ui';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { INPUT_WIDTH } from '../constants';
 | 
				
			||||||
import CloudMonitoringDatasource from '../datasource';
 | 
					import CloudMonitoringDatasource from '../datasource';
 | 
				
			||||||
import { AnnotationTarget, EditorMode, MetricDescriptor, MetricKind } from '../types';
 | 
					import {
 | 
				
			||||||
 | 
					  EditorMode,
 | 
				
			||||||
 | 
					  MetricKind,
 | 
				
			||||||
 | 
					  AnnotationMetricQuery,
 | 
				
			||||||
 | 
					  CloudMonitoringOptions,
 | 
				
			||||||
 | 
					  CloudMonitoringQuery,
 | 
				
			||||||
 | 
					  AlignmentTypes,
 | 
				
			||||||
 | 
					} from '../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { AnnotationsHelp, LabelFilter, Metrics, Project, QueryEditorRow } from './';
 | 
					import { MetricQueryEditor } from './MetricQueryEditor';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { Input } = LegacyForms;
 | 
					import { AnnotationsHelp, QueryEditorRow } from './';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Props {
 | 
					export type Props = QueryEditorProps<CloudMonitoringDatasource, CloudMonitoringQuery, CloudMonitoringOptions>;
 | 
				
			||||||
  refId: string;
 | 
					 | 
				
			||||||
  onQueryChange: (target: AnnotationTarget) => void;
 | 
					 | 
				
			||||||
  target: AnnotationTarget;
 | 
					 | 
				
			||||||
  datasource: CloudMonitoringDatasource;
 | 
					 | 
				
			||||||
  templateSrv: TemplateSrv;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface State extends AnnotationTarget {
 | 
					export const defaultQuery: (datasource: CloudMonitoringDatasource) => AnnotationMetricQuery = (datasource) => ({
 | 
				
			||||||
  variableOptionGroup: SelectableValue<string>;
 | 
					 | 
				
			||||||
  variableOptions: Array<SelectableValue<string>>;
 | 
					 | 
				
			||||||
  labels: any;
 | 
					 | 
				
			||||||
  [key: string]: any;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const DefaultTarget: State = {
 | 
					 | 
				
			||||||
  editorMode: EditorMode.Visual,
 | 
					  editorMode: EditorMode.Visual,
 | 
				
			||||||
  projectName: '',
 | 
					  projectName: datasource.getDefaultProject(),
 | 
				
			||||||
  projects: [],
 | 
					  projects: [],
 | 
				
			||||||
  metricType: '',
 | 
					  metricType: '',
 | 
				
			||||||
  filters: [],
 | 
					  filters: [],
 | 
				
			||||||
| 
						 | 
					@ -40,112 +35,68 @@ const DefaultTarget: State = {
 | 
				
			||||||
  labels: {},
 | 
					  labels: {},
 | 
				
			||||||
  variableOptionGroup: {},
 | 
					  variableOptionGroup: {},
 | 
				
			||||||
  variableOptions: [],
 | 
					  variableOptions: [],
 | 
				
			||||||
};
 | 
					  query: '',
 | 
				
			||||||
 | 
					  crossSeriesReducer: 'REDUCE_NONE',
 | 
				
			||||||
 | 
					  perSeriesAligner: AlignmentTypes.ALIGN_NONE,
 | 
				
			||||||
 | 
					  alignmentPeriod: 'grafana-auto',
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class AnnotationQueryEditor extends React.Component<Props, State> {
 | 
					export const AnnotationQueryEditor = (props: Props) => {
 | 
				
			||||||
  state: State = DefaultTarget;
 | 
					  const { datasource, query, onRunQuery, data, onChange } = props;
 | 
				
			||||||
 | 
					  const meta = data?.series.length ? data?.series[0].meta : {};
 | 
				
			||||||
  async UNSAFE_componentWillMount() {
 | 
					  const customMetaData = meta?.custom ?? {};
 | 
				
			||||||
    // Unfortunately, migrations like this need to go UNSAFE_componentWillMount. As soon as there's
 | 
					  const metricQuery = { ...defaultQuery(datasource), ...query.metricQuery };
 | 
				
			||||||
    // migration hook for this module.ts, we can do the migrations there instead.
 | 
					  const [title, setTitle] = useState(metricQuery.title || '');
 | 
				
			||||||
    const { target, datasource } = this.props;
 | 
					  const [text, setText] = useState(metricQuery.text || '');
 | 
				
			||||||
    if (!target.projectName) {
 | 
					  const variableOptionGroup = {
 | 
				
			||||||
      target.projectName = datasource.getDefaultProject();
 | 
					    label: 'Template Variables',
 | 
				
			||||||
    }
 | 
					    options: datasource.getVariables().map(toOption),
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const variableOptionGroup = {
 | 
					 | 
				
			||||||
      label: 'Template Variables',
 | 
					 | 
				
			||||||
      options: datasource.getVariables().map(toOption),
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const projects = await datasource.getProjects();
 | 
					 | 
				
			||||||
    this.setState({
 | 
					 | 
				
			||||||
      variableOptionGroup,
 | 
					 | 
				
			||||||
      variableOptions: variableOptionGroup.options,
 | 
					 | 
				
			||||||
      ...target,
 | 
					 | 
				
			||||||
      projects,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    datasource
 | 
					 | 
				
			||||||
      .getLabels(target.metricType, target.projectName, target.refId)
 | 
					 | 
				
			||||||
      .then((labels) => this.setState({ labels }));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onMetricTypeChange = ({ valueType, metricKind, type, unit }: MetricDescriptor) => {
 | 
					 | 
				
			||||||
    const { onQueryChange, datasource } = this.props;
 | 
					 | 
				
			||||||
    this.setState(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        metricType: type,
 | 
					 | 
				
			||||||
        unit,
 | 
					 | 
				
			||||||
        valueType,
 | 
					 | 
				
			||||||
        metricKind,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      () => {
 | 
					 | 
				
			||||||
        onQueryChange(this.state);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    datasource.getLabels(type, this.state.refId, this.state.projectName).then((labels) => this.setState({ labels }));
 | 
					 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onChange(prop: string, value: string | string[]) {
 | 
					  const handleQueryChange = (metricQuery: AnnotationMetricQuery) => onChange({ ...query, metricQuery });
 | 
				
			||||||
    this.setState({ [prop]: value }, () => {
 | 
					  const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
 | 
				
			||||||
      this.props.onQueryChange(this.state);
 | 
					    setTitle(e.target.value);
 | 
				
			||||||
    });
 | 
					  };
 | 
				
			||||||
  }
 | 
					  const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
 | 
				
			||||||
 | 
					    setText(e.target.value);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render() {
 | 
					  useDebounce(
 | 
				
			||||||
    const { metricType, projectName, filters, title, text, variableOptionGroup, labels, variableOptions } = this.state;
 | 
					    () => {
 | 
				
			||||||
    const { datasource } = this.props;
 | 
					      onChange({ ...query, metricQuery: { ...metricQuery, title } });
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    1000,
 | 
				
			||||||
 | 
					    [title, onChange]
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  useDebounce(
 | 
				
			||||||
 | 
					    () => {
 | 
				
			||||||
 | 
					      onChange({ ...query, metricQuery: { ...metricQuery, text } });
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    1000,
 | 
				
			||||||
 | 
					    [text, onChange]
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					  return (
 | 
				
			||||||
      <>
 | 
					    <>
 | 
				
			||||||
        <Project
 | 
					      <MetricQueryEditor
 | 
				
			||||||
          refId={this.props.refId}
 | 
					        refId={query.refId}
 | 
				
			||||||
          templateVariableOptions={variableOptions}
 | 
					        variableOptionGroup={variableOptionGroup}
 | 
				
			||||||
          datasource={datasource}
 | 
					        customMetaData={customMetaData}
 | 
				
			||||||
          projectName={projectName || datasource.getDefaultProject()}
 | 
					        onChange={handleQueryChange}
 | 
				
			||||||
          onChange={(value) => this.onChange('projectName', value)}
 | 
					        onRunQuery={onRunQuery}
 | 
				
			||||||
        />
 | 
					        datasource={datasource}
 | 
				
			||||||
        <Metrics
 | 
					        query={metricQuery}
 | 
				
			||||||
          refId={this.props.refId}
 | 
					      />
 | 
				
			||||||
          projectName={projectName}
 | 
					 | 
				
			||||||
          metricType={metricType}
 | 
					 | 
				
			||||||
          templateSrv={datasource.templateSrv}
 | 
					 | 
				
			||||||
          datasource={datasource}
 | 
					 | 
				
			||||||
          templateVariableOptions={variableOptions}
 | 
					 | 
				
			||||||
          onChange={(metric) => this.onMetricTypeChange(metric)}
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          {(metric) => (
 | 
					 | 
				
			||||||
            <>
 | 
					 | 
				
			||||||
              <LabelFilter
 | 
					 | 
				
			||||||
                labels={labels}
 | 
					 | 
				
			||||||
                filters={filters}
 | 
					 | 
				
			||||||
                onChange={(value) => this.onChange('filters', value)}
 | 
					 | 
				
			||||||
                variableOptionGroup={variableOptionGroup}
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
            </>
 | 
					 | 
				
			||||||
          )}
 | 
					 | 
				
			||||||
        </Metrics>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <QueryEditorRow label="Title">
 | 
					      <QueryEditorRow label="Title" htmlFor="annotation-query-title">
 | 
				
			||||||
          <Input
 | 
					        <Input id="annotation-query-title" value={title} width={INPUT_WIDTH} onChange={handleTitleChange} />
 | 
				
			||||||
            type="text"
 | 
					      </QueryEditorRow>
 | 
				
			||||||
            className="gf-form-input width-20"
 | 
					 | 
				
			||||||
            value={title}
 | 
					 | 
				
			||||||
            onChange={(e) => this.onChange('title', e.target.value)}
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        </QueryEditorRow>
 | 
					 | 
				
			||||||
        <QueryEditorRow label="Text">
 | 
					 | 
				
			||||||
          <Input
 | 
					 | 
				
			||||||
            type="text"
 | 
					 | 
				
			||||||
            className="gf-form-input width-20"
 | 
					 | 
				
			||||||
            value={text}
 | 
					 | 
				
			||||||
            onChange={(e) => this.onChange('text', e.target.value)}
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        </QueryEditorRow>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <AnnotationsHelp />
 | 
					      <QueryEditorRow label="Text" htmlFor="annotation-query-text">
 | 
				
			||||||
      </>
 | 
					        <Input id="annotation-query-text" value={text} width={INPUT_WIDTH} onChange={handleTextChange} />
 | 
				
			||||||
    );
 | 
					      </QueryEditorRow>
 | 
				
			||||||
  }
 | 
					
 | 
				
			||||||
}
 | 
					      <AnnotationsHelp />
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,20 @@ export const AUTH_TYPES = [
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const ALIGNMENTS = [
 | 
					export const ALIGNMENTS = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    text: 'none',
 | 
				
			||||||
 | 
					    value: 'ALIGN_NONE',
 | 
				
			||||||
 | 
					    valueTypes: [
 | 
				
			||||||
 | 
					      ValueTypes.INT64,
 | 
				
			||||||
 | 
					      ValueTypes.DOUBLE,
 | 
				
			||||||
 | 
					      ValueTypes.MONEY,
 | 
				
			||||||
 | 
					      ValueTypes.DISTRIBUTION,
 | 
				
			||||||
 | 
					      ValueTypes.STRING,
 | 
				
			||||||
 | 
					      ValueTypes.VALUE_TYPE_UNSPECIFIED,
 | 
				
			||||||
 | 
					      ValueTypes.BOOL,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    metricKinds: [MetricKind.GAUGE],
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    text: 'delta',
 | 
					    text: 'delta',
 | 
				
			||||||
    value: 'ALIGN_DELTA',
 | 
					    value: 'ALIGN_DELTA',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,7 @@ import { DataSourceWithBackend, getBackendSrv, toDataQueryResponse } from '@graf
 | 
				
			||||||
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
 | 
					import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
 | 
				
			||||||
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
 | 
					import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { CloudMonitoringAnnotationSupport } from './annotationSupport';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  CloudMonitoringOptions,
 | 
					  CloudMonitoringOptions,
 | 
				
			||||||
  CloudMonitoringQuery,
 | 
					  CloudMonitoringQuery,
 | 
				
			||||||
| 
						 | 
					@ -41,6 +42,7 @@ export default class CloudMonitoringDatasource extends DataSourceWithBackend<
 | 
				
			||||||
    this.authenticationType = instanceSettings.jsonData.authenticationType || 'jwt';
 | 
					    this.authenticationType = instanceSettings.jsonData.authenticationType || 'jwt';
 | 
				
			||||||
    this.variables = new CloudMonitoringVariableSupport(this);
 | 
					    this.variables = new CloudMonitoringVariableSupport(this);
 | 
				
			||||||
    this.intervalMs = 0;
 | 
					    this.intervalMs = 0;
 | 
				
			||||||
 | 
					    this.annotations = CloudMonitoringAnnotationSupport(this);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getVariables() {
 | 
					  getVariables() {
 | 
				
			||||||
| 
						 | 
					@ -55,73 +57,15 @@ export default class CloudMonitoringDatasource extends DataSourceWithBackend<
 | 
				
			||||||
    return super.query(request);
 | 
					    return super.query(request);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async annotationQuery(options: any) {
 | 
					 | 
				
			||||||
    await this.ensureGCEDefaultProject();
 | 
					 | 
				
			||||||
    const annotation = options.annotation;
 | 
					 | 
				
			||||||
    const queries = [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        refId: 'annotationQuery',
 | 
					 | 
				
			||||||
        type: 'annotationQuery',
 | 
					 | 
				
			||||||
        datasource: this.getRef(),
 | 
					 | 
				
			||||||
        view: 'FULL',
 | 
					 | 
				
			||||||
        crossSeriesReducer: 'REDUCE_NONE',
 | 
					 | 
				
			||||||
        perSeriesAligner: 'ALIGN_NONE',
 | 
					 | 
				
			||||||
        metricType: this.templateSrv.replace(annotation.target.metricType, options.scopedVars || {}),
 | 
					 | 
				
			||||||
        title: this.templateSrv.replace(annotation.target.title, options.scopedVars || {}),
 | 
					 | 
				
			||||||
        text: this.templateSrv.replace(annotation.target.text, options.scopedVars || {}),
 | 
					 | 
				
			||||||
        projectName: this.templateSrv.replace(
 | 
					 | 
				
			||||||
          annotation.target.projectName ? annotation.target.projectName : this.getDefaultProject(),
 | 
					 | 
				
			||||||
          options.scopedVars || {}
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        filters: this.interpolateFilters(annotation.target.filters || [], options.scopedVars),
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return lastValueFrom(
 | 
					 | 
				
			||||||
      getBackendSrv()
 | 
					 | 
				
			||||||
        .fetch<PostResponse>({
 | 
					 | 
				
			||||||
          url: '/api/ds/query',
 | 
					 | 
				
			||||||
          method: 'POST',
 | 
					 | 
				
			||||||
          data: {
 | 
					 | 
				
			||||||
            from: options.range.from.valueOf().toString(),
 | 
					 | 
				
			||||||
            to: options.range.to.valueOf().toString(),
 | 
					 | 
				
			||||||
            queries,
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .pipe(
 | 
					 | 
				
			||||||
          map(({ data }) => {
 | 
					 | 
				
			||||||
            const dataQueryResponse = toDataQueryResponse({
 | 
					 | 
				
			||||||
              data: data,
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            const df: any = [];
 | 
					 | 
				
			||||||
            if (dataQueryResponse.data.length !== 0) {
 | 
					 | 
				
			||||||
              for (let i = 0; i < dataQueryResponse.data.length; i++) {
 | 
					 | 
				
			||||||
                for (let j = 0; j < dataQueryResponse.data[i].fields[0].values.length; j++) {
 | 
					 | 
				
			||||||
                  df.push({
 | 
					 | 
				
			||||||
                    annotation: annotation,
 | 
					 | 
				
			||||||
                    time: Date.parse(dataQueryResponse.data[i].fields[0].values.get(j)),
 | 
					 | 
				
			||||||
                    title: dataQueryResponse.data[i].fields[1].values.get(j),
 | 
					 | 
				
			||||||
                    tags: [],
 | 
					 | 
				
			||||||
                    text: dataQueryResponse.data[i].fields[3].values.get(j),
 | 
					 | 
				
			||||||
                  });
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return df;
 | 
					 | 
				
			||||||
          })
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  applyTemplateVariables(
 | 
					  applyTemplateVariables(
 | 
				
			||||||
    { metricQuery, refId, queryType, sloQuery }: CloudMonitoringQuery,
 | 
					    { metricQuery, refId, queryType, sloQuery, type = 'timeSeriesQuery' }: CloudMonitoringQuery,
 | 
				
			||||||
    scopedVars: ScopedVars
 | 
					    scopedVars: ScopedVars
 | 
				
			||||||
  ): Record<string, any> {
 | 
					  ): Record<string, any> {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      datasource: this.getRef(),
 | 
					      datasource: this.getRef(),
 | 
				
			||||||
      refId,
 | 
					      refId,
 | 
				
			||||||
      intervalMs: this.intervalMs,
 | 
					      intervalMs: this.intervalMs,
 | 
				
			||||||
      type: 'timeSeriesQuery',
 | 
					      type,
 | 
				
			||||||
      queryType,
 | 
					      queryType,
 | 
				
			||||||
      metricQuery: {
 | 
					      metricQuery: {
 | 
				
			||||||
        ...this.interpolateProps(metricQuery, scopedVars),
 | 
					        ...this.interpolateProps(metricQuery, scopedVars),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -127,7 +127,7 @@ describe('functions', () => {
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it('should return all alignment options except two', () => {
 | 
					      it('should return all alignment options except two', () => {
 | 
				
			||||||
        expect(result.length).toBe(9);
 | 
					        expect(result.length).toBe(10);
 | 
				
			||||||
        expect(result.map((o: any) => o.value)).toEqual(
 | 
					        expect(result.map((o: any) => o.value)).toEqual(
 | 
				
			||||||
          expect.not.arrayContaining(['REDUCE_COUNT_TRUE', 'REDUCE_COUNT_FALSE'])
 | 
					          expect.not.arrayContaining(['REDUCE_COUNT_TRUE', 'REDUCE_COUNT_FALSE'])
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
| 
						 | 
					@ -173,7 +173,7 @@ describe('functions', () => {
 | 
				
			||||||
  describe('getAlignmentPickerData', () => {
 | 
					  describe('getAlignmentPickerData', () => {
 | 
				
			||||||
    it('should return default data', () => {
 | 
					    it('should return default data', () => {
 | 
				
			||||||
      const res = getAlignmentPickerData();
 | 
					      const res = getAlignmentPickerData();
 | 
				
			||||||
      expect(res.alignOptions).toHaveLength(9);
 | 
					      expect(res.alignOptions).toHaveLength(10);
 | 
				
			||||||
      expect(res.perSeriesAligner).toEqual(AlignmentTypes.ALIGN_MEAN);
 | 
					      expect(res.perSeriesAligner).toEqual(AlignmentTypes.ALIGN_MEAN);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,5 @@
 | 
				
			||||||
import { DataSourcePlugin } from '@grafana/data';
 | 
					import { DataSourcePlugin } from '@grafana/data';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { CloudMonitoringAnnotationsQueryCtrl } from './annotations_query_ctrl';
 | 
					 | 
				
			||||||
import CloudMonitoringCheatSheet from './components/CloudMonitoringCheatSheet';
 | 
					import CloudMonitoringCheatSheet from './components/CloudMonitoringCheatSheet';
 | 
				
			||||||
import { ConfigEditor } from './components/ConfigEditor/ConfigEditor';
 | 
					import { ConfigEditor } from './components/ConfigEditor/ConfigEditor';
 | 
				
			||||||
import { QueryEditor } from './components/QueryEditor';
 | 
					import { QueryEditor } from './components/QueryEditor';
 | 
				
			||||||
| 
						 | 
					@ -12,5 +11,4 @@ export const plugin = new DataSourcePlugin<CloudMonitoringDatasource, CloudMonit
 | 
				
			||||||
  .setQueryEditorHelp(CloudMonitoringCheatSheet)
 | 
					  .setQueryEditorHelp(CloudMonitoringCheatSheet)
 | 
				
			||||||
  .setQueryEditor(QueryEditor)
 | 
					  .setQueryEditor(QueryEditor)
 | 
				
			||||||
  .setConfigEditor(ConfigEditor)
 | 
					  .setConfigEditor(ConfigEditor)
 | 
				
			||||||
  .setAnnotationQueryCtrl(CloudMonitoringAnnotationsQueryCtrl)
 | 
					 | 
				
			||||||
  .setVariableQueryEditor(CloudMonitoringVariableQueryEditor);
 | 
					  .setVariableQueryEditor(CloudMonitoringVariableQueryEditor);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +0,0 @@
 | 
				
			||||||
<cloud-monitoring-annotation-query-editor
 | 
					 | 
				
			||||||
  target="ctrl.annotation.target"
 | 
					 | 
				
			||||||
  on-query-change="(ctrl.onQueryChange)"
 | 
					 | 
				
			||||||
  datasource="ctrl.datasource"
 | 
					 | 
				
			||||||
  template-srv="ctrl.templateSrv"
 | 
					 | 
				
			||||||
></cloud-monitoring-annotation-query-editor>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -106,6 +106,7 @@ export enum AlignmentTypes {
 | 
				
			||||||
  ALIGN_PERCENTILE_50 = 'ALIGN_PERCENTILE_50',
 | 
					  ALIGN_PERCENTILE_50 = 'ALIGN_PERCENTILE_50',
 | 
				
			||||||
  ALIGN_PERCENTILE_05 = 'ALIGN_PERCENTILE_05',
 | 
					  ALIGN_PERCENTILE_05 = 'ALIGN_PERCENTILE_05',
 | 
				
			||||||
  ALIGN_PERCENT_CHANGE = 'ALIGN_PERCENT_CHANGE',
 | 
					  ALIGN_PERCENT_CHANGE = 'ALIGN_PERCENT_CHANGE',
 | 
				
			||||||
 | 
					  ALIGN_NONE = 'ALIGN_NONE',
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface BaseQuery {
 | 
					export interface BaseQuery {
 | 
				
			||||||
| 
						 | 
					@ -130,6 +131,11 @@ export interface MetricQuery extends BaseQuery {
 | 
				
			||||||
  graphPeriod?: 'disabled' | string;
 | 
					  graphPeriod?: 'disabled' | string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AnnotationMetricQuery extends MetricQuery {
 | 
				
			||||||
 | 
					  title?: string;
 | 
				
			||||||
 | 
					  text?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface SLOQuery extends BaseQuery {
 | 
					export interface SLOQuery extends BaseQuery {
 | 
				
			||||||
  selectorName: string;
 | 
					  selectorName: string;
 | 
				
			||||||
  serviceId: string;
 | 
					  serviceId: string;
 | 
				
			||||||
| 
						 | 
					@ -142,7 +148,7 @@ export interface SLOQuery extends BaseQuery {
 | 
				
			||||||
export interface CloudMonitoringQuery extends DataQuery {
 | 
					export interface CloudMonitoringQuery extends DataQuery {
 | 
				
			||||||
  datasourceId?: number; // Should not be necessary anymore
 | 
					  datasourceId?: number; // Should not be necessary anymore
 | 
				
			||||||
  queryType: QueryType;
 | 
					  queryType: QueryType;
 | 
				
			||||||
  metricQuery: MetricQuery;
 | 
					  metricQuery: MetricQuery | AnnotationMetricQuery;
 | 
				
			||||||
  sloQuery?: SLOQuery;
 | 
					  sloQuery?: SLOQuery;
 | 
				
			||||||
  intervalMs: number;
 | 
					  intervalMs: number;
 | 
				
			||||||
  type: string;
 | 
					  type: string;
 | 
				
			||||||
| 
						 | 
					@ -160,7 +166,7 @@ export interface CloudMonitoringSecureJsonData {
 | 
				
			||||||
  privateKey?: string;
 | 
					  privateKey?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface AnnotationTarget {
 | 
					export interface LegacyCloudMonitoringAnnotationQuery {
 | 
				
			||||||
  projectName: string;
 | 
					  projectName: string;
 | 
				
			||||||
  metricType: string;
 | 
					  metricType: string;
 | 
				
			||||||
  refId: string;
 | 
					  refId: string;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue