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:
Erik Sundell 2022-05-16 12:15:54 +02:00 committed by GitHub
parent fa37c6c9d3
commit 3106af9eec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 144 additions and 36 deletions

View File

@ -24,6 +24,7 @@ type cloudWatchQuery struct {
Label string
MatchExact bool
UsedExpression string
TimezoneUTCOffset string
MetricQueryType metricQueryType
MetricEditorMode metricEditorMode
}

View File

@ -5,6 +5,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"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,
@ -14,6 +15,15 @@ func (e *cloudWatchExecutor) buildMetricDataInput(startTime time.Time, endTime t
EndTime: aws.Time(endTime),
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 {
metricDataQuery, err := e.buildMetricDataQuery(query)
if err != nil {

View 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)
})
}
}

View File

@ -201,6 +201,8 @@ func parseRequestQuery(model *simplejson.Json, refId string, startTime time.Time
label := model.Get("label").MustString()
returnData := !model.Get("hide").MustBool(false)
queryType := model.Get("type").MustString()
timezoneUTCOffset := model.Get("timezoneUTCOffset").MustString("")
if queryType == "" {
// 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
@ -238,6 +240,7 @@ func parseRequestQuery(model *simplejson.Json, refId string, startTime time.Time
MetricQueryType: metricQueryType,
MetricEditorMode: metricEditorModeValue,
SqlExpression: sqlExpression,
TimezoneUTCOffset: timezoneUTCOffset,
}, nil
}

View File

@ -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', () => {
const ds = setupMockedDataSource({ variables: [labelsVariable, dimensionVariable], mockGetVariableName: false });
it('converts keys and values correctly', () => {

View File

@ -12,6 +12,7 @@ import {
DataSourceInstanceSettings,
DataSourceWithLogsContextSupport,
dateMath,
dateTimeFormat,
FieldType,
LoadingState,
LogRowModel,
@ -22,6 +23,7 @@ import {
import { DataSourceWithBackend, FetchError, getBackendSrv, toDataQueryResponse } from '@grafana/runtime';
import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider';
import { notifyApp } from 'app/core/actions';
import { config } from 'app/core/config';
import { createErrorNotification } from 'app/core/copy/appNotification';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
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 { AppNotificationTimeout } from 'app/types';
import config from '../../../core/config';
import { CloudWatchAnnotationSupport } from './annotationSupport';
import { SQLCompletionItemProvider } from './cloudwatch-sql/completion/CompletionItemProvider';
import { ThrottlingErrorMessage } from './components/ThrottlingErrorMessage';
@ -290,11 +290,17 @@ export class CloudWatchDatasource
metricQueries: CloudWatchMetricsQuery[],
options: DataQueryRequest<CloudWatchQuery>
): Observable<DataQueryResponse> => {
const timezoneUTCOffset = dateTimeFormat(Date.now(), {
timeZone: options.timezone,
format: 'Z',
}).replace(':', '');
const validMetricsQueries = metricQueries.filter(this.filterQuery).map((q: CloudWatchMetricsQuery): MetricQuery => {
const migratedQuery = migrateMetricQuery(q);
const migratedAndIterpolatedQuery = this.replaceMetricQueryVars(migratedQuery, options);
return {
timezoneUTCOffset,
intervalMs: options.intervalMs,
maxDataPoints: options.maxDataPoints,
...migratedAndIterpolatedQuery,