mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Cloudwatch: Use new annotation API (#48102)
* use new annotation api * fix bad merge
This commit is contained in:
parent
25e153e4e7
commit
f58a2d879e
@ -12,6 +12,13 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
type annotationEvent struct {
|
||||
Title string
|
||||
Time time.Time
|
||||
Tags string
|
||||
Text string
|
||||
}
|
||||
|
||||
func (e *cloudWatchExecutor) executeAnnotationQuery(pluginCtx backend.PluginContext, model *simplejson.Json, query backend.DataQuery) (*backend.QueryDataResponse, error) {
|
||||
result := backend.NewQueryDataResponse()
|
||||
|
||||
@ -79,7 +86,7 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(pluginCtx backend.PluginCont
|
||||
}
|
||||
}
|
||||
|
||||
annotations := make([]map[string]string, 0)
|
||||
annotations := make([]*annotationEvent, 0)
|
||||
for _, alarmName := range alarmNames {
|
||||
params := &cloudwatch.DescribeAlarmHistoryInput{
|
||||
AlarmName: alarmName,
|
||||
@ -92,12 +99,12 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(pluginCtx backend.PluginCont
|
||||
return nil, errutil.Wrap("failed to call cloudwatch:DescribeAlarmHistory", err)
|
||||
}
|
||||
for _, history := range resp.AlarmHistoryItems {
|
||||
annotation := make(map[string]string)
|
||||
annotation["time"] = history.Timestamp.UTC().Format(time.RFC3339)
|
||||
annotation["title"] = *history.AlarmName
|
||||
annotation["tags"] = *history.HistoryItemType
|
||||
annotation["text"] = *history.HistorySummary
|
||||
annotations = append(annotations, annotation)
|
||||
annotations = append(annotations, &annotationEvent{
|
||||
Time: *history.Timestamp,
|
||||
Title: *history.AlarmName,
|
||||
Tags: *history.HistoryItemType,
|
||||
Text: *history.HistorySummary,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,16 +115,16 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(pluginCtx backend.PluginCont
|
||||
return result, err
|
||||
}
|
||||
|
||||
func transformAnnotationToTable(annotations []map[string]string, query backend.DataQuery) *data.Frame {
|
||||
func transformAnnotationToTable(annotations []*annotationEvent, query backend.DataQuery) *data.Frame {
|
||||
frame := data.NewFrame(query.RefID,
|
||||
data.NewField("time", nil, []string{}),
|
||||
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 {
|
||||
frame.AppendRow(a["time"], a["title"], a["tags"], a["text"])
|
||||
frame.AppendRow(a.Time, a.Title, a.Tags, a.Text)
|
||||
}
|
||||
|
||||
frame.Meta = &data.FrameMeta{
|
||||
|
@ -123,7 +123,6 @@ export function registerAngularDirectives() {
|
||||
['datasource', { watchDepth: 'reference' }],
|
||||
['templateSrv', { watchDepth: 'reference' }],
|
||||
]);
|
||||
|
||||
react2AngularDirective('secretFormField', SecretFormField, [
|
||||
'value',
|
||||
'isConfigured',
|
||||
|
@ -45,7 +45,7 @@ import {
|
||||
migrateMultipleStatsAnnotationQuery,
|
||||
migrateMultipleStatsMetricsQuery,
|
||||
} from 'app/plugins/datasource/cloudwatch/migrations';
|
||||
import { CloudWatchAnnotationQuery, CloudWatchMetricsQuery } from 'app/plugins/datasource/cloudwatch/types';
|
||||
import { CloudWatchMetricsQuery, LegacyAnnotationQuery } from 'app/plugins/datasource/cloudwatch/types';
|
||||
import { plugin as gaugePanelPlugin } from 'app/plugins/panel/gauge/module';
|
||||
import { plugin as statPanelPlugin } from 'app/plugins/panel/stat/module';
|
||||
|
||||
@ -1170,7 +1170,9 @@ function isCloudWatchQuery(target: DataQuery): target is CloudWatchMetricsQuery
|
||||
);
|
||||
}
|
||||
|
||||
function isLegacyCloudWatchAnnotationQuery(target: AnnotationQuery<DataQuery>): target is CloudWatchAnnotationQuery {
|
||||
function isLegacyCloudWatchAnnotationQuery(
|
||||
target: AnnotationQuery<DataQuery>
|
||||
): target is AnnotationQuery<LegacyAnnotationQuery> {
|
||||
return (
|
||||
target.hasOwnProperty('dimensions') &&
|
||||
target.hasOwnProperty('namespace') &&
|
||||
|
@ -0,0 +1,167 @@
|
||||
import { AnnotationQuery } from '@grafana/data';
|
||||
|
||||
import { CloudWatchAnnotationSupport } from './annotationSupport';
|
||||
import { CloudWatchAnnotationQuery, LegacyAnnotationQuery } from './types';
|
||||
|
||||
const metricStatAnnotationQuery: CloudWatchAnnotationQuery = {
|
||||
queryMode: 'Annotations',
|
||||
region: 'us-east-2',
|
||||
namespace: 'AWS/EC2',
|
||||
period: '300',
|
||||
metricName: 'CPUUtilization',
|
||||
dimensions: { InstanceId: 'i-123' },
|
||||
matchExact: true,
|
||||
statistic: 'Average',
|
||||
refId: 'anno',
|
||||
prefixMatching: false,
|
||||
actionPrefix: '',
|
||||
alarmNamePrefix: '',
|
||||
};
|
||||
|
||||
const prefixMatchingAnnotationQuery: CloudWatchAnnotationQuery = {
|
||||
queryMode: 'Annotations',
|
||||
region: 'us-east-2',
|
||||
namespace: '',
|
||||
period: '300',
|
||||
metricName: '',
|
||||
dimensions: undefined,
|
||||
statistic: 'Average',
|
||||
refId: 'anno',
|
||||
prefixMatching: true,
|
||||
actionPrefix: 'arn',
|
||||
alarmNamePrefix: 'test-alarm',
|
||||
};
|
||||
|
||||
const annotationQuery: AnnotationQuery<CloudWatchAnnotationQuery> = {
|
||||
name: 'Anno',
|
||||
enable: false,
|
||||
iconColor: '',
|
||||
target: metricStatAnnotationQuery!,
|
||||
};
|
||||
|
||||
const legacyAnnotationQuery: LegacyAnnotationQuery = {
|
||||
name: 'Anno',
|
||||
enable: false,
|
||||
iconColor: '',
|
||||
region: '',
|
||||
namespace: 'AWS/EC2',
|
||||
period: '300',
|
||||
metricName: 'CPUUtilization',
|
||||
dimensions: { InstanceId: 'i-123' },
|
||||
matchExact: true,
|
||||
statistic: '',
|
||||
refId: '',
|
||||
prefixMatching: false,
|
||||
actionPrefix: '',
|
||||
alarmNamePrefix: '',
|
||||
target: {
|
||||
limit: 0,
|
||||
matchAny: false,
|
||||
tags: [],
|
||||
type: '',
|
||||
},
|
||||
alias: '',
|
||||
builtIn: 0,
|
||||
datasource: undefined,
|
||||
expression: '',
|
||||
hide: false,
|
||||
id: '',
|
||||
type: '',
|
||||
statistics: [],
|
||||
};
|
||||
|
||||
describe('annotationSupport', () => {
|
||||
describe('when prepareAnnotation', () => {
|
||||
describe('is being called with new style annotations', () => {
|
||||
it('should return the same query without changing it', () => {
|
||||
const preparedAnnotation = CloudWatchAnnotationSupport.prepareAnnotation(annotationQuery);
|
||||
expect(preparedAnnotation).toEqual(annotationQuery);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is being called with legacy annotations', () => {
|
||||
it('should return a new query', () => {
|
||||
const preparedAnnotation = CloudWatchAnnotationSupport.prepareAnnotation(legacyAnnotationQuery);
|
||||
expect(preparedAnnotation).not.toEqual(annotationQuery);
|
||||
});
|
||||
|
||||
it('should set default values if not given', () => {
|
||||
const preparedAnnotation = CloudWatchAnnotationSupport.prepareAnnotation(legacyAnnotationQuery);
|
||||
expect(preparedAnnotation.target?.statistic).toEqual('Average');
|
||||
expect(preparedAnnotation.target?.region).toEqual('default');
|
||||
expect(preparedAnnotation.target?.queryMode).toEqual('Annotations');
|
||||
expect(preparedAnnotation.target?.refId).toEqual('annotationQuery');
|
||||
});
|
||||
|
||||
it('should not set default values if given', () => {
|
||||
const annotation = CloudWatchAnnotationSupport.prepareAnnotation({
|
||||
...legacyAnnotationQuery,
|
||||
statistic: 'Min',
|
||||
region: 'us-east-2',
|
||||
queryMode: 'Annotations',
|
||||
refId: 'A',
|
||||
});
|
||||
expect(annotation.target?.statistic).toEqual('Min');
|
||||
expect(annotation.target?.region).toEqual('us-east-2');
|
||||
expect(annotation.target?.queryMode).toEqual('Annotations');
|
||||
expect(annotation.target?.refId).toEqual('A');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when prepareQuery', () => {
|
||||
describe('is being called without a target', () => {
|
||||
it('should return undefined', () => {
|
||||
const preparedQuery = CloudWatchAnnotationSupport.prepareQuery({
|
||||
...annotationQuery,
|
||||
target: undefined,
|
||||
});
|
||||
expect(preparedQuery).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('is being called with a complete metric stat query', () => {
|
||||
it('should return the annotation target', () => {
|
||||
expect(CloudWatchAnnotationSupport.prepareQuery(annotationQuery)).toEqual(annotationQuery.target);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is being called with an incomplete metric stat query', () => {
|
||||
it('should return undefined', () => {
|
||||
const preparedQuery = CloudWatchAnnotationSupport.prepareQuery({
|
||||
...annotationQuery,
|
||||
target: {
|
||||
...annotationQuery.target!,
|
||||
dimensions: {},
|
||||
metricName: '',
|
||||
statistic: undefined,
|
||||
},
|
||||
});
|
||||
expect(preparedQuery).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('is being called with an incomplete prefix matching query', () => {
|
||||
it('should return the annotation target', () => {
|
||||
const query = {
|
||||
...annotationQuery,
|
||||
target: prefixMatchingAnnotationQuery,
|
||||
};
|
||||
expect(CloudWatchAnnotationSupport.prepareQuery(query)).toEqual(query.target);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is being called with an incomplete prefix matching query', () => {
|
||||
it('should return undefined', () => {
|
||||
const query = {
|
||||
...annotationQuery,
|
||||
target: {
|
||||
...prefixMatchingAnnotationQuery,
|
||||
actionPrefix: '',
|
||||
},
|
||||
};
|
||||
expect(CloudWatchAnnotationSupport.prepareQuery(query)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,60 @@
|
||||
import { AnnotationQuery } from '@grafana/data';
|
||||
|
||||
import { AnnotationQueryEditor } from './components/AnnotationQueryEditor';
|
||||
import { isCloudWatchAnnotation } from './guards';
|
||||
import { CloudWatchAnnotationQuery, CloudWatchQuery, LegacyAnnotationQuery } from './types';
|
||||
|
||||
export const CloudWatchAnnotationSupport = {
|
||||
// converts legacy angular style queries to new format. Also sets the same default values as in the deprecated angular directive
|
||||
prepareAnnotation: (
|
||||
query: LegacyAnnotationQuery | AnnotationQuery<CloudWatchAnnotationQuery>
|
||||
): AnnotationQuery<CloudWatchAnnotationQuery> => {
|
||||
if (isCloudWatchAnnotation(query)) {
|
||||
return query;
|
||||
}
|
||||
|
||||
return {
|
||||
// setting AnnotationQuery props explicitly since spreading would incorrectly use props that should be on the target only
|
||||
datasource: query.datasource,
|
||||
enable: query.enable,
|
||||
iconColor: query.iconColor,
|
||||
name: query.name,
|
||||
builtIn: query.builtIn,
|
||||
hide: query.hide,
|
||||
target: {
|
||||
...query.target,
|
||||
...query,
|
||||
statistic: query.statistic || 'Average',
|
||||
region: query.region || 'default',
|
||||
queryMode: 'Annotations',
|
||||
refId: query.refId || 'annotationQuery',
|
||||
},
|
||||
};
|
||||
},
|
||||
// return undefined if query is not complete so that annotation query execution is quietly skipped
|
||||
prepareQuery: (anno: AnnotationQuery<CloudWatchAnnotationQuery>): CloudWatchQuery | undefined => {
|
||||
if (!anno.target) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const {
|
||||
prefixMatching,
|
||||
actionPrefix,
|
||||
alarmNamePrefix,
|
||||
statistic,
|
||||
namespace,
|
||||
metricName,
|
||||
dimensions = {},
|
||||
} = anno.target;
|
||||
const validPrefixMatchingQuery = !!prefixMatching && !!actionPrefix && !!alarmNamePrefix;
|
||||
const validMetricStatQuery =
|
||||
!prefixMatching && !!namespace && !!metricName && !!statistic && !!Object.values(dimensions).length;
|
||||
|
||||
if (validPrefixMatchingQuery || validMetricStatQuery) {
|
||||
return anno.target;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
QueryEditor: AnnotationQueryEditor,
|
||||
};
|
@ -1,34 +0,0 @@
|
||||
import { defaultsDeep } from 'lodash';
|
||||
|
||||
import { CloudWatchAnnotationQuery } from './types';
|
||||
|
||||
export class CloudWatchAnnotationsQueryCtrl {
|
||||
static templateUrl = 'partials/annotations.editor.html';
|
||||
declare annotation: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor($scope: any) {
|
||||
this.annotation = $scope.ctrl.annotation;
|
||||
|
||||
defaultsDeep(this.annotation, {
|
||||
namespace: '',
|
||||
metricName: '',
|
||||
expression: '',
|
||||
dimensions: {},
|
||||
region: 'default',
|
||||
id: '',
|
||||
alias: '',
|
||||
statistic: 'Average',
|
||||
matchExact: true,
|
||||
prefixMatching: false,
|
||||
actionPrefix: '',
|
||||
alarmNamePrefix: '',
|
||||
});
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
onChange(query: CloudWatchAnnotationQuery) {
|
||||
Object.assign(this.annotation, query);
|
||||
}
|
||||
}
|
@ -1,8 +1,12 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { QueryEditorProps } from '@grafana/data';
|
||||
|
||||
import { setupMockedDataSource } from '../__mocks__/CloudWatchDataSource';
|
||||
import { CloudWatchAnnotationQuery } from '../types';
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
import { CloudWatchAnnotationQuery, CloudWatchJsonData, CloudWatchMetricsQuery, CloudWatchQuery } from '../types';
|
||||
|
||||
import { AnnotationQueryEditor } from './AnnotationQueryEditor';
|
||||
|
||||
@ -10,21 +14,16 @@ const ds = setupMockedDataSource({
|
||||
variables: [],
|
||||
});
|
||||
|
||||
const q: CloudWatchAnnotationQuery = {
|
||||
id: '',
|
||||
const q: CloudWatchQuery = {
|
||||
queryMode: 'Annotations',
|
||||
region: 'us-east-2',
|
||||
namespace: '',
|
||||
period: '',
|
||||
alias: '',
|
||||
metricName: '',
|
||||
dimensions: {},
|
||||
matchExact: true,
|
||||
statistic: '',
|
||||
expression: '',
|
||||
refId: '',
|
||||
enable: true,
|
||||
name: '',
|
||||
iconColor: '',
|
||||
prefixMatching: false,
|
||||
actionPrefix: '',
|
||||
alarmNamePrefix: '',
|
||||
@ -36,7 +35,7 @@ ds.datasource.getMetrics = jest.fn().mockResolvedValue([]);
|
||||
ds.datasource.getDimensionKeys = jest.fn().mockResolvedValue([]);
|
||||
ds.datasource.getVariables = jest.fn().mockReturnValue([]);
|
||||
|
||||
const props = {
|
||||
const props: QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData> = {
|
||||
datasource: ds.datasource,
|
||||
query: q,
|
||||
onChange: jest.fn(),
|
||||
@ -51,11 +50,18 @@ describe('AnnotationQueryEditor', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error component in case CloudWatchQuery is not CloudWatchAnnotationQuery', async () => {
|
||||
ds.datasource.getDimensionValues = jest.fn().mockResolvedValue([[{ label: 'dimVal1', value: 'dimVal1' }]]);
|
||||
render(
|
||||
<AnnotationQueryEditor {...props} query={{ ...props.query, queryMode: 'Metrics' } as CloudWatchMetricsQuery} />
|
||||
);
|
||||
await waitFor(() => expect(screen.getByText('Invalid annotation query')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should not display wildcard option in dimension value dropdown', async () => {
|
||||
ds.datasource.getDimensionValues = jest.fn().mockResolvedValue([[{ label: 'dimVal1', value: 'dimVal1' }]]);
|
||||
props.query.dimensions = { instanceId: 'instance-123' };
|
||||
(props.query as CloudWatchAnnotationQuery).dimensions = { instanceId: 'instance-123' };
|
||||
render(<AnnotationQueryEditor {...props} />);
|
||||
|
||||
const valueElement = screen.getByText('instance-123');
|
||||
expect(valueElement).toBeInTheDocument();
|
||||
expect(screen.queryByText('*')).toBeNull();
|
||||
|
@ -1,27 +1,30 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
|
||||
import { PanelData } from '@grafana/data';
|
||||
import { EditorField, EditorHeader, EditorRow, EditorSwitch, InlineSelect, Space } from '@grafana/experimental';
|
||||
import { Input } from '@grafana/ui';
|
||||
import { QueryEditorProps } from '@grafana/data';
|
||||
import { EditorField, EditorHeader, EditorRow, InlineSelect, Space, EditorSwitch } from '@grafana/experimental';
|
||||
import { Alert, Input } from '@grafana/ui';
|
||||
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
import { isCloudWatchAnnotationQuery } from '../guards';
|
||||
import { useRegions } from '../hooks';
|
||||
import { CloudWatchAnnotationQuery, CloudWatchMetricsQuery } from '../types';
|
||||
import { CloudWatchJsonData, CloudWatchQuery, MetricStat } from '../types';
|
||||
|
||||
import { MetricStatEditor } from './MetricStatEditor';
|
||||
|
||||
export type Props = {
|
||||
query: CloudWatchAnnotationQuery;
|
||||
datasource: CloudWatchDatasource;
|
||||
onChange: (value: CloudWatchAnnotationQuery) => void;
|
||||
data?: PanelData;
|
||||
};
|
||||
export type Props = QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData>;
|
||||
|
||||
export function AnnotationQueryEditor(props: React.PropsWithChildren<Props>) {
|
||||
export const AnnotationQueryEditor = (props: Props) => {
|
||||
const { query, onChange, datasource } = props;
|
||||
|
||||
const [regions, regionIsLoading] = useRegions(datasource);
|
||||
|
||||
if (!isCloudWatchAnnotationQuery(query)) {
|
||||
return (
|
||||
<Alert severity="error" title="Invalid annotation query" topSpacing={2}>
|
||||
{JSON.stringify(query, null, 4)}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EditorHeader>
|
||||
@ -38,8 +41,10 @@ export function AnnotationQueryEditor(props: React.PropsWithChildren<Props>) {
|
||||
<Space v={0.5} />
|
||||
<MetricStatEditor
|
||||
{...props}
|
||||
refId={query.refId}
|
||||
metricStat={query}
|
||||
disableExpressions={true}
|
||||
onChange={(editorQuery: CloudWatchMetricsQuery) => onChange({ ...query, ...editorQuery })}
|
||||
onChange={(metricStat: MetricStat) => onChange({ ...query, ...metricStat })}
|
||||
onRunQuery={() => {}}
|
||||
></MetricStatEditor>
|
||||
<Space v={0.5} />
|
||||
@ -81,4 +86,4 @@ export function AnnotationQueryEditor(props: React.PropsWithChildren<Props>) {
|
||||
</EditorRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -43,7 +43,7 @@ describe('Dimensions', () => {
|
||||
InstanceId: '*',
|
||||
InstanceGroup: 'Group1',
|
||||
};
|
||||
render(<Dimensions {...props} query={props.query} dimensionKeys={[]} />);
|
||||
render(<Dimensions {...props} metricStat={props.query} dimensionKeys={[]} />);
|
||||
const filterItems = screen.getAllByTestId('cloudwatch-dimensions-filter-item');
|
||||
expect(filterItems.length).toBe(2);
|
||||
|
||||
@ -59,7 +59,7 @@ describe('Dimensions', () => {
|
||||
it('it should add the new item but not call onChange', async () => {
|
||||
props.query.dimensions = {};
|
||||
const onChange = jest.fn();
|
||||
render(<Dimensions {...props} query={props.query} onChange={onChange} dimensionKeys={[]} />);
|
||||
render(<Dimensions {...props} metricStat={props.query} onChange={onChange} dimensionKeys={[]} />);
|
||||
|
||||
await userEvent.click(screen.getByLabelText('Add'));
|
||||
expect(screen.getByTestId('cloudwatch-dimensions-filter-item')).toBeInTheDocument();
|
||||
@ -72,7 +72,7 @@ describe('Dimensions', () => {
|
||||
props.query.dimensions = {};
|
||||
const onChange = jest.fn();
|
||||
const { container } = render(
|
||||
<Dimensions {...props} query={props.query} onChange={onChange} dimensionKeys={[]} />
|
||||
<Dimensions {...props} metricStat={props.query} onChange={onChange} dimensionKeys={[]} />
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByLabelText('Add'));
|
||||
@ -92,7 +92,7 @@ describe('Dimensions', () => {
|
||||
props.query.dimensions = {};
|
||||
const onChange = jest.fn();
|
||||
const { container } = render(
|
||||
<Dimensions {...props} query={props.query} onChange={onChange} dimensionKeys={[]} />
|
||||
<Dimensions {...props} metricStat={props.query} onChange={onChange} dimensionKeys={[]} />
|
||||
);
|
||||
|
||||
const label = await screen.findByLabelText('Add');
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { isEqual } from 'lodash';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorList } from '@grafana/experimental';
|
||||
|
||||
import { CloudWatchDatasource } from '../../datasource';
|
||||
import { Dimensions as DimensionsType, DimensionsQuery } from '../../types';
|
||||
import { Dimensions as DimensionsType, MetricStat } from '../../types';
|
||||
|
||||
import { FilterItem } from './FilterItem';
|
||||
|
||||
export interface Props {
|
||||
query: DimensionsQuery;
|
||||
metricStat: MetricStat;
|
||||
onChange: (dimensions: DimensionsType) => void;
|
||||
datasource: CloudWatchDatasource;
|
||||
dimensionKeys: Array<SelectableValue<string>>;
|
||||
@ -45,16 +45,22 @@ const filterConditionsToDimensions = (filters: DimensionFilterCondition[]) => {
|
||||
}, {});
|
||||
};
|
||||
|
||||
export const Dimensions: React.FC<Props> = ({ query, datasource, dimensionKeys, disableExpressions, onChange }) => {
|
||||
const [items, setItems] = useState<DimensionFilterCondition[]>([]);
|
||||
useEffect(() => setItems(dimensionsToFilterConditions(query.dimensions)), [query.dimensions]);
|
||||
export const Dimensions: React.FC<Props> = ({
|
||||
metricStat,
|
||||
datasource,
|
||||
dimensionKeys,
|
||||
disableExpressions,
|
||||
onChange,
|
||||
}) => {
|
||||
const dimensionFilters = useMemo(() => dimensionsToFilterConditions(metricStat.dimensions), [metricStat.dimensions]);
|
||||
const [items, setItems] = useState<DimensionFilterCondition[]>(dimensionFilters);
|
||||
const onDimensionsChange = (newItems: Array<Partial<DimensionFilterCondition>>) => {
|
||||
setItems(newItems);
|
||||
|
||||
// The onChange event should only be triggered in the case there is a complete dimension object.
|
||||
// So when a new key is added that does not yet have a value, it should not trigger an onChange event.
|
||||
const newDimensions = filterConditionsToDimensions(newItems);
|
||||
if (!isEqual(newDimensions, query.dimensions)) {
|
||||
if (!isEqual(newDimensions, metricStat.dimensions)) {
|
||||
onChange(newDimensions);
|
||||
}
|
||||
};
|
||||
@ -63,14 +69,14 @@ export const Dimensions: React.FC<Props> = ({ query, datasource, dimensionKeys,
|
||||
<EditorList
|
||||
items={items}
|
||||
onChange={onDimensionsChange}
|
||||
renderItem={makeRenderFilter(datasource, query, dimensionKeys, disableExpressions)}
|
||||
renderItem={makeRenderFilter(datasource, metricStat, dimensionKeys, disableExpressions)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function makeRenderFilter(
|
||||
datasource: CloudWatchDatasource,
|
||||
query: DimensionsQuery,
|
||||
metricStat: MetricStat,
|
||||
dimensionKeys: Array<SelectableValue<string>>,
|
||||
disableExpressions: boolean
|
||||
) {
|
||||
@ -84,7 +90,7 @@ function makeRenderFilter(
|
||||
filter={item}
|
||||
onChange={(item) => onChange(item)}
|
||||
datasource={datasource}
|
||||
query={query}
|
||||
metricStat={metricStat}
|
||||
disableExpressions={disableExpressions}
|
||||
dimensionKeys={dimensionKeys}
|
||||
onDelete={onDelete}
|
||||
|
@ -3,17 +3,17 @@ import React, { FunctionComponent, useMemo } from 'react';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
|
||||
import { GrafanaTheme2, SelectableValue, toOption } from '@grafana/data';
|
||||
import { InputGroup, AccessoryButton } from '@grafana/experimental';
|
||||
import { AccessoryButton, InputGroup } from '@grafana/experimental';
|
||||
import { Select, stylesFactory, useTheme2 } from '@grafana/ui';
|
||||
|
||||
import { CloudWatchDatasource } from '../../datasource';
|
||||
import { Dimensions, DimensionsQuery } from '../../types';
|
||||
import { Dimensions, MetricStat } from '../../types';
|
||||
import { appendTemplateVariables } from '../../utils/utils';
|
||||
|
||||
import { DimensionFilterCondition } from './Dimensions';
|
||||
|
||||
export interface Props {
|
||||
query: DimensionsQuery;
|
||||
metricStat: MetricStat;
|
||||
datasource: CloudWatchDatasource;
|
||||
filter: DimensionFilterCondition;
|
||||
dimensionKeys: Array<SelectableValue<string>>;
|
||||
@ -34,7 +34,7 @@ const excludeCurrentKey = (dimensions: Dimensions, currentKey: string | undefine
|
||||
|
||||
export const FilterItem: FunctionComponent<Props> = ({
|
||||
filter,
|
||||
query: { region, namespace, metricName, dimensions },
|
||||
metricStat: { region, namespace, metricName, dimensions },
|
||||
datasource,
|
||||
dimensionKeys,
|
||||
disableExpressions,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { intersectionBy, debounce, unionBy } from 'lodash';
|
||||
import { debounce, intersectionBy, unionBy } from 'lodash';
|
||||
import { LanguageMap, languages as prismLanguages } from 'prismjs';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Editor, Node, Plugin } from 'slate';
|
||||
@ -19,6 +19,8 @@ import { notifyApp } from 'app/core/actions';
|
||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||
import { dispatch } from 'app/store/store';
|
||||
import { ExploreId } from 'app/types';
|
||||
// Utils & Services
|
||||
// dom also includes Element polyfills
|
||||
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
import { CloudWatchLanguageProvider } from '../language_provider';
|
||||
@ -339,7 +341,7 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
|
||||
<div className="gf-form gf-form--grow flex-shrink-1">
|
||||
<QueryField
|
||||
additionalPlugins={this.plugins}
|
||||
query={query.expression ?? ''}
|
||||
query={(query as CloudWatchLogsQuery).expression ?? ''}
|
||||
onChange={this.onChangeQuery}
|
||||
onClick={this.onQueryFieldClick}
|
||||
onRunQuery={this.props.onRunQuery}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { fireEvent, render, screen, act, waitFor } from '@testing-library/react';
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import selectEvent from 'react-select-event';
|
||||
|
||||
import { MetricStatEditor } from '..';
|
||||
import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource';
|
||||
import { CloudWatchMetricsQuery } from '../../types';
|
||||
import { MetricStat } from '../../types';
|
||||
|
||||
const ds = setupMockedDataSource({
|
||||
variables: [],
|
||||
@ -15,23 +15,19 @@ ds.datasource.getNamespaces = jest.fn().mockResolvedValue([]);
|
||||
ds.datasource.getMetrics = jest.fn().mockResolvedValue([]);
|
||||
ds.datasource.getDimensionKeys = jest.fn().mockResolvedValue([]);
|
||||
ds.datasource.getVariables = jest.fn().mockReturnValue([]);
|
||||
const q: CloudWatchMetricsQuery = {
|
||||
id: '',
|
||||
const metricStat: MetricStat = {
|
||||
region: 'us-east-2',
|
||||
namespace: '',
|
||||
period: '',
|
||||
alias: '',
|
||||
metricName: '',
|
||||
dimensions: {},
|
||||
matchExact: true,
|
||||
statistic: '',
|
||||
expression: '',
|
||||
refId: '',
|
||||
matchExact: true,
|
||||
};
|
||||
|
||||
const props = {
|
||||
refId: 'A',
|
||||
datasource: ds.datasource,
|
||||
query: q,
|
||||
metricStat,
|
||||
onChange: jest.fn(),
|
||||
onRunQuery: jest.fn(),
|
||||
};
|
||||
@ -50,7 +46,7 @@ describe('MetricStatEditor', () => {
|
||||
|
||||
await userEvent.type(statisticElement, statistic);
|
||||
fireEvent.keyDown(statisticElement, { keyCode: 13 });
|
||||
expect(onChange).toHaveBeenCalledWith({ ...props.query, statistic });
|
||||
expect(onChange).toHaveBeenCalledWith({ ...props.metricStat, statistic });
|
||||
expect(onRunQuery).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@ -96,7 +92,13 @@ describe('MetricStatEditor', () => {
|
||||
});
|
||||
|
||||
it('should be unchecked when value is false', async () => {
|
||||
render(<MetricStatEditor {...props} query={{ ...props.query, matchExact: false }} disableExpressions={false} />);
|
||||
render(
|
||||
<MetricStatEditor
|
||||
{...props}
|
||||
metricStat={{ ...props.metricStat, matchExact: false }}
|
||||
disableExpressions={false}
|
||||
/>
|
||||
);
|
||||
expect(await screen.findByLabelText('Match exact - optional')).not.toBeChecked();
|
||||
});
|
||||
});
|
||||
@ -139,24 +141,24 @@ describe('MetricStatEditor', () => {
|
||||
await selectEvent.select(metricsSelect, 'm1');
|
||||
|
||||
expect(onChange.mock.calls).toEqual([
|
||||
[{ ...propsNamespaceMetrics.query, namespace: 'n1' }], // First call, namespace select
|
||||
[{ ...propsNamespaceMetrics.query, metricName: 'm1' }], // Second call, metric select
|
||||
[{ ...propsNamespaceMetrics.metricStat, namespace: 'n1' }], // First call, namespace select
|
||||
[{ ...propsNamespaceMetrics.metricStat, metricName: 'm1' }], // Second call, metric select
|
||||
]);
|
||||
expect(onRunQuery).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should remove metricName from query if it does not exist in new namespace', async () => {
|
||||
it('should remove metricName from metricStat if it does not exist in new namespace', async () => {
|
||||
propsNamespaceMetrics.datasource.getMetrics = jest
|
||||
.fn()
|
||||
.mockImplementation((namespace: string, region: string) => {
|
||||
let mockMetrics =
|
||||
namespace === 'n1' && region === props.query.region
|
||||
namespace === 'n1' && region === props.metricStat.region
|
||||
? metrics
|
||||
: [{ value: 'oldNamespaceMetric', label: 'oldNamespaceMetric', text: 'oldNamespaceMetric' }];
|
||||
return Promise.resolve(mockMetrics);
|
||||
});
|
||||
propsNamespaceMetrics.query.metricName = 'oldNamespaceMetric';
|
||||
propsNamespaceMetrics.query.namespace = 'n2';
|
||||
propsNamespaceMetrics.metricStat.metricName = 'oldNamespaceMetric';
|
||||
propsNamespaceMetrics.metricStat.namespace = 'n2';
|
||||
|
||||
await act(async () => {
|
||||
render(<MetricStatEditor {...propsNamespaceMetrics} />);
|
||||
@ -167,12 +169,12 @@ describe('MetricStatEditor', () => {
|
||||
|
||||
await selectEvent.select(namespaceSelect, 'n1');
|
||||
|
||||
expect(onChange.mock.calls).toEqual([[{ ...propsNamespaceMetrics.query, metricName: '', namespace: 'n1' }]]);
|
||||
expect(onChange.mock.calls).toEqual([[{ ...propsNamespaceMetrics.metricStat, metricName: '', namespace: 'n1' }]]);
|
||||
});
|
||||
|
||||
it('should not remove metricName from query if it does exist in new namespace', async () => {
|
||||
propsNamespaceMetrics.query.namespace = 'n1';
|
||||
propsNamespaceMetrics.query.metricName = 'm1';
|
||||
it('should not remove metricName from metricStat if it does exist in new namespace', async () => {
|
||||
propsNamespaceMetrics.metricStat.namespace = 'n1';
|
||||
propsNamespaceMetrics.metricStat.metricName = 'm1';
|
||||
|
||||
await act(async () => {
|
||||
render(<MetricStatEditor {...propsNamespaceMetrics} />);
|
||||
@ -184,7 +186,9 @@ describe('MetricStatEditor', () => {
|
||||
await selectEvent.select(namespaceSelect, 'n2');
|
||||
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(onChange.mock.calls).toEqual([[{ ...propsNamespaceMetrics.query, metricName: 'm1', namespace: 'n2' }]]);
|
||||
expect(onChange.mock.calls).toEqual([
|
||||
[{ ...propsNamespaceMetrics.metricStat, metricName: 'm1', namespace: 'n2' }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -7,50 +7,52 @@ import { Select } from '@grafana/ui';
|
||||
import { Dimensions } from '..';
|
||||
import { CloudWatchDatasource } from '../../datasource';
|
||||
import { useDimensionKeys, useMetrics, useNamespaces } from '../../hooks';
|
||||
import { CloudWatchMetricsQuery } from '../../types';
|
||||
import { MetricStat } from '../../types';
|
||||
import { appendTemplateVariables, toOption } from '../../utils/utils';
|
||||
|
||||
export type Props = {
|
||||
query: CloudWatchMetricsQuery;
|
||||
refId: string;
|
||||
metricStat: MetricStat;
|
||||
datasource: CloudWatchDatasource;
|
||||
disableExpressions?: boolean;
|
||||
onChange: (value: CloudWatchMetricsQuery) => void;
|
||||
onChange: (value: MetricStat) => void;
|
||||
onRunQuery: () => void;
|
||||
};
|
||||
|
||||
export function MetricStatEditor({
|
||||
query,
|
||||
refId,
|
||||
metricStat,
|
||||
datasource,
|
||||
disableExpressions = false,
|
||||
onChange,
|
||||
onRunQuery,
|
||||
}: React.PropsWithChildren<Props>) {
|
||||
const { region, namespace, metricName, dimensions } = query;
|
||||
const { region, namespace, metricName, dimensions } = metricStat;
|
||||
const namespaces = useNamespaces(datasource);
|
||||
const metrics = useMetrics(datasource, region, namespace);
|
||||
const dimensionKeys = useDimensionKeys(datasource, region, namespace, metricName, dimensions ?? {});
|
||||
|
||||
const onQueryChange = (query: CloudWatchMetricsQuery) => {
|
||||
onChange(query);
|
||||
const onMetricStatChange = (metricStat: MetricStat) => {
|
||||
onChange(metricStat);
|
||||
onRunQuery();
|
||||
};
|
||||
|
||||
const onNamespaceChange = async (query: CloudWatchMetricsQuery) => {
|
||||
const validatedQuery = await validateMetricName(query);
|
||||
onQueryChange(validatedQuery);
|
||||
const onNamespaceChange = async (metricStat: MetricStat) => {
|
||||
const validatedQuery = await validateMetricName(metricStat);
|
||||
onMetricStatChange(validatedQuery);
|
||||
};
|
||||
|
||||
const validateMetricName = async (query: CloudWatchMetricsQuery) => {
|
||||
let { metricName, namespace, region } = query;
|
||||
const validateMetricName = async (metricStat: MetricStat) => {
|
||||
let { metricName, namespace, region } = metricStat;
|
||||
if (!metricName) {
|
||||
return query;
|
||||
return metricStat;
|
||||
}
|
||||
await datasource.getMetrics(namespace, region).then((result: Array<SelectableValue<string>>) => {
|
||||
if (!result.find((metric) => metric.value === metricName)) {
|
||||
metricName = '';
|
||||
}
|
||||
});
|
||||
return { ...query, metricName };
|
||||
return { ...metricStat, metricName };
|
||||
};
|
||||
|
||||
return (
|
||||
@ -60,12 +62,12 @@ export function MetricStatEditor({
|
||||
<EditorField label="Namespace" width={26}>
|
||||
<Select
|
||||
aria-label="Namespace"
|
||||
value={query.namespace}
|
||||
value={metricStat.namespace}
|
||||
allowCustomValue
|
||||
options={namespaces}
|
||||
onChange={({ value: namespace }) => {
|
||||
if (namespace) {
|
||||
onNamespaceChange({ ...query, namespace });
|
||||
onNamespaceChange({ ...metricStat, namespace });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@ -73,12 +75,12 @@ export function MetricStatEditor({
|
||||
<EditorField label="Metric name" width={16}>
|
||||
<Select
|
||||
aria-label="Metric name"
|
||||
value={query.metricName || null}
|
||||
value={metricStat.metricName || null}
|
||||
allowCustomValue
|
||||
options={metrics}
|
||||
onChange={({ value: metricName }) => {
|
||||
if (metricName) {
|
||||
onQueryChange({ ...query, metricName });
|
||||
onMetricStatChange({ ...metricStat, metricName });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@ -86,12 +88,12 @@ export function MetricStatEditor({
|
||||
|
||||
<EditorField label="Statistic" width={16}>
|
||||
<Select
|
||||
inputId={`${query.refId}-metric-stat-editor-select-statistic`}
|
||||
inputId={`${refId}-metric-stat-editor-select-statistic`}
|
||||
allowCustomValue
|
||||
value={toOption(query.statistic ?? datasource.standardStatistics[0])}
|
||||
value={toOption(metricStat.statistic ?? datasource.standardStatistics[0])}
|
||||
options={appendTemplateVariables(
|
||||
datasource,
|
||||
datasource.standardStatistics.filter((s) => s !== query.statistic).map(toOption)
|
||||
datasource.standardStatistics.filter((s) => s !== metricStat.statistic).map(toOption)
|
||||
)}
|
||||
onChange={({ value: statistic }) => {
|
||||
if (
|
||||
@ -103,7 +105,7 @@ export function MetricStatEditor({
|
||||
return;
|
||||
}
|
||||
|
||||
onQueryChange({ ...query, statistic });
|
||||
onMetricStatChange({ ...metricStat, statistic });
|
||||
}}
|
||||
/>
|
||||
</EditorField>
|
||||
@ -113,8 +115,8 @@ export function MetricStatEditor({
|
||||
<EditorRow>
|
||||
<EditorField label="Dimensions">
|
||||
<Dimensions
|
||||
query={query}
|
||||
onChange={(dimensions) => onQueryChange({ ...query, dimensions })}
|
||||
metricStat={metricStat}
|
||||
onChange={(dimensions) => onMetricStatChange({ ...metricStat, dimensions })}
|
||||
dimensionKeys={dimensionKeys}
|
||||
disableExpressions={disableExpressions}
|
||||
datasource={datasource}
|
||||
@ -129,11 +131,11 @@ export function MetricStatEditor({
|
||||
tooltip="Only show metrics that exactly match all defined dimension names."
|
||||
>
|
||||
<EditorSwitch
|
||||
id={`${query.refId}-cloudwatch-match-exact`}
|
||||
value={!!query.matchExact}
|
||||
id={`${refId}-cloudwatch-match-exact`}
|
||||
value={!!metricStat.matchExact}
|
||||
onChange={(e) => {
|
||||
onQueryChange({
|
||||
...query,
|
||||
onMetricStatChange({
|
||||
...metricStat,
|
||||
matchExact: e.currentTarget.checked,
|
||||
});
|
||||
}}
|
||||
|
@ -5,13 +5,14 @@ import { EditorField, EditorRow, Space } from '@grafana/experimental';
|
||||
import { Input } from '@grafana/ui';
|
||||
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
import { isMetricsQuery } from '../guards';
|
||||
import { isCloudWatchMetricsQuery } from '../guards';
|
||||
import {
|
||||
CloudWatchJsonData,
|
||||
CloudWatchMetricsQuery,
|
||||
CloudWatchQuery,
|
||||
MetricEditorMode,
|
||||
MetricQueryType,
|
||||
MetricStat,
|
||||
} from '../types';
|
||||
|
||||
import QueryHeader from './QueryHeader';
|
||||
@ -87,7 +88,7 @@ export class MetricsQueryEditor extends PureComponent<Props, State> {
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource}
|
||||
onChange={(newQuery) => {
|
||||
if (isMetricsQuery(newQuery) && newQuery.metricEditorMode !== query.metricEditorMode) {
|
||||
if (isCloudWatchMetricsQuery(newQuery) && newQuery.metricEditorMode !== query.metricEditorMode) {
|
||||
this.setState({ sqlCodeEditorIsDirty: false });
|
||||
}
|
||||
this.onChange(newQuery);
|
||||
@ -99,7 +100,12 @@ export class MetricsQueryEditor extends PureComponent<Props, State> {
|
||||
{query.metricQueryType === MetricQueryType.Search && (
|
||||
<>
|
||||
{query.metricEditorMode === MetricEditorMode.Builder && (
|
||||
<MetricStatEditor {...{ ...this.props, query }}></MetricStatEditor>
|
||||
<MetricStatEditor
|
||||
{...this.props}
|
||||
refId={query.refId}
|
||||
metricStat={query}
|
||||
onChange={(metricStat: MetricStat) => this.props.onChange({ ...query, ...metricStat })}
|
||||
></MetricStatEditor>
|
||||
)}
|
||||
{query.metricEditorMode === MetricEditorMode.Code && (
|
||||
<MathExpressionQueryField
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { render, screen, waitFor, act } from '@testing-library/react';
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { setupMockedDataSource } from '../__mocks__/CloudWatchDataSource';
|
||||
@ -14,7 +14,7 @@ ds.datasource.getRegions = jest.fn().mockResolvedValue([]);
|
||||
describe('QueryHeader', () => {
|
||||
it('should display metric options for metrics', async () => {
|
||||
const query: CloudWatchMetricsQuery = {
|
||||
queryType: 'Metrics',
|
||||
queryMode: 'Metrics',
|
||||
id: '',
|
||||
region: 'us-east-2',
|
||||
namespace: '',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { pick } from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
import { ExploreMode, SelectableValue } from '@grafana/data';
|
||||
import { SelectableValue, ExploreMode } from '@grafana/data';
|
||||
import { EditorHeader, InlineSelect } from '@grafana/experimental';
|
||||
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
@ -70,7 +70,7 @@ const QueryHeader: React.FC<QueryHeaderProps> = ({
|
||||
|
||||
<InlineSelect aria-label="Query mode" value={queryMode} options={apiModes} onChange={onQueryModeChange} />
|
||||
|
||||
{queryMode !== ExploreMode.Logs && (
|
||||
{queryMode === ExploreMode.Metrics && (
|
||||
<MetricsQueryHeader
|
||||
query={query}
|
||||
datasource={datasource}
|
||||
|
@ -139,7 +139,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
/>
|
||||
<InlineField label="Dimensions" labelWidth={20} tooltip="Dimensions to filter the returned values on">
|
||||
<Dimensions
|
||||
query={{ ...parsedQuery, dimensions: parsedQuery.dimensionFilters }}
|
||||
metricStat={{ ...parsedQuery, dimensions: parsedQuery.dimensionFilters }}
|
||||
onChange={(dimensions) => {
|
||||
onChange({ ...parsedQuery, dimensionFilters: dimensions });
|
||||
}}
|
||||
|
@ -17,9 +17,7 @@ import {
|
||||
LogRowModel,
|
||||
rangeUtil,
|
||||
ScopedVars,
|
||||
TableData,
|
||||
TimeRange,
|
||||
toLegacyResponseData,
|
||||
} from '@grafana/data';
|
||||
import { DataSourceWithBackend, FetchError, getBackendSrv, toDataQueryResponse } from '@grafana/runtime';
|
||||
import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider';
|
||||
@ -31,12 +29,15 @@ import { VariableWithMultiSupport } from 'app/features/variables/types';
|
||||
import { store } from 'app/store/store';
|
||||
import { AppNotificationTimeout } from 'app/types';
|
||||
|
||||
import { CloudWatchAnnotationSupport } from './annotationSupport';
|
||||
import { SQLCompletionItemProvider } from './cloudwatch-sql/completion/CompletionItemProvider';
|
||||
import { ThrottlingErrorMessage } from './components/ThrottlingErrorMessage';
|
||||
import { isCloudWatchAnnotationQuery, isCloudWatchLogsQuery, isCloudWatchMetricsQuery } from './guards';
|
||||
import { CloudWatchLanguageProvider } from './language_provider';
|
||||
import memoizedDebounce from './memoizedDebounce';
|
||||
import { MetricMathCompletionItemProvider } from './metric-math/completion/CompletionItemProvider';
|
||||
import {
|
||||
CloudWatchAnnotationQuery,
|
||||
CloudWatchJsonData,
|
||||
CloudWatchLogsQuery,
|
||||
CloudWatchLogsQueryStatus,
|
||||
@ -48,7 +49,6 @@ import {
|
||||
GetLogEventsRequest,
|
||||
GetLogGroupFieldsRequest,
|
||||
GetLogGroupFieldsResponse,
|
||||
isCloudWatchLogsQuery,
|
||||
LogAction,
|
||||
MetricEditorMode,
|
||||
MetricQuery,
|
||||
@ -126,13 +126,14 @@ export class CloudWatchDatasource
|
||||
this.sqlCompletionItemProvider = new SQLCompletionItemProvider(this, this.templateSrv);
|
||||
this.metricMathCompletionItemProvider = new MetricMathCompletionItemProvider(this, this.templateSrv);
|
||||
this.variables = new CloudWatchVariableSupport(this, this.templateSrv);
|
||||
this.annotations = CloudWatchAnnotationSupport;
|
||||
}
|
||||
|
||||
query(options: DataQueryRequest<CloudWatchQuery>): Observable<DataQueryResponse> {
|
||||
options = cloneDeep(options);
|
||||
|
||||
let queries = options.targets.filter((item) => item.id !== '' || item.hide !== true);
|
||||
const { logQueries, metricsQueries } = this.getTargetsByQueryMode(queries);
|
||||
let queries = options.targets.filter((item) => item.hide !== true);
|
||||
const { logQueries, metricsQueries, annotationQueries } = this.getTargetsByQueryMode(queries);
|
||||
|
||||
const dataQueryResponses: Array<Observable<DataQueryResponse>> = [];
|
||||
if (logQueries.length > 0) {
|
||||
@ -143,6 +144,9 @@ export class CloudWatchDatasource
|
||||
dataQueryResponses.push(this.handleMetricQueries(metricsQueries, options));
|
||||
}
|
||||
|
||||
if (annotationQueries.length > 0) {
|
||||
dataQueryResponses.push(this.handleAnnotationQuery(annotationQueries, options));
|
||||
}
|
||||
// No valid targets, return the empty result to save a round trip.
|
||||
if (isEmpty(dataQueryResponses)) {
|
||||
return of({
|
||||
@ -237,9 +241,13 @@ export class CloudWatchDatasource
|
||||
};
|
||||
|
||||
filterQuery(query: CloudWatchQuery): boolean {
|
||||
if (query.queryMode === 'Logs') {
|
||||
if (isCloudWatchLogsQuery(query)) {
|
||||
return !!query.logGroupNames?.length;
|
||||
} else if (isCloudWatchAnnotationQuery(query)) {
|
||||
// annotation query validity already checked in annotationSupport
|
||||
return true;
|
||||
}
|
||||
|
||||
const { region, metricQueryType, metricEditorMode, expression, metricName, namespace, sqlExpression, statistic } =
|
||||
query;
|
||||
if (!region) {
|
||||
@ -297,6 +305,34 @@ export class CloudWatchDatasource
|
||||
return this.performTimeSeriesQuery(request, options.range);
|
||||
};
|
||||
|
||||
handleAnnotationQuery(
|
||||
queries: CloudWatchAnnotationQuery[],
|
||||
options: DataQueryRequest<CloudWatchQuery>
|
||||
): Observable<DataQueryResponse> {
|
||||
return this.awsRequest(DS_QUERY_ENDPOINT, {
|
||||
from: options.range.from.valueOf().toString(),
|
||||
to: options.range.to.valueOf().toString(),
|
||||
queries: queries.map((query) => ({
|
||||
...query,
|
||||
statistic: this.templateSrv.replace(query.statistic),
|
||||
region: this.templateSrv.replace(this.getActualRegion(query.region)),
|
||||
namespace: this.templateSrv.replace(query.namespace),
|
||||
metricName: this.templateSrv.replace(query.metricName),
|
||||
dimensions: this.convertDimensionFormat(query.dimensions ?? {}, {}),
|
||||
period: query.period ? parseInt(query.period, 10) : 300,
|
||||
actionPrefix: query.actionPrefix ?? '',
|
||||
alarmNamePrefix: query.alarmNamePrefix ?? '',
|
||||
type: 'annotationQuery',
|
||||
datasource: this.getRef(),
|
||||
})),
|
||||
}).pipe(
|
||||
map((r) => {
|
||||
const frames = toDataQueryResponse({ data: r }).data as DataFrame[];
|
||||
return { data: frames };
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks progress and polls data of a started logs query with some retry logic.
|
||||
* @param queryParams
|
||||
@ -732,52 +768,6 @@ export class CloudWatchDatasource
|
||||
});
|
||||
}
|
||||
|
||||
annotationQuery(options: any) {
|
||||
const annotation = options.annotation;
|
||||
const statistic = this.templateSrv.replace(annotation.statistic);
|
||||
const defaultPeriod = annotation.prefixMatching ? '' : '300';
|
||||
let period = annotation.period || defaultPeriod;
|
||||
period = parseInt(period, 10);
|
||||
const parameters = {
|
||||
prefixMatching: annotation.prefixMatching,
|
||||
region: this.templateSrv.replace(this.getActualRegion(annotation.region)),
|
||||
namespace: this.templateSrv.replace(annotation.namespace),
|
||||
metricName: this.templateSrv.replace(annotation.metricName),
|
||||
dimensions: this.convertDimensionFormat(annotation.dimensions, {}),
|
||||
statistic: statistic,
|
||||
period: period,
|
||||
actionPrefix: annotation.actionPrefix || '',
|
||||
alarmNamePrefix: annotation.alarmNamePrefix || '',
|
||||
};
|
||||
|
||||
return lastValueFrom(
|
||||
this.awsRequest(DS_QUERY_ENDPOINT, {
|
||||
from: options.range.from.valueOf().toString(),
|
||||
to: options.range.to.valueOf().toString(),
|
||||
queries: [
|
||||
{
|
||||
refId: 'annotationQuery',
|
||||
datasource: this.getRef(),
|
||||
type: 'annotationQuery',
|
||||
...parameters,
|
||||
},
|
||||
],
|
||||
}).pipe(
|
||||
map((r) => {
|
||||
const frames = toDataQueryResponse({ data: r }).data as DataFrame[];
|
||||
const table = toLegacyResponseData(frames[0]) as TableData;
|
||||
return table.rows.map((v) => ({
|
||||
annotation: annotation,
|
||||
time: Date.parse(v[0]),
|
||||
title: v[1],
|
||||
tags: [v[2]],
|
||||
text: v[3],
|
||||
}));
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
targetContainsTemplate(target: any) {
|
||||
return (
|
||||
this.templateSrv.containsTemplate(target.region) ||
|
||||
@ -883,19 +873,22 @@ export class CloudWatchDatasource
|
||||
getTargetsByQueryMode = (targets: CloudWatchQuery[]) => {
|
||||
const logQueries: CloudWatchLogsQuery[] = [];
|
||||
const metricsQueries: CloudWatchMetricsQuery[] = [];
|
||||
const annotationQueries: CloudWatchAnnotationQuery[] = [];
|
||||
|
||||
targets.forEach((query) => {
|
||||
const mode = query.queryMode ?? 'Metrics';
|
||||
if (mode === 'Logs') {
|
||||
logQueries.push(query as CloudWatchLogsQuery);
|
||||
if (isCloudWatchAnnotationQuery(query)) {
|
||||
annotationQueries.push(query);
|
||||
} else if (isCloudWatchLogsQuery(query)) {
|
||||
logQueries.push(query);
|
||||
} else {
|
||||
metricsQueries.push(query as CloudWatchMetricsQuery);
|
||||
metricsQueries.push(query);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
logQueries,
|
||||
metricsQueries,
|
||||
annotationQueries,
|
||||
};
|
||||
};
|
||||
|
||||
@ -907,9 +900,7 @@ export class CloudWatchDatasource
|
||||
return queries.map((query) => ({
|
||||
...query,
|
||||
region: this.getActualRegion(this.replace(query.region, scopedVars)),
|
||||
expression: this.replace(query.expression, scopedVars),
|
||||
|
||||
...(!isCloudWatchLogsQuery(query) && this.interpolateMetricsQueryVariables(query, scopedVars)),
|
||||
...(isCloudWatchMetricsQuery(query) && this.interpolateMetricsQueryVariables(query, scopedVars)),
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,16 @@
|
||||
import { CloudWatchMetricsQuery, CloudWatchQuery } from './types';
|
||||
import { AnnotationQuery } from '@grafana/data';
|
||||
|
||||
export const isMetricsQuery = (query: CloudWatchQuery): query is CloudWatchMetricsQuery => {
|
||||
return query.queryMode === 'Metrics';
|
||||
};
|
||||
import { CloudWatchAnnotationQuery, CloudWatchLogsQuery, CloudWatchMetricsQuery, CloudWatchQuery } from './types';
|
||||
|
||||
export const isCloudWatchLogsQuery = (cloudwatchQuery: CloudWatchQuery): cloudwatchQuery is CloudWatchLogsQuery =>
|
||||
cloudwatchQuery.queryMode === 'Logs';
|
||||
|
||||
export const isCloudWatchMetricsQuery = (cloudwatchQuery: CloudWatchQuery): cloudwatchQuery is CloudWatchMetricsQuery =>
|
||||
cloudwatchQuery.queryMode === 'Metrics';
|
||||
|
||||
export const isCloudWatchAnnotationQuery = (
|
||||
cloudwatchQuery: CloudWatchQuery
|
||||
): cloudwatchQuery is CloudWatchAnnotationQuery => cloudwatchQuery.queryMode === 'Annotations';
|
||||
|
||||
export const isCloudWatchAnnotation = (query: unknown): query is AnnotationQuery<CloudWatchAnnotationQuery> =>
|
||||
(query as AnnotationQuery<CloudWatchAnnotationQuery>).target?.queryMode === 'Annotations';
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { DataQuery } from '@grafana/data';
|
||||
import { AnnotationQuery, DataQuery } from '@grafana/data';
|
||||
|
||||
import {
|
||||
migrateCloudWatchQuery,
|
||||
migrateMultipleStatsAnnotationQuery,
|
||||
migrateMultipleStatsMetricsQuery,
|
||||
migrateCloudWatchQuery,
|
||||
migrateVariableQuery,
|
||||
} from './migrations';
|
||||
import {
|
||||
CloudWatchAnnotationQuery,
|
||||
CloudWatchMetricsQuery,
|
||||
MetricQueryType,
|
||||
LegacyAnnotationQuery,
|
||||
MetricEditorMode,
|
||||
MetricQueryType,
|
||||
VariableQueryType,
|
||||
} from './types';
|
||||
|
||||
@ -77,13 +77,15 @@ describe('migration', () => {
|
||||
});
|
||||
|
||||
describe('migrateMultipleStatsAnnotationQuery', () => {
|
||||
const annotationToMigrate = {
|
||||
const annotationToMigrate: AnnotationQuery<LegacyAnnotationQuery> = {
|
||||
statistics: ['p23.23', 'SampleCount'],
|
||||
name: 'Test annotation',
|
||||
enable: false,
|
||||
iconColor: '',
|
||||
};
|
||||
|
||||
const newAnnotations = migrateMultipleStatsAnnotationQuery(annotationToMigrate as CloudWatchAnnotationQuery);
|
||||
const newCloudWatchAnnotations = newAnnotations as CloudWatchAnnotationQuery[];
|
||||
const newAnnotations = migrateMultipleStatsAnnotationQuery(annotationToMigrate);
|
||||
const newCloudWatchAnnotations = newAnnotations;
|
||||
|
||||
it('should create one new annotation for each stat', () => {
|
||||
expect(newAnnotations.length).toBe(1);
|
||||
@ -107,11 +109,13 @@ describe('migration', () => {
|
||||
});
|
||||
|
||||
describe('migrateMultipleStatsAnnotationQuery with only with stat', () => {
|
||||
const annotationToMigrate = {
|
||||
const annotationToMigrate: AnnotationQuery<LegacyAnnotationQuery> = {
|
||||
statistics: ['p23.23'],
|
||||
name: 'Test annotation',
|
||||
} as CloudWatchAnnotationQuery;
|
||||
const newAnnotations = migrateMultipleStatsAnnotationQuery(annotationToMigrate as CloudWatchAnnotationQuery);
|
||||
enable: false,
|
||||
iconColor: '',
|
||||
};
|
||||
const newAnnotations = migrateMultipleStatsAnnotationQuery(annotationToMigrate);
|
||||
|
||||
it('should not create new annotations', () => {
|
||||
expect(newAnnotations.length).toBe(0);
|
||||
|
@ -2,9 +2,9 @@ import { AnnotationQuery, DataQuery } from '@grafana/data';
|
||||
import { getNextRefIdChar } from 'app/core/utils/query';
|
||||
|
||||
import {
|
||||
MetricEditorMode,
|
||||
CloudWatchAnnotationQuery,
|
||||
CloudWatchMetricsQuery,
|
||||
LegacyAnnotationQuery,
|
||||
MetricEditorMode,
|
||||
MetricQueryType,
|
||||
VariableQuery,
|
||||
VariableQueryType,
|
||||
@ -36,9 +36,9 @@ export function migrateMultipleStatsMetricsQuery(
|
||||
// Migrates an annotation query that use more than one statistic into multiple queries
|
||||
// E.g query.statistics = ['Max', 'Min'] will be migrated to two queries - query1.statistic = 'Max' and query2.statistic = 'Min'
|
||||
export function migrateMultipleStatsAnnotationQuery(
|
||||
annotationQuery: CloudWatchAnnotationQuery
|
||||
annotationQuery: AnnotationQuery<LegacyAnnotationQuery>
|
||||
): Array<AnnotationQuery<DataQuery>> {
|
||||
const newAnnotations: CloudWatchAnnotationQuery[] = [];
|
||||
const newAnnotations: Array<AnnotationQuery<LegacyAnnotationQuery>> = [];
|
||||
|
||||
if (annotationQuery && 'statistics' in annotationQuery && annotationQuery?.statistics?.length) {
|
||||
for (const stat of annotationQuery.statistics.splice(1)) {
|
||||
@ -53,7 +53,7 @@ export function migrateMultipleStatsAnnotationQuery(
|
||||
delete annotationQuery.statistics;
|
||||
}
|
||||
|
||||
return newAnnotations as Array<AnnotationQuery<DataQuery>>;
|
||||
return newAnnotations;
|
||||
}
|
||||
|
||||
export function migrateCloudWatchQuery(query: CloudWatchMetricsQuery) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { DataSourcePlugin } from '@grafana/data';
|
||||
|
||||
import { CloudWatchAnnotationsQueryCtrl } from './annotations_query_ctrl';
|
||||
import { ConfigEditor } from './components/ConfigEditor';
|
||||
import LogsCheatSheet from './components/LogsCheatSheet';
|
||||
import { CloudWatchLogsQueryEditor } from './components/LogsQueryEditor';
|
||||
@ -17,5 +16,4 @@ export const plugin = new DataSourcePlugin<CloudWatchDatasource, CloudWatchQuery
|
||||
.setQueryEditor(PanelQueryEditor)
|
||||
.setMetadataInspector(MetaInspector)
|
||||
.setExploreMetricsQueryField(PanelQueryEditor)
|
||||
.setExploreLogsQueryField(CloudWatchLogsQueryEditor)
|
||||
.setAnnotationQueryCtrl(CloudWatchAnnotationsQueryCtrl);
|
||||
.setExploreLogsQueryField(CloudWatchLogsQueryEditor);
|
||||
|
@ -1,5 +0,0 @@
|
||||
<cloudwatch-annotation-query-editor
|
||||
datasource="ctrl.datasource"
|
||||
on-change="ctrl.onChange"
|
||||
query="ctrl.annotation"
|
||||
></cloudwatch-annotation-query-editor>
|
@ -1,94 +0,0 @@
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-8">Region</label>
|
||||
<metric-segment segment="regionSegment" get-options="getRegions()" on-change="regionChanged()"></metric-segment>
|
||||
</div>
|
||||
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline" ng-if="target.expression.length === 0">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-8">Metric</label>
|
||||
|
||||
<metric-segment segment="namespaceSegment" get-options="getNamespaces()" on-change="namespaceChanged()">
|
||||
</metric-segment>
|
||||
<metric-segment segment="metricSegment" get-options="getMetrics()" on-change="metricChanged()"></metric-segment>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword">Stats</label>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-repeat="segment in statSegments">
|
||||
<metric-segment segment="segment" get-options="getStatSegments(segment, $index)"
|
||||
on-change="statSegmentChanged(segment, $index)"></metric-segment>
|
||||
</div>
|
||||
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline" ng-if="target.expression.length === 0">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-8">Dimensions</label>
|
||||
<metric-segment ng-repeat="segment in dimSegments" segment="segment" get-options="getDimSegments(segment, $index)"
|
||||
on-change="dimSegmentChanged(segment, $index)"></metric-segment>
|
||||
</div>
|
||||
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline" ng-if="target.statistics.length === 1">
|
||||
<div class="gf-form">
|
||||
<label class=" gf-form-label query-keyword width-8 ">
|
||||
Id
|
||||
<info-popover mode="right-normal ">Id can include numbers, letters, and underscore, and must start with a
|
||||
lowercase letter.</info-popover>
|
||||
</label>
|
||||
<input type="text " class="gf-form-input " ng-model="target.id " spellcheck="false"
|
||||
ng-pattern="/^[a-z][a-zA-Z0-9_]*$/" ng-model-onblur ng-change="onChange() " />
|
||||
</div>
|
||||
<div class="gf-form max-width-30 ">
|
||||
<label class="gf-form-label query-keyword width-7 ">Expression</label>
|
||||
<input type="text " class="gf-form-input " ng-model="target.expression
|
||||
" spellcheck="false" ng-model-onblur ng-change="onChange() " />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline ">
|
||||
<div class="gf-form ">
|
||||
<label class="gf-form-label query-keyword width-8 ">
|
||||
Min period
|
||||
<info-popover mode="right-normal ">Minimum interval between points in seconds</info-popover>
|
||||
</label>
|
||||
<input type="text " class="gf-form-input " ng-model="target.period " spellcheck="false" placeholder="auto
|
||||
" ng-model-onblur ng-change="onChange() " />
|
||||
</div>
|
||||
<div class="gf-form max-width-30 ">
|
||||
<label class="gf-form-label query-keyword width-7 ">Alias</label>
|
||||
<input type="text " class="gf-form-input " ng-model="target.alias " spellcheck="false" ng-model-onblur
|
||||
ng-change="onChange() " />
|
||||
<info-popover mode="right-absolute ">
|
||||
Alias replacement variables:
|
||||
<ul ng-non-bindable>
|
||||
<li>{{ metric }}</li>
|
||||
<li>{{ stat }}</li>
|
||||
<li>{{ namespace }}</li>
|
||||
<li>{{ region }}</li>
|
||||
<li>{{ period }}</li>
|
||||
<li>{{ label }}</li>
|
||||
<li>{{ YOUR_DIMENSION_NAME }}</li>
|
||||
</ul>
|
||||
</info-popover>
|
||||
</div>
|
||||
|
||||
<div class="gf-form gf-form--grow ">
|
||||
<div class="gf-form-label gf-form-label--grow "></div>
|
||||
</div>
|
||||
</div>
|
@ -579,9 +579,9 @@ describe('CloudWatchDatasource', () => {
|
||||
|
||||
ds.interpolateVariablesInQueries([logQuery], {});
|
||||
|
||||
// We interpolate `expression` and `region` in CloudWatchLogsQuery
|
||||
// We interpolate `region` in CloudWatchLogsQuery
|
||||
expect(templateSrv.replace).toHaveBeenCalledWith(`$${variableName}`, {});
|
||||
expect(templateSrv.replace).toHaveBeenCalledTimes(2);
|
||||
expect(templateSrv.replace).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should replace correct variables in CloudWatchMetricsQuery', () => {
|
||||
@ -589,9 +589,9 @@ describe('CloudWatchDatasource', () => {
|
||||
const { ds } = getTestContext({ templateSrv });
|
||||
const variableName = 'someVar';
|
||||
const logQuery: CloudWatchMetricsQuery = {
|
||||
queryMode: 'Metrics',
|
||||
id: 'someId',
|
||||
refId: 'someRefId',
|
||||
queryMode: 'Metrics',
|
||||
expression: `$${variableName}`,
|
||||
region: `$${variableName}`,
|
||||
period: `$${variableName}`,
|
||||
@ -610,7 +610,7 @@ describe('CloudWatchDatasource', () => {
|
||||
|
||||
// We interpolate `expression`, `region`, `period`, `alias`, `metricName`, `nameSpace` and `dimensions` in CloudWatchMetricsQuery
|
||||
expect(templateSrv.replace).toHaveBeenCalledWith(`$${variableName}`, {});
|
||||
expect(templateSrv.replace).toHaveBeenCalledTimes(9);
|
||||
expect(templateSrv.replace).toHaveBeenCalledTimes(8);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,17 +1,17 @@
|
||||
import { AwsAuthDataSourceSecureJsonData, AwsAuthDataSourceJsonData } from '@grafana/aws-sdk';
|
||||
import { AwsAuthDataSourceJsonData, AwsAuthDataSourceSecureJsonData } from '@grafana/aws-sdk';
|
||||
import { DataQuery, DataSourceRef, SelectableValue } from '@grafana/data';
|
||||
|
||||
export interface Dimensions {
|
||||
[key: string]: string | string[];
|
||||
}
|
||||
|
||||
import {
|
||||
QueryEditorArrayExpression,
|
||||
QueryEditorFunctionExpression,
|
||||
QueryEditorPropertyExpression,
|
||||
} from './expressions';
|
||||
|
||||
export type CloudWatchQueryMode = 'Metrics' | 'Logs';
|
||||
export interface Dimensions {
|
||||
[key: string]: string | string[];
|
||||
}
|
||||
|
||||
export type CloudWatchQueryMode = 'Metrics' | 'Logs' | 'Annotations';
|
||||
|
||||
export enum MetricQueryType {
|
||||
'Search',
|
||||
@ -35,43 +35,36 @@ export interface SQLExpression {
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface DimensionsQuery extends DataQuery {
|
||||
namespace: string;
|
||||
region: string;
|
||||
metricName?: string;
|
||||
dimensions?: Dimensions;
|
||||
}
|
||||
|
||||
export interface CloudWatchMetricsQuery extends DataQuery {
|
||||
export interface CloudWatchMetricsQuery extends MetricStat, DataQuery {
|
||||
queryMode?: 'Metrics';
|
||||
metricQueryType?: MetricQueryType;
|
||||
metricEditorMode?: MetricEditorMode;
|
||||
|
||||
//common props
|
||||
id: string;
|
||||
region: string;
|
||||
namespace: string;
|
||||
period?: string;
|
||||
alias?: string;
|
||||
|
||||
//Basic editor builder props
|
||||
metricName?: string;
|
||||
dimensions?: Dimensions;
|
||||
matchExact?: boolean;
|
||||
statistic?: string;
|
||||
/**
|
||||
* @deprecated use statistic
|
||||
*/
|
||||
statistics?: string[];
|
||||
|
||||
// Math expression query
|
||||
expression?: string;
|
||||
|
||||
sqlExpression?: string;
|
||||
|
||||
sql?: SQLExpression;
|
||||
}
|
||||
|
||||
export interface MetricStat {
|
||||
region: string;
|
||||
namespace: string;
|
||||
metricName?: string;
|
||||
dimensions?: Dimensions;
|
||||
matchExact?: boolean;
|
||||
period?: string;
|
||||
statistic?: string;
|
||||
/**
|
||||
* @deprecated use statistic
|
||||
*/
|
||||
statistics?: string[];
|
||||
}
|
||||
|
||||
export interface CloudWatchMathExpressionQuery extends DataQuery {
|
||||
expression: string;
|
||||
}
|
||||
@ -95,7 +88,6 @@ export enum CloudWatchLogsQueryStatus {
|
||||
|
||||
export interface CloudWatchLogsQuery extends DataQuery {
|
||||
queryMode: 'Logs';
|
||||
|
||||
id: string;
|
||||
region: string;
|
||||
expression?: string;
|
||||
@ -103,22 +95,15 @@ export interface CloudWatchLogsQuery extends DataQuery {
|
||||
statsGroups?: string[];
|
||||
}
|
||||
|
||||
export type CloudWatchQuery = CloudWatchMetricsQuery | CloudWatchLogsQuery;
|
||||
export type CloudWatchQuery = CloudWatchMetricsQuery | CloudWatchLogsQuery | CloudWatchAnnotationQuery;
|
||||
|
||||
export const isCloudWatchLogsQuery = (cloudwatchQuery: CloudWatchQuery): cloudwatchQuery is CloudWatchLogsQuery =>
|
||||
(cloudwatchQuery as CloudWatchLogsQuery).queryMode === 'Logs';
|
||||
|
||||
interface AnnotationProperties {
|
||||
enable: boolean;
|
||||
name: string;
|
||||
iconColor: string;
|
||||
prefixMatching: boolean;
|
||||
actionPrefix: string;
|
||||
alarmNamePrefix: string;
|
||||
export interface CloudWatchAnnotationQuery extends MetricStat, DataQuery {
|
||||
queryMode: 'Annotations';
|
||||
prefixMatching?: boolean;
|
||||
actionPrefix?: string;
|
||||
alarmNamePrefix?: string;
|
||||
}
|
||||
|
||||
export type CloudWatchAnnotationQuery = CloudWatchMetricsQuery & AnnotationProperties;
|
||||
|
||||
export type SelectableStrings = Array<SelectableValue<string>>;
|
||||
|
||||
export interface CloudWatchJsonData extends AwsAuthDataSourceJsonData {
|
||||
@ -369,7 +354,7 @@ export interface MetricRequest {
|
||||
|
||||
export interface MetricQuery {
|
||||
[key: string]: any;
|
||||
datasource: DataSourceRef;
|
||||
datasource?: DataSourceRef;
|
||||
refId?: string;
|
||||
maxDataPoints?: number;
|
||||
intervalMs?: number;
|
||||
@ -400,3 +385,33 @@ export interface VariableQuery extends DataQuery {
|
||||
resourceType: string;
|
||||
tags: string;
|
||||
}
|
||||
|
||||
export interface LegacyAnnotationQuery extends MetricStat, DataQuery {
|
||||
actionPrefix: string;
|
||||
alarmNamePrefix: string;
|
||||
alias: string;
|
||||
builtIn: number;
|
||||
datasource: any;
|
||||
dimensions: Dimensions;
|
||||
enable: boolean;
|
||||
expression: string;
|
||||
hide: boolean;
|
||||
iconColor: string;
|
||||
id: string;
|
||||
matchExact: boolean;
|
||||
metricName: string;
|
||||
name: string;
|
||||
namespace: string;
|
||||
period: string;
|
||||
prefixMatching: boolean;
|
||||
region: string;
|
||||
statistic: string;
|
||||
statistics: string[];
|
||||
target: {
|
||||
limit: number;
|
||||
matchAny: boolean;
|
||||
tags: any[];
|
||||
type: string;
|
||||
};
|
||||
type: string;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user