mirror of
https://github.com/grafana/grafana.git
synced 2025-01-16 11:42:35 -06:00
Cloudwatch: Refactor metrics query editor (#48537)
* refactor metrics query editor to return a function component * add tests for metrics query editor * add simple render tests for panel query editor * remove obsolete test * pr feedback
This commit is contained in:
parent
39ee365b82
commit
a5c570a5f6
@ -49,7 +49,9 @@ export function setupMockedDataSource({
|
||||
} as any
|
||||
);
|
||||
datasource.getVariables = () => ['test'];
|
||||
datasource.getRegions = () => Promise.resolve([]);
|
||||
|
||||
datasource.getNamespaces = jest.fn().mockResolvedValue([]);
|
||||
datasource.getRegions = jest.fn().mockResolvedValue([]);
|
||||
const fetchMock = jest.fn().mockReturnValue(of({ data }));
|
||||
setBackendSrv({ fetch: fetchMock } as any);
|
||||
|
||||
|
@ -10,7 +10,7 @@ import { CustomVariableModel, initialVariableModelState } from '../../../../feat
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
import { CloudWatchJsonData, MetricEditorMode, MetricQueryType } from '../types';
|
||||
|
||||
import { MetricsQueryEditor, normalizeQuery, Props } from './MetricsQueryEditor';
|
||||
import { MetricsQueryEditor, Props } from './MetricsQueryEditor';
|
||||
|
||||
const setup = () => {
|
||||
const instanceSettings = {
|
||||
@ -79,64 +79,6 @@ describe('QueryEditor', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('normalizes query on mount', async () => {
|
||||
const { act } = renderer;
|
||||
const props = setup();
|
||||
// This does not actually even conform to the prop type but this happens on initialisation somehow
|
||||
props.query = {
|
||||
queryMode: 'Metrics',
|
||||
apiMode: 'Metrics',
|
||||
refId: '',
|
||||
expression: '',
|
||||
matchExact: true,
|
||||
metricQueryType: MetricQueryType.Search,
|
||||
metricEditorMode: MetricEditorMode.Builder,
|
||||
} as any;
|
||||
await act(async () => {
|
||||
renderer.create(<MetricsQueryEditor {...props} />);
|
||||
});
|
||||
expect((props.onChange as jest.Mock).mock.calls[0][0]).toEqual({
|
||||
namespace: '',
|
||||
metricName: '',
|
||||
expression: '',
|
||||
sqlExpression: '',
|
||||
dimensions: {},
|
||||
region: 'default',
|
||||
id: '',
|
||||
alias: '',
|
||||
statistic: 'Average',
|
||||
period: '',
|
||||
queryMode: 'Metrics',
|
||||
apiMode: 'Metrics',
|
||||
refId: '',
|
||||
matchExact: true,
|
||||
metricQueryType: MetricQueryType.Search,
|
||||
metricEditorMode: MetricEditorMode.Builder,
|
||||
});
|
||||
});
|
||||
|
||||
describe('should use correct default values', () => {
|
||||
it('should normalize query with default values', () => {
|
||||
expect(normalizeQuery({ refId: '42' } as any)).toEqual({
|
||||
namespace: '',
|
||||
metricName: '',
|
||||
expression: '',
|
||||
sqlExpression: '',
|
||||
dimensions: {},
|
||||
region: 'default',
|
||||
id: '',
|
||||
alias: '',
|
||||
statistic: 'Average',
|
||||
matchExact: true,
|
||||
period: '',
|
||||
queryMode: 'Metrics',
|
||||
refId: '42',
|
||||
metricQueryType: MetricQueryType.Search,
|
||||
metricEditorMode: MetricEditorMode.Builder,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle editor modes correctly', () => {
|
||||
it('when metric query type is metric search and editor mode is builder', async () => {
|
||||
await act(async () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ChangeEvent, PureComponent } from 'react';
|
||||
import React, { ChangeEvent, useState } from 'react';
|
||||
|
||||
import { QueryEditorProps } from '@grafana/data';
|
||||
import { EditorField, EditorRow, Space } from '@grafana/experimental';
|
||||
@ -16,181 +16,132 @@ import {
|
||||
} from '../types';
|
||||
|
||||
import QueryHeader from './QueryHeader';
|
||||
import usePreparedMetricsQuery from './usePreparedMetricsQuery';
|
||||
|
||||
import { Alias, MathExpressionQueryField, MetricStatEditor, SQLBuilderEditor, SQLCodeEditor } from './';
|
||||
|
||||
export type Props = QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData>;
|
||||
|
||||
interface State {
|
||||
sqlCodeEditorIsDirty: boolean;
|
||||
export interface Props extends QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData> {
|
||||
query: CloudWatchMetricsQuery;
|
||||
}
|
||||
|
||||
export const normalizeQuery = ({
|
||||
namespace,
|
||||
metricName,
|
||||
expression,
|
||||
dimensions,
|
||||
region,
|
||||
id,
|
||||
alias,
|
||||
statistic,
|
||||
period,
|
||||
sqlExpression,
|
||||
metricQueryType,
|
||||
metricEditorMode,
|
||||
...rest
|
||||
}: CloudWatchMetricsQuery): CloudWatchMetricsQuery => {
|
||||
const normalizedQuery = {
|
||||
queryMode: 'Metrics' as const,
|
||||
namespace: namespace ?? '',
|
||||
metricName: metricName ?? '',
|
||||
expression: expression ?? '',
|
||||
dimensions: dimensions ?? {},
|
||||
region: region ?? 'default',
|
||||
id: id ?? '',
|
||||
alias: alias ?? '',
|
||||
statistic: statistic ?? 'Average',
|
||||
period: period ?? '',
|
||||
metricQueryType: metricQueryType ?? MetricQueryType.Search,
|
||||
metricEditorMode: metricEditorMode ?? MetricEditorMode.Builder,
|
||||
sqlExpression: sqlExpression ?? '',
|
||||
...rest,
|
||||
};
|
||||
return !rest.hasOwnProperty('matchExact') ? { ...normalizedQuery, matchExact: true } : normalizedQuery;
|
||||
};
|
||||
export const MetricsQueryEditor = (props: Props) => {
|
||||
const { query, onRunQuery, datasource } = props;
|
||||
const [sqlCodeEditorIsDirty, setSQLCodeEditorIsDirty] = useState(false);
|
||||
const preparedQuery = usePreparedMetricsQuery(query, props.onChange);
|
||||
|
||||
export class MetricsQueryEditor extends PureComponent<Props, State> {
|
||||
state = {
|
||||
sqlCodeEditorIsDirty: false,
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
const metricsQuery = this.props.query as CloudWatchMetricsQuery;
|
||||
const query = normalizeQuery(metricsQuery);
|
||||
this.props.onChange(query);
|
||||
};
|
||||
|
||||
onChange = (query: CloudWatchQuery) => {
|
||||
const { onChange, onRunQuery } = this.props;
|
||||
const onChange = (query: CloudWatchQuery) => {
|
||||
const { onChange, onRunQuery } = props;
|
||||
onChange(query);
|
||||
onRunQuery();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { onRunQuery, datasource } = this.props;
|
||||
const metricsQuery = this.props.query as CloudWatchMetricsQuery;
|
||||
const query = normalizeQuery(metricsQuery);
|
||||
return (
|
||||
<>
|
||||
<QueryHeader
|
||||
query={query}
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource}
|
||||
onChange={(newQuery) => {
|
||||
if (isCloudWatchMetricsQuery(newQuery) && newQuery.metricEditorMode !== query.metricEditorMode) {
|
||||
setSQLCodeEditorIsDirty(false);
|
||||
}
|
||||
onChange(newQuery);
|
||||
}}
|
||||
sqlCodeEditorIsDirty={sqlCodeEditorIsDirty}
|
||||
/>
|
||||
<Space v={0.5} />
|
||||
|
||||
return (
|
||||
<>
|
||||
<QueryHeader
|
||||
query={query}
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource}
|
||||
onChange={(newQuery) => {
|
||||
if (isCloudWatchMetricsQuery(newQuery) && newQuery.metricEditorMode !== query.metricEditorMode) {
|
||||
this.setState({ sqlCodeEditorIsDirty: false });
|
||||
{query.metricQueryType === MetricQueryType.Search && (
|
||||
<>
|
||||
{query.metricEditorMode === MetricEditorMode.Builder && (
|
||||
<MetricStatEditor
|
||||
{...props}
|
||||
refId={query.refId}
|
||||
metricStat={query}
|
||||
onChange={(metricStat: MetricStat) => props.onChange({ ...query, ...metricStat })}
|
||||
></MetricStatEditor>
|
||||
)}
|
||||
{query.metricEditorMode === MetricEditorMode.Code && (
|
||||
<MathExpressionQueryField
|
||||
onRunQuery={onRunQuery}
|
||||
expression={query.expression ?? ''}
|
||||
onChange={(expression) => props.onChange({ ...query, expression })}
|
||||
datasource={datasource}
|
||||
></MathExpressionQueryField>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{query.metricQueryType === MetricQueryType.Query && (
|
||||
<>
|
||||
{query.metricEditorMode === MetricEditorMode.Code && (
|
||||
<SQLCodeEditor
|
||||
region={query.region}
|
||||
sql={query.sqlExpression ?? ''}
|
||||
onChange={(sqlExpression) => {
|
||||
if (!sqlCodeEditorIsDirty) {
|
||||
setSQLCodeEditorIsDirty(true);
|
||||
}
|
||||
props.onChange({ ...preparedQuery, sqlExpression });
|
||||
}}
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource}
|
||||
/>
|
||||
)}
|
||||
|
||||
{query.metricEditorMode === MetricEditorMode.Builder && (
|
||||
<>
|
||||
<SQLBuilderEditor
|
||||
query={query}
|
||||
onChange={props.onChange}
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource}
|
||||
></SQLBuilderEditor>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Space v={0.5} />
|
||||
<EditorRow>
|
||||
<EditorField
|
||||
label="ID"
|
||||
width={26}
|
||||
optional
|
||||
tooltip="ID can be used to reference other queries in math expressions. The ID can include numbers, letters, and underscore, and must start with a lowercase letter."
|
||||
invalid={!!query.id && !/^$|^[a-z][a-zA-Z0-9_]*$/.test(query.id)}
|
||||
>
|
||||
<Input
|
||||
id={`${query.refId}-cloudwatch-metric-query-editor-id`}
|
||||
onBlur={onRunQuery}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => onChange({ ...preparedQuery, id: event.target.value })}
|
||||
type="text"
|
||||
value={query.id}
|
||||
/>
|
||||
</EditorField>
|
||||
|
||||
<EditorField label="Period" width={26} tooltip="Minimum interval between points in seconds.">
|
||||
<Input
|
||||
id={`${query.refId}-cloudwatch-metric-query-editor-period`}
|
||||
value={query.period || ''}
|
||||
placeholder="auto"
|
||||
onBlur={onRunQuery}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
onChange({ ...preparedQuery, period: event.target.value })
|
||||
}
|
||||
this.onChange(newQuery);
|
||||
}}
|
||||
sqlCodeEditorIsDirty={this.state.sqlCodeEditorIsDirty}
|
||||
/>
|
||||
<Space v={0.5} />
|
||||
/>
|
||||
</EditorField>
|
||||
|
||||
{query.metricQueryType === MetricQueryType.Search && (
|
||||
<>
|
||||
{query.metricEditorMode === MetricEditorMode.Builder && (
|
||||
<MetricStatEditor
|
||||
{...this.props}
|
||||
refId={query.refId}
|
||||
metricStat={query}
|
||||
onChange={(metricStat: MetricStat) => this.props.onChange({ ...query, ...metricStat })}
|
||||
></MetricStatEditor>
|
||||
)}
|
||||
{query.metricEditorMode === MetricEditorMode.Code && (
|
||||
<MathExpressionQueryField
|
||||
onRunQuery={onRunQuery}
|
||||
expression={query.expression ?? ''}
|
||||
onChange={(expression) => this.props.onChange({ ...query, expression })}
|
||||
datasource={datasource}
|
||||
></MathExpressionQueryField>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{query.metricQueryType === MetricQueryType.Query && (
|
||||
<>
|
||||
{query.metricEditorMode === MetricEditorMode.Code && (
|
||||
<SQLCodeEditor
|
||||
region={query.region}
|
||||
sql={query.sqlExpression ?? ''}
|
||||
onChange={(sqlExpression) => {
|
||||
if (!this.state.sqlCodeEditorIsDirty) {
|
||||
this.setState({ sqlCodeEditorIsDirty: true });
|
||||
}
|
||||
this.props.onChange({ ...metricsQuery, sqlExpression });
|
||||
}}
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource}
|
||||
/>
|
||||
)}
|
||||
|
||||
{query.metricEditorMode === MetricEditorMode.Builder && (
|
||||
<>
|
||||
<SQLBuilderEditor
|
||||
query={query}
|
||||
onChange={this.props.onChange}
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource}
|
||||
></SQLBuilderEditor>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Space v={0.5} />
|
||||
<EditorRow>
|
||||
<EditorField
|
||||
label="ID"
|
||||
width={26}
|
||||
optional
|
||||
tooltip="ID can be used to reference other queries in math expressions. The ID can include numbers, letters, and underscore, and must start with a lowercase letter."
|
||||
invalid={!!query.id && !/^$|^[a-z][a-zA-Z0-9_]*$/.test(query.id)}
|
||||
>
|
||||
<Input
|
||||
id={`${query.refId}-cloudwatch-metric-query-editor-id`}
|
||||
onBlur={onRunQuery}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
this.onChange({ ...metricsQuery, id: event.target.value })
|
||||
}
|
||||
type="text"
|
||||
value={query.id}
|
||||
/>
|
||||
</EditorField>
|
||||
|
||||
<EditorField label="Period" width={26} tooltip="Minimum interval between points in seconds.">
|
||||
<Input
|
||||
id={`${query.refId}-cloudwatch-metric-query-editor-period`}
|
||||
value={query.period || ''}
|
||||
placeholder="auto"
|
||||
onBlur={onRunQuery}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
this.onChange({ ...metricsQuery, period: event.target.value })
|
||||
}
|
||||
/>
|
||||
</EditorField>
|
||||
|
||||
<EditorField
|
||||
label="Alias"
|
||||
width={26}
|
||||
optional
|
||||
tooltip="Change time series legend name using this field. See documentation for replacement variable formats."
|
||||
>
|
||||
<Alias
|
||||
value={metricsQuery.alias ?? ''}
|
||||
onChange={(value: string) => this.onChange({ ...metricsQuery, alias: value })}
|
||||
/>
|
||||
</EditorField>
|
||||
</EditorRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
<EditorField
|
||||
label="Alias"
|
||||
width={26}
|
||||
optional
|
||||
tooltip="Change time series legend name using this field. See documentation for replacement variable formats."
|
||||
>
|
||||
<Alias
|
||||
value={preparedQuery.alias ?? ''}
|
||||
onChange={(value: string) => onChange({ ...preparedQuery, alias: value })}
|
||||
/>
|
||||
</EditorField>
|
||||
</EditorRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,133 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { QueryEditorProps } from '@grafana/data';
|
||||
|
||||
import { setupMockedDataSource } from '../__mocks__/CloudWatchDataSource';
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
import { CloudWatchQuery, CloudWatchJsonData, MetricEditorMode, MetricQueryType } from '../types';
|
||||
|
||||
import { PanelQueryEditor } from './PanelQueryEditor';
|
||||
|
||||
// the following three fields are added to legacy queries in the dashboard migrator
|
||||
const migratedFields = {
|
||||
statistic: 'Average',
|
||||
metricEditorMode: MetricEditorMode.Builder,
|
||||
metricQueryType: MetricQueryType.Query,
|
||||
};
|
||||
|
||||
const props: QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData> = {
|
||||
datasource: setupMockedDataSource().datasource,
|
||||
onRunQuery: jest.fn(),
|
||||
onChange: jest.fn(),
|
||||
query: {} as CloudWatchQuery,
|
||||
};
|
||||
|
||||
describe('PanelQueryEditor should render right editor', () => {
|
||||
describe('when using grafana 6.3.0 metric query', () => {
|
||||
it('should render the metrics query editor', async () => {
|
||||
const query = {
|
||||
...migratedFields,
|
||||
dimensions: {
|
||||
InstanceId: 'i-123',
|
||||
},
|
||||
expression: '',
|
||||
highResolution: false,
|
||||
id: '',
|
||||
metricName: 'CPUUtilization',
|
||||
namespace: 'AWS/EC2',
|
||||
period: '',
|
||||
refId: 'A',
|
||||
region: 'default',
|
||||
returnData: false,
|
||||
};
|
||||
await act(async () => {
|
||||
render(<PanelQueryEditor {...props} query={query} />);
|
||||
});
|
||||
expect(screen.getByText('Metric name')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when using grafana 7.0.0 style metrics query', () => {
|
||||
it('should render the metrics query editor', async () => {
|
||||
const query = {
|
||||
...migratedFields,
|
||||
alias: '',
|
||||
apiMode: 'Logs',
|
||||
dimensions: {
|
||||
InstanceId: 'i-123',
|
||||
},
|
||||
expression: '',
|
||||
id: '',
|
||||
logGroupNames: [],
|
||||
matchExact: true,
|
||||
metricName: 'CPUUtilization',
|
||||
namespace: 'AWS/EC2',
|
||||
period: '',
|
||||
queryMode: 'Logs',
|
||||
refId: 'A',
|
||||
region: 'ap-northeast-2',
|
||||
statistics: 'Average',
|
||||
} as any;
|
||||
await act(async () => {
|
||||
render(<PanelQueryEditor {...props} query={query} />);
|
||||
});
|
||||
expect(screen.getByText('Choose Log Groups')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when using grafana 7.0.0 style logs query', () => {
|
||||
it('should render the metrics query editor', async () => {
|
||||
const query = {
|
||||
...migratedFields,
|
||||
alias: '',
|
||||
apiMode: 'Logs',
|
||||
dimensions: {
|
||||
InstanceId: 'i-123',
|
||||
},
|
||||
expression: '',
|
||||
id: '',
|
||||
logGroupNames: [],
|
||||
matchExact: true,
|
||||
metricName: 'CPUUtilization',
|
||||
namespace: 'AWS/EC2',
|
||||
period: '',
|
||||
queryMode: 'Logs',
|
||||
refId: 'A',
|
||||
region: 'ap-northeast-2',
|
||||
statistic: 'Average',
|
||||
} as any;
|
||||
await act(async () => {
|
||||
render(<PanelQueryEditor {...props} query={query} />);
|
||||
});
|
||||
expect(screen.getByText('Log Groups')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when using grafana query from curated ec2 dashboard', () => {
|
||||
it('should render the metrics query editor', async () => {
|
||||
const query = {
|
||||
...migratedFields,
|
||||
|
||||
alias: 'Inbound',
|
||||
dimensions: {
|
||||
InstanceId: '*',
|
||||
},
|
||||
expression:
|
||||
"SUM(REMOVE_EMPTY(SEARCH('{AWS/EC2,InstanceId} MetricName=\"NetworkIn\"', 'Sum', $period)))/$period",
|
||||
id: '',
|
||||
matchExact: true,
|
||||
metricName: 'NetworkOut',
|
||||
namespace: 'AWS/EC2',
|
||||
period: '$period',
|
||||
refId: 'B',
|
||||
region: '$region',
|
||||
statistic: 'Average',
|
||||
} as any;
|
||||
await act(async () => {
|
||||
render(<PanelQueryEditor {...props} query={query} />);
|
||||
});
|
||||
expect(screen.getByText('Metric name')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,8 +1,9 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { QueryEditorProps, ExploreMode } from '@grafana/data';
|
||||
import { QueryEditorProps } from '@grafana/data';
|
||||
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
import { isCloudWatchMetricsQuery } from '../guards';
|
||||
import { CloudWatchJsonData, CloudWatchQuery } from '../types';
|
||||
|
||||
import LogsQueryEditor from './LogsQueryEditor';
|
||||
@ -13,14 +14,13 @@ export type Props = QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, Clou
|
||||
export class PanelQueryEditor extends PureComponent<Props> {
|
||||
render() {
|
||||
const { query } = this.props;
|
||||
const apiMode = query.queryMode ?? 'Metrics';
|
||||
|
||||
return (
|
||||
<>
|
||||
{apiMode === ExploreMode.Logs ? (
|
||||
<LogsQueryEditor {...this.props} allowCustomValue />
|
||||
{isCloudWatchMetricsQuery(query) ? (
|
||||
<MetricsQueryEditor {...this.props} query={query} />
|
||||
) : (
|
||||
<MetricsQueryEditor {...this.props} />
|
||||
<LogsQueryEditor {...this.props} allowCustomValue />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -0,0 +1,76 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from '../types';
|
||||
|
||||
import usePreparedMetricsQuery, { DEFAULT_QUERY } from './usePreparedMetricsQuery';
|
||||
|
||||
interface TestScenario {
|
||||
name: string;
|
||||
query: any;
|
||||
expectedQuery: CloudWatchMetricsQuery;
|
||||
}
|
||||
|
||||
const baseQuery: CloudWatchMetricsQuery = {
|
||||
refId: 'A',
|
||||
id: '',
|
||||
region: 'us-east-2',
|
||||
namespace: 'AWS/EC2',
|
||||
dimensions: { InstanceId: 'x-123' },
|
||||
};
|
||||
|
||||
describe('usePrepareMetricsQuery', () => {
|
||||
describe('when an incomplete query is provided', () => {
|
||||
const testTable: TestScenario[] = [
|
||||
{ name: 'Empty query', query: { refId: 'A' }, expectedQuery: { ...DEFAULT_QUERY, refId: 'A' } },
|
||||
{
|
||||
name: 'Match exact is not part of the query',
|
||||
query: { ...baseQuery },
|
||||
expectedQuery: { ...DEFAULT_QUERY, ...baseQuery, matchExact: true },
|
||||
},
|
||||
{
|
||||
name: 'Match exact is part of the query',
|
||||
query: { ...baseQuery, matchExact: false },
|
||||
expectedQuery: { ...DEFAULT_QUERY, ...baseQuery, matchExact: false },
|
||||
},
|
||||
{
|
||||
name: 'When editor mode and builder mode different from default is specified',
|
||||
query: { ...baseQuery, metricQueryType: MetricQueryType.Query, metricEditorMode: MetricEditorMode.Code },
|
||||
expectedQuery: {
|
||||
...DEFAULT_QUERY,
|
||||
...baseQuery,
|
||||
metricQueryType: MetricQueryType.Query,
|
||||
metricEditorMode: MetricEditorMode.Code,
|
||||
},
|
||||
},
|
||||
];
|
||||
describe.each(testTable)('scenario %#: $name', (scenario) => {
|
||||
it('should set the default values and trigger onChangeQuery', async () => {
|
||||
const onChangeQuery = jest.fn();
|
||||
const { result } = renderHook(() => usePreparedMetricsQuery(scenario.query, onChangeQuery));
|
||||
expect(onChangeQuery).toHaveBeenLastCalledWith(result.current);
|
||||
expect(result.current).toEqual(scenario.expectedQuery);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a complete query is provided', () => {
|
||||
it('should not change the query and should not call onChangeQuery', async () => {
|
||||
const onChangeQuery = jest.fn();
|
||||
const completeQuery: CloudWatchMetricsQuery = {
|
||||
...baseQuery,
|
||||
expression: '',
|
||||
queryMode: 'Metrics',
|
||||
metricName: '',
|
||||
statistic: 'Sum',
|
||||
period: '300',
|
||||
metricQueryType: MetricQueryType.Query,
|
||||
metricEditorMode: MetricEditorMode.Code,
|
||||
sqlExpression: 'SELECT 1',
|
||||
matchExact: false,
|
||||
};
|
||||
const { result } = renderHook(() => usePreparedMetricsQuery(completeQuery, onChangeQuery));
|
||||
expect(onChangeQuery).not.toHaveBeenCalled();
|
||||
expect(result.current).toEqual(completeQuery);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,48 @@
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from '../types';
|
||||
|
||||
export const DEFAULT_QUERY: Omit<CloudWatchMetricsQuery, 'refId'> = {
|
||||
queryMode: 'Metrics',
|
||||
namespace: '',
|
||||
metricName: '',
|
||||
expression: '',
|
||||
dimensions: {},
|
||||
region: 'default',
|
||||
id: '',
|
||||
statistic: 'Average',
|
||||
period: '',
|
||||
metricQueryType: MetricQueryType.Search,
|
||||
metricEditorMode: MetricEditorMode.Builder,
|
||||
sqlExpression: '',
|
||||
matchExact: true,
|
||||
};
|
||||
|
||||
const prepareQuery = (query: CloudWatchMetricsQuery) => {
|
||||
const withDefaults = { ...DEFAULT_QUERY, ...query };
|
||||
|
||||
// If we didn't make any changes to the object, then return the original object to keep the
|
||||
// identity the same, and not trigger any other useEffects or anything.
|
||||
return deepEqual(withDefaults, query) ? query : withDefaults;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns queries with some defaults + migrations, and calls onChange function to notify if it changes
|
||||
*/
|
||||
const usePreparedMetricsQuery = (
|
||||
query: CloudWatchMetricsQuery,
|
||||
onChangeQuery: (newQuery: CloudWatchMetricsQuery) => void
|
||||
) => {
|
||||
const preparedQuery = useMemo(() => prepareQuery(query), [query]);
|
||||
|
||||
useEffect(() => {
|
||||
if (preparedQuery !== query) {
|
||||
onChangeQuery(preparedQuery);
|
||||
}
|
||||
}, [preparedQuery, query, onChangeQuery]);
|
||||
|
||||
return preparedQuery;
|
||||
};
|
||||
|
||||
export default usePreparedMetricsQuery;
|
@ -6,7 +6,7 @@ export const isCloudWatchLogsQuery = (cloudwatchQuery: CloudWatchQuery): cloudwa
|
||||
cloudwatchQuery.queryMode === 'Logs';
|
||||
|
||||
export const isCloudWatchMetricsQuery = (cloudwatchQuery: CloudWatchQuery): cloudwatchQuery is CloudWatchMetricsQuery =>
|
||||
cloudwatchQuery.queryMode === 'Metrics';
|
||||
cloudwatchQuery.queryMode === 'Metrics' || !cloudwatchQuery.hasOwnProperty('queryMode'); // in early versions of this plugin, queryMode wasn't defined in a CloudWatchMetricsQuery
|
||||
|
||||
export const isCloudWatchAnnotationQuery = (
|
||||
cloudwatchQuery: CloudWatchQuery
|
||||
|
Loading…
Reference in New Issue
Block a user