CloudWatch: Refactor metrics resource request frontend (#57602)

* refactor metric resource request frontend

* remove dupe interface
This commit is contained in:
Erik Sundell 2022-10-26 15:59:26 +02:00 committed by GitHub
parent 94aa090fb0
commit 237ff2699d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 81 additions and 39 deletions

View File

@ -12,7 +12,7 @@ export function setupMockedAPI({
response,
getMock,
}: {
getMock?: jest.Func;
getMock?: jest.Mock;
response?: Array<{ text: string; label: string; value: string }>;
variables?: CustomVariableModel[];
mockGetVariableName?: boolean;
@ -21,7 +21,7 @@ export function setupMockedAPI({
const timeSrv = getTimeSrv();
const api = new CloudWatchAPI(CloudWatchSettings, templateService);
const resourceRequestMock = jest.fn().mockReturnValue(response);
let resourceRequestMock = getMock ? getMock : jest.fn().mockReturnValue(response);
setBackendSrv({
...getBackendSrv(),
get: resourceRequestMock,

View File

@ -82,6 +82,7 @@ export function setupMockedDataSource({
datasource.api.getNamespaces = jest.fn().mockResolvedValue([]);
datasource.api.getRegions = jest.fn().mockResolvedValue([]);
datasource.api.getDimensionKeys = jest.fn().mockResolvedValue([]);
datasource.api.getMetrics = jest.fn().mockResolvedValue([]);
datasource.logsQueryRunner.defaultLogGroups = [];
const fetchMock = jest.fn().mockReturnValue(of({}));
setBackendSrv({

View File

@ -61,13 +61,53 @@ describe('api', () => {
const { api, resourceRequestMock } = setupMockedAPI({ getMock });
resourceRequestMock.mockResolvedValue([]);
await Promise.all([
api.getMetrics('AWS/EC2', 'us-east-1'),
api.getMetrics('AWS/EC2', 'us-east-1'),
api.getMetrics('AWS/EC2', 'us-east-2'),
api.getMetrics('AWS/EC2', 'us-east-2'),
api.getMetrics('AWS/EC2', 'us-east-2'),
api.getMetrics({ namespace: 'AWS/EC2', region: 'us-east-1' }),
api.getMetrics({ namespace: 'AWS/EC2', region: 'us-east-1' }),
api.getMetrics({ namespace: 'AWS/EC2', region: 'us-east-2' }),
api.getMetrics({ namespace: 'AWS/EC2', region: 'us-east-2' }),
api.getMetrics({ namespace: 'AWS/EC2', region: 'us-east-2' }),
]);
expect(resourceRequestMock).toHaveBeenCalledTimes(2);
});
});
describe('should handle backend srv response mapping', () => {
it('when getAllMetrics is called', async () => {
const getMock = jest.fn().mockResolvedValue([
{
namespace: 'AWS/EC2',
name: 'CPUUtilization',
},
{
namespace: 'AWS/Redshift',
name: 'CPUPercentage',
},
]);
const { api } = setupMockedAPI({ getMock });
const allMetrics = await api.getAllMetrics({ region: 'us-east-2' });
expect(allMetrics).toEqual([
{ metricName: 'CPUUtilization', namespace: 'AWS/EC2' },
{ metricName: 'CPUPercentage', namespace: 'AWS/Redshift' },
]);
});
it('when getMetrics', async () => {
const getMock = jest.fn().mockResolvedValue([
{
namespace: 'AWS/EC2',
name: 'CPUUtilization',
},
{
namespace: 'AWS/EC2',
name: 'CPUPercentage',
},
]);
const { api } = setupMockedAPI({ getMock });
const allMetrics = await api.getMetrics({ region: 'us-east-2', namespace: 'AWS/EC2' });
expect(allMetrics).toEqual([
{ label: 'CPUUtilization', value: 'CPUUtilization' },
{ label: 'CPUPercentage', value: 'CPUPercentage' },
]);
});
});
});

View File

@ -10,6 +10,7 @@ import {
DescribeLogGroupsRequest,
GetDimensionKeysRequest,
GetDimensionValuesRequest,
GetMetricsRequest,
MetricResponse,
MultiFilters,
} from './types';
@ -59,7 +60,7 @@ export class CloudWatchAPI extends CloudWatchRequest {
});
}
async getMetrics(namespace: string | undefined, region?: string): Promise<Array<SelectableValue<string>>> {
async getMetrics({ region, namespace }: GetMetricsRequest): Promise<Array<SelectableValue<string>>> {
if (!namespace) {
return [];
}
@ -70,12 +71,10 @@ export class CloudWatchAPI extends CloudWatchRequest {
}).then((metrics) => metrics.map((m) => ({ label: m.name, value: m.name })));
}
async getAllMetrics(region: string): Promise<Array<{ metricName?: string; namespace: string }>> {
const values = await this.memoizedGetRequest<MetricResponse[]>('all-metrics', {
async getAllMetrics({ region }: GetMetricsRequest): Promise<Array<{ metricName?: string; namespace: string }>> {
return this.memoizedGetRequest<MetricResponse[]>('metrics', {
region: this.templateSrv.replace(this.getActualRegion(region)),
});
return values.map((v) => ({ metricName: v.name, namespace: v.namespace }));
}).then((metrics) => metrics.map((m) => ({ metricName: m.name, namespace: m.namespace })));
}
async getDimensionKeys({

View File

@ -112,14 +112,14 @@ export class SQLCompletionItemProvider extends CompletionItemProvider {
const namespaceToken = getNamespaceToken(currentToken);
if (namespaceToken?.value) {
// if a namespace is specified, only suggest metrics for the namespace
const metrics = await this.api.getMetrics(
this.templateSrv.replace(namespaceToken?.value.replace(/\"/g, '')),
this.templateSrv.replace(this.region)
);
const metrics = await this.api.getMetrics({
namespace: namespaceToken?.value.replace(/\"/g, ''),
region: this.region,
});
metrics.forEach((m) => m.value && addSuggestion(m.value));
} else {
// If no namespace is specified in the query, just list all metrics
const metrics = await this.api.getAllMetrics(this.templateSrv.replace(this.region));
const metrics = await this.api.getAllMetrics({ region: this.region });
uniq(metrics.map((m) => m.metricName)).forEach((m) => m && addSuggestion(m, { insertText: m }));
}
}
@ -147,7 +147,7 @@ export class SQLCompletionItemProvider extends CompletionItemProvider {
let namespaces = [];
if (metricNameToken?.value) {
// if a metric is specified, only suggest namespaces that actually have that metric
const metrics = await this.api.getAllMetrics(this.region);
const metrics = await this.api.getMetrics({ region: this.region });
const metricName = this.templateSrv.replace(metricNameToken.value);
namespaces = metrics.filter((m) => m.metricName === metricName).map((m) => m.namespace);
} else {

View File

@ -148,15 +148,13 @@ describe('MetricStatEditor', () => {
});
it('should remove metricName from metricStat if it does not exist in new namespace', async () => {
propsNamespaceMetrics.datasource.api.getMetrics = jest
.fn()
.mockImplementation((namespace: string, region: string) => {
let mockMetrics =
namespace === 'n1' && region === props.metricStat.region
? metrics
: [{ value: 'oldNamespaceMetric', label: 'oldNamespaceMetric', text: 'oldNamespaceMetric' }];
return Promise.resolve(mockMetrics);
});
propsNamespaceMetrics.datasource.api.getMetrics = jest.fn().mockImplementation(({ namespace, region }) => {
let mockMetrics =
namespace === 'n1' && region === props.metricStat.region
? metrics
: [{ value: 'oldNamespaceMetric', label: 'oldNamespaceMetric', text: 'oldNamespaceMetric' }];
return Promise.resolve(mockMetrics);
});
propsNamespaceMetrics.metricStat.metricName = 'oldNamespaceMetric';
propsNamespaceMetrics.metricStat.namespace = 'n2';

View File

@ -48,7 +48,7 @@ export function MetricStatEditor({
if (!metricName) {
return metricStat;
}
await datasource.api.getMetrics(namespace, region).then((result: Array<SelectableValue<string>>) => {
await datasource.api.getMetrics({ namespace, region }).then((result: Array<SelectableValue<string>>) => {
if (!result.find((metric) => metric.value === metricName)) {
metricName = '';
}

View File

@ -67,8 +67,8 @@ const SQLBuilderSelectRow: React.FC<SQLBuilderSelectRowProps> = ({ datasource, q
};
const validateMetricName = async (query: CloudWatchMetricsQuery) => {
let { region, sql } = query;
await datasource.api.getMetrics(query.namespace, region).then((result: Array<SelectableValue<string>>) => {
let { region, sql, namespace } = query;
await datasource.api.getMetrics({ namespace, region }).then((result: Array<SelectableValue<string>>) => {
if (!result.some((metric) => metric.value === metricName)) {
sql = removeMetricName(query).sql;
}

View File

@ -230,7 +230,7 @@ describe('VariableEditor', () => {
container: document.body,
});
expect(ds.datasource.api.getMetrics).toHaveBeenCalledWith('z2', 'b1');
expect(ds.datasource.api.getMetrics).toHaveBeenCalledWith({ namespace: 'z2', region: 'b1' });
expect(ds.datasource.api.getDimensionKeys).toHaveBeenCalledWith({ namespace: 'z2', region: 'b1' });
expect(props.onChange).toHaveBeenCalledWith({
...defaultQuery,

View File

@ -65,7 +65,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
const sanitizeQuery = async (query: VariableQuery) => {
let { metricName, dimensionKey, dimensionFilters, namespace, region } = query;
if (metricName) {
await datasource.api.getMetrics(namespace, region).then((result: Array<SelectableValue<string>>) => {
await datasource.api.getMetrics({ namespace, region }).then((result: Array<SelectableValue<string>>) => {
if (!result.find((metric) => metric.value === metricName)) {
metricName = '';
}

View File

@ -215,7 +215,7 @@ describe('datasource', () => {
},
]),
}).datasource;
const allMetrics = await datasource.api.getAllMetrics('us-east-2');
const allMetrics = await datasource.api.getAllMetrics({ region: 'us-east-2' });
expect(allMetrics[0].metricName).toEqual('CPUUtilization');
expect(allMetrics[0].namespace).toEqual('AWS/EC2');
expect(allMetrics[1].metricName).toEqual('CPUPercentage');

View File

@ -42,7 +42,7 @@ export const useNamespaces = (datasource: CloudWatchDatasource) => {
export const useMetrics = (datasource: CloudWatchDatasource, region: string, namespace: string | undefined) => {
const [metrics, setMetrics] = useState<Array<SelectableValue<string>>>([]);
useEffect(() => {
datasource.api.getMetrics(namespace, region).then((result: Array<SelectableValue<string>>) => {
datasource.api.getMetrics({ namespace, region }).then((result: Array<SelectableValue<string>>) => {
setMetrics(appendTemplateVariables(datasource, result));
});
}, [datasource, region, namespace]);

View File

@ -451,6 +451,11 @@ export interface LegacyAnnotationQuery extends MetricStat, DataQuery {
type: string;
}
export interface MetricResponse {
name: string;
namespace: string;
}
export interface ResourceRequest {
region: string;
}
@ -468,7 +473,6 @@ export interface GetDimensionValuesRequest extends ResourceRequest {
dimensionFilters?: Dimensions;
}
export interface MetricResponse {
name: string;
namespace: string;
export interface GetMetricsRequest extends ResourceRequest {
namespace?: string;
}

View File

@ -84,7 +84,7 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
}
async handleMetricsQuery({ namespace, region }: VariableQuery) {
const metrics = await this.api.getMetrics(namespace, region);
const metrics = await this.api.getMetrics({ namespace, region });
return metrics.map((s) => ({
text: s.label,
value: s.value,