mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Cloudwatch: Set time zone offset in GMD request (#48772)
* set timezone offset in case feature is enabled * add unit tests * add unit tests * remove unused import
This commit is contained in:
parent
fa37c6c9d3
commit
3106af9eec
@ -9,23 +9,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type cloudWatchQuery struct {
|
type cloudWatchQuery struct {
|
||||||
RefId string
|
RefId string
|
||||||
Region string
|
Region string
|
||||||
Id string
|
Id string
|
||||||
Namespace string
|
Namespace string
|
||||||
MetricName string
|
MetricName string
|
||||||
Statistic string
|
Statistic string
|
||||||
Expression string
|
Expression string
|
||||||
SqlExpression string
|
SqlExpression string
|
||||||
ReturnData bool
|
ReturnData bool
|
||||||
Dimensions map[string][]string
|
Dimensions map[string][]string
|
||||||
Period int
|
Period int
|
||||||
Alias string
|
Alias string
|
||||||
Label string
|
Label string
|
||||||
MatchExact bool
|
MatchExact bool
|
||||||
UsedExpression string
|
UsedExpression string
|
||||||
MetricQueryType metricQueryType
|
TimezoneUTCOffset string
|
||||||
MetricEditorMode metricEditorMode
|
MetricQueryType metricQueryType
|
||||||
|
MetricEditorMode metricEditorMode
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *cloudWatchQuery) getGMDAPIMode() gmdApiMode {
|
func (q *cloudWatchQuery) getGMDAPIMode() gmdApiMode {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e *cloudWatchExecutor) buildMetricDataInput(startTime time.Time, endTime time.Time,
|
func (e *cloudWatchExecutor) buildMetricDataInput(startTime time.Time, endTime time.Time,
|
||||||
@ -14,6 +15,15 @@ func (e *cloudWatchExecutor) buildMetricDataInput(startTime time.Time, endTime t
|
|||||||
EndTime: aws.Time(endTime),
|
EndTime: aws.Time(endTime),
|
||||||
ScanBy: aws.String("TimestampAscending"),
|
ScanBy: aws.String("TimestampAscending"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shouldSetLabelOptions := e.features.IsEnabled(featuremgmt.FlagCloudWatchDynamicLabels) && len(queries) > 0 && len(queries[0].TimezoneUTCOffset) > 0
|
||||||
|
|
||||||
|
if shouldSetLabelOptions {
|
||||||
|
metricDataInput.LabelOptions = &cloudwatch.LabelOptions{
|
||||||
|
Timezone: aws.String(queries[0].TimezoneUTCOffset),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, query := range queries {
|
for _, query := range queries {
|
||||||
metricDataQuery, err := e.buildMetricDataQuery(query)
|
metricDataQuery, err := e.buildMetricDataQuery(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
44
pkg/tsdb/cloudwatch/metric_data_input_builder_test.go
Normal file
44
pkg/tsdb/cloudwatch/metric_data_input_builder_test.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package cloudwatch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMetricDataInputBuilder(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
timezoneUTCOffset string
|
||||||
|
expectedLabelOptions *cloudwatch.LabelOptions
|
||||||
|
featureEnabled bool
|
||||||
|
}{
|
||||||
|
{name: "when timezoneUTCOffset is provided and feature is enabled", timezoneUTCOffset: "+1234", expectedLabelOptions: &cloudwatch.LabelOptions{Timezone: aws.String("+1234")}, featureEnabled: true},
|
||||||
|
{name: "when timezoneUTCOffset is not provided and feature is enabled", timezoneUTCOffset: "", expectedLabelOptions: nil, featureEnabled: true},
|
||||||
|
{name: "when timezoneUTCOffset is provided and feature is disabled", timezoneUTCOffset: "+1234", expectedLabelOptions: nil, featureEnabled: false},
|
||||||
|
{name: "when timezoneUTCOffset is not provided and feature is disabled", timezoneUTCOffset: "", expectedLabelOptions: nil, featureEnabled: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
executor := newExecutor(nil, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures(featuremgmt.FlagCloudWatchDynamicLabels, tc.featureEnabled))
|
||||||
|
query := getBaseQuery()
|
||||||
|
query.TimezoneUTCOffset = tc.timezoneUTCOffset
|
||||||
|
|
||||||
|
from := now.Add(time.Hour * -2)
|
||||||
|
to := now.Add(time.Hour * -1)
|
||||||
|
mdi, err := executor.buildMetricDataInput(from, to, []*cloudWatchQuery{query})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
require.NotNil(t, mdi)
|
||||||
|
assert.Equal(t, tc.expectedLabelOptions, mdi.LabelOptions)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -201,6 +201,8 @@ func parseRequestQuery(model *simplejson.Json, refId string, startTime time.Time
|
|||||||
label := model.Get("label").MustString()
|
label := model.Get("label").MustString()
|
||||||
returnData := !model.Get("hide").MustBool(false)
|
returnData := !model.Get("hide").MustBool(false)
|
||||||
queryType := model.Get("type").MustString()
|
queryType := model.Get("type").MustString()
|
||||||
|
timezoneUTCOffset := model.Get("timezoneUTCOffset").MustString("")
|
||||||
|
|
||||||
if queryType == "" {
|
if queryType == "" {
|
||||||
// If no type is provided we assume we are called by alerting service, which requires to return data!
|
// If no type is provided we assume we are called by alerting service, which requires to return data!
|
||||||
// Note, this is sort of a hack, but the official Grafana interfaces do not carry the information
|
// Note, this is sort of a hack, but the official Grafana interfaces do not carry the information
|
||||||
@ -221,23 +223,24 @@ func parseRequestQuery(model *simplejson.Json, refId string, startTime time.Time
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &cloudWatchQuery{
|
return &cloudWatchQuery{
|
||||||
RefId: refId,
|
RefId: refId,
|
||||||
Region: region,
|
Region: region,
|
||||||
Id: id,
|
Id: id,
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
MetricName: metricName,
|
MetricName: metricName,
|
||||||
Statistic: statistic,
|
Statistic: statistic,
|
||||||
Expression: expression,
|
Expression: expression,
|
||||||
ReturnData: returnData,
|
ReturnData: returnData,
|
||||||
Dimensions: dimensions,
|
Dimensions: dimensions,
|
||||||
Period: period,
|
Period: period,
|
||||||
Alias: alias,
|
Alias: alias,
|
||||||
Label: label,
|
Label: label,
|
||||||
MatchExact: matchExact,
|
MatchExact: matchExact,
|
||||||
UsedExpression: "",
|
UsedExpression: "",
|
||||||
MetricQueryType: metricQueryType,
|
MetricQueryType: metricQueryType,
|
||||||
MetricEditorMode: metricEditorModeValue,
|
MetricEditorMode: metricEditorModeValue,
|
||||||
SqlExpression: sqlExpression,
|
SqlExpression: sqlExpression,
|
||||||
|
TimezoneUTCOffset: timezoneUTCOffset,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,6 +399,50 @@ describe('datasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('timezoneUTCOffset', () => {
|
||||||
|
const testQuery = {
|
||||||
|
id: '',
|
||||||
|
refId: 'a',
|
||||||
|
region: 'us-east-2',
|
||||||
|
namespace: '',
|
||||||
|
period: '',
|
||||||
|
label: '${MAX_TIME_RELATIVE}',
|
||||||
|
metricName: '',
|
||||||
|
dimensions: {},
|
||||||
|
matchExact: true,
|
||||||
|
statistic: '',
|
||||||
|
expression: '',
|
||||||
|
metricQueryType: MetricQueryType.Query,
|
||||||
|
metricEditorMode: MetricEditorMode.Code,
|
||||||
|
sqlExpression: 'SELECT SUM($metric) FROM "$namespace" GROUP BY ${labels:raw} LIMIT $limit',
|
||||||
|
};
|
||||||
|
const testTable = [
|
||||||
|
['Europe/Stockholm', '+0200'],
|
||||||
|
['America/New_York', '-0400'],
|
||||||
|
['Asia/Tokyo', '+0900'],
|
||||||
|
['UTC', '+0000'],
|
||||||
|
];
|
||||||
|
describe.each(testTable)('should use the right time zone offset', (ianaTimezone, expectedOffset) => {
|
||||||
|
const { datasource, fetchMock } = setupMockedDataSource();
|
||||||
|
datasource.handleMetricQueries([testQuery], {
|
||||||
|
range: { from: dateTime(), to: dateTime() },
|
||||||
|
timezone: ianaTimezone,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(fetchMock).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({
|
||||||
|
queries: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
timezoneUTCOffset: expectedOffset,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('convertMultiFiltersFormat', () => {
|
describe('convertMultiFiltersFormat', () => {
|
||||||
const ds = setupMockedDataSource({ variables: [labelsVariable, dimensionVariable], mockGetVariableName: false });
|
const ds = setupMockedDataSource({ variables: [labelsVariable, dimensionVariable], mockGetVariableName: false });
|
||||||
it('converts keys and values correctly', () => {
|
it('converts keys and values correctly', () => {
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
DataSourceInstanceSettings,
|
DataSourceInstanceSettings,
|
||||||
DataSourceWithLogsContextSupport,
|
DataSourceWithLogsContextSupport,
|
||||||
dateMath,
|
dateMath,
|
||||||
|
dateTimeFormat,
|
||||||
FieldType,
|
FieldType,
|
||||||
LoadingState,
|
LoadingState,
|
||||||
LogRowModel,
|
LogRowModel,
|
||||||
@ -22,6 +23,7 @@ import {
|
|||||||
import { DataSourceWithBackend, FetchError, getBackendSrv, toDataQueryResponse } from '@grafana/runtime';
|
import { DataSourceWithBackend, FetchError, getBackendSrv, toDataQueryResponse } from '@grafana/runtime';
|
||||||
import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider';
|
import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider';
|
||||||
import { notifyApp } from 'app/core/actions';
|
import { notifyApp } from 'app/core/actions';
|
||||||
|
import { config } from 'app/core/config';
|
||||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||||
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';
|
||||||
@ -29,8 +31,6 @@ import { VariableWithMultiSupport } from 'app/features/variables/types';
|
|||||||
import { store } from 'app/store/store';
|
import { store } from 'app/store/store';
|
||||||
import { AppNotificationTimeout } from 'app/types';
|
import { AppNotificationTimeout } from 'app/types';
|
||||||
|
|
||||||
import config from '../../../core/config';
|
|
||||||
|
|
||||||
import { CloudWatchAnnotationSupport } from './annotationSupport';
|
import { CloudWatchAnnotationSupport } from './annotationSupport';
|
||||||
import { SQLCompletionItemProvider } from './cloudwatch-sql/completion/CompletionItemProvider';
|
import { SQLCompletionItemProvider } from './cloudwatch-sql/completion/CompletionItemProvider';
|
||||||
import { ThrottlingErrorMessage } from './components/ThrottlingErrorMessage';
|
import { ThrottlingErrorMessage } from './components/ThrottlingErrorMessage';
|
||||||
@ -290,11 +290,17 @@ export class CloudWatchDatasource
|
|||||||
metricQueries: CloudWatchMetricsQuery[],
|
metricQueries: CloudWatchMetricsQuery[],
|
||||||
options: DataQueryRequest<CloudWatchQuery>
|
options: DataQueryRequest<CloudWatchQuery>
|
||||||
): Observable<DataQueryResponse> => {
|
): Observable<DataQueryResponse> => {
|
||||||
|
const timezoneUTCOffset = dateTimeFormat(Date.now(), {
|
||||||
|
timeZone: options.timezone,
|
||||||
|
format: 'Z',
|
||||||
|
}).replace(':', '');
|
||||||
|
|
||||||
const validMetricsQueries = metricQueries.filter(this.filterQuery).map((q: CloudWatchMetricsQuery): MetricQuery => {
|
const validMetricsQueries = metricQueries.filter(this.filterQuery).map((q: CloudWatchMetricsQuery): MetricQuery => {
|
||||||
const migratedQuery = migrateMetricQuery(q);
|
const migratedQuery = migrateMetricQuery(q);
|
||||||
const migratedAndIterpolatedQuery = this.replaceMetricQueryVars(migratedQuery, options);
|
const migratedAndIterpolatedQuery = this.replaceMetricQueryVars(migratedQuery, options);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
timezoneUTCOffset,
|
||||||
intervalMs: options.intervalMs,
|
intervalMs: options.intervalMs,
|
||||||
maxDataPoints: options.maxDataPoints,
|
maxDataPoints: options.maxDataPoints,
|
||||||
...migratedAndIterpolatedQuery,
|
...migratedAndIterpolatedQuery,
|
||||||
|
Loading…
Reference in New Issue
Block a user