cache api calls when args are the same (#57082)
This commit is contained in:
@@ -10,7 +10,9 @@ import { CloudWatchSettings, setupMockedTemplateService } from './CloudWatchData
|
||||
export function setupMockedAPI({
|
||||
variables,
|
||||
response,
|
||||
getMock,
|
||||
}: {
|
||||
getMock?: jest.Func;
|
||||
response?: Array<{ text: string; label: string; value: string }>;
|
||||
variables?: CustomVariableModel[];
|
||||
mockGetVariableName?: boolean;
|
||||
|
||||
@@ -61,7 +61,9 @@ export const CloudWatchSettings: DataSourceInstanceSettings<CloudWatchJsonData>
|
||||
export function setupMockedDataSource({
|
||||
variables,
|
||||
mockGetVariableName = true,
|
||||
getMock = jest.fn(),
|
||||
}: {
|
||||
getMock?: jest.Func;
|
||||
variables?: CustomVariableModel[];
|
||||
mockGetVariableName?: boolean;
|
||||
} = {}) {
|
||||
@@ -84,7 +86,7 @@ export function setupMockedDataSource({
|
||||
setBackendSrv({
|
||||
...getBackendSrv(),
|
||||
fetch: fetchMock,
|
||||
get: jest.fn(),
|
||||
get: getMock,
|
||||
});
|
||||
|
||||
return { datasource, fetchMock, templateService, timeSrv };
|
||||
|
||||
@@ -1,55 +1,72 @@
|
||||
import { setupMockedAPI } from './__mocks__/API';
|
||||
|
||||
describe('describeLogGroup', () => {
|
||||
it('replaces region correctly in the query', async () => {
|
||||
const { api, resourceRequestMock } = setupMockedAPI();
|
||||
await api.describeLogGroups({ region: 'default' });
|
||||
expect(resourceRequestMock.mock.calls[0][1].region).toBe('us-west-1');
|
||||
describe('api', () => {
|
||||
describe('describeLogGroup', () => {
|
||||
it('replaces region correctly in the query', async () => {
|
||||
const { api, resourceRequestMock } = setupMockedAPI();
|
||||
await api.describeLogGroups({ region: 'default' });
|
||||
expect(resourceRequestMock.mock.calls[0][1].region).toBe('us-west-1');
|
||||
|
||||
await api.describeLogGroups({ region: 'eu-east' });
|
||||
expect(resourceRequestMock.mock.calls[1][1].region).toBe('eu-east');
|
||||
await api.describeLogGroups({ region: 'eu-east' });
|
||||
expect(resourceRequestMock.mock.calls[1][1].region).toBe('eu-east');
|
||||
});
|
||||
|
||||
it('should return log groups as an array of options', async () => {
|
||||
const response = [
|
||||
{
|
||||
text: '/aws/containerinsights/dev303-workshop/application',
|
||||
value: '/aws/containerinsights/dev303-workshop/application',
|
||||
label: '/aws/containerinsights/dev303-workshop/application',
|
||||
},
|
||||
{
|
||||
text: '/aws/containerinsights/dev303-workshop/flowlogs',
|
||||
value: '/aws/containerinsights/dev303-workshop/flowlogs',
|
||||
label: '/aws/containerinsights/dev303-workshop/flowlogs',
|
||||
},
|
||||
{
|
||||
text: '/aws/containerinsights/dev303-workshop/dataplane',
|
||||
value: '/aws/containerinsights/dev303-workshop/dataplane',
|
||||
label: '/aws/containerinsights/dev303-workshop/dataplane',
|
||||
},
|
||||
];
|
||||
|
||||
const { api } = setupMockedAPI({ response });
|
||||
const expectedLogGroups = [
|
||||
{
|
||||
text: '/aws/containerinsights/dev303-workshop/application',
|
||||
value: '/aws/containerinsights/dev303-workshop/application',
|
||||
label: '/aws/containerinsights/dev303-workshop/application',
|
||||
},
|
||||
{
|
||||
text: '/aws/containerinsights/dev303-workshop/flowlogs',
|
||||
value: '/aws/containerinsights/dev303-workshop/flowlogs',
|
||||
label: '/aws/containerinsights/dev303-workshop/flowlogs',
|
||||
},
|
||||
{
|
||||
text: '/aws/containerinsights/dev303-workshop/dataplane',
|
||||
value: '/aws/containerinsights/dev303-workshop/dataplane',
|
||||
label: '/aws/containerinsights/dev303-workshop/dataplane',
|
||||
},
|
||||
];
|
||||
|
||||
const logGroups = await api.describeLogGroups({ region: 'default' });
|
||||
|
||||
expect(logGroups).toEqual(expectedLogGroups);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return log groups as an array of options', async () => {
|
||||
const response = [
|
||||
{
|
||||
text: '/aws/containerinsights/dev303-workshop/application',
|
||||
value: '/aws/containerinsights/dev303-workshop/application',
|
||||
label: '/aws/containerinsights/dev303-workshop/application',
|
||||
},
|
||||
{
|
||||
text: '/aws/containerinsights/dev303-workshop/flowlogs',
|
||||
value: '/aws/containerinsights/dev303-workshop/flowlogs',
|
||||
label: '/aws/containerinsights/dev303-workshop/flowlogs',
|
||||
},
|
||||
{
|
||||
text: '/aws/containerinsights/dev303-workshop/dataplane',
|
||||
value: '/aws/containerinsights/dev303-workshop/dataplane',
|
||||
label: '/aws/containerinsights/dev303-workshop/dataplane',
|
||||
},
|
||||
];
|
||||
|
||||
const { api } = setupMockedAPI({ response });
|
||||
const expectedLogGroups = [
|
||||
{
|
||||
text: '/aws/containerinsights/dev303-workshop/application',
|
||||
value: '/aws/containerinsights/dev303-workshop/application',
|
||||
label: '/aws/containerinsights/dev303-workshop/application',
|
||||
},
|
||||
{
|
||||
text: '/aws/containerinsights/dev303-workshop/flowlogs',
|
||||
value: '/aws/containerinsights/dev303-workshop/flowlogs',
|
||||
label: '/aws/containerinsights/dev303-workshop/flowlogs',
|
||||
},
|
||||
{
|
||||
text: '/aws/containerinsights/dev303-workshop/dataplane',
|
||||
value: '/aws/containerinsights/dev303-workshop/dataplane',
|
||||
label: '/aws/containerinsights/dev303-workshop/dataplane',
|
||||
},
|
||||
];
|
||||
|
||||
const logGroups = await api.describeLogGroups({ region: 'default' });
|
||||
|
||||
expect(logGroups).toEqual(expectedLogGroups);
|
||||
describe('memoization', () => {
|
||||
it('should not initiate new api request in case a previous request had same args', async () => {
|
||||
const getMock = jest.fn();
|
||||
const { api, resourceRequestMock } = setupMockedAPI({ getMock });
|
||||
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'),
|
||||
]);
|
||||
expect(resourceRequestMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { memoize } from 'lodash';
|
||||
|
||||
import { DataSourceInstanceSettings, SelectableValue } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
@@ -10,37 +12,39 @@ export interface SelectableResourceValue extends SelectableValue<string> {
|
||||
}
|
||||
|
||||
export class CloudWatchAPI extends CloudWatchRequest {
|
||||
private memoizedGetRequest;
|
||||
|
||||
constructor(instanceSettings: DataSourceInstanceSettings<CloudWatchJsonData>, templateSrv: TemplateSrv) {
|
||||
super(instanceSettings, templateSrv);
|
||||
this.memoizedGetRequest = memoize(this.getRequest.bind(this), (path, parameters) =>
|
||||
JSON.stringify({ path, parameters })
|
||||
);
|
||||
}
|
||||
|
||||
resourceRequest(
|
||||
subtype: string,
|
||||
parameters?: Record<string, string | string[] | number>
|
||||
): Promise<SelectableResourceValue[]> {
|
||||
private getRequest<T>(subtype: string, parameters?: Record<string, string | string[] | number>): Promise<T> {
|
||||
return getBackendSrv().get(`/api/datasources/${this.instanceSettings.id}/resources/${subtype}`, parameters);
|
||||
}
|
||||
|
||||
getRegions() {
|
||||
return this.resourceRequest('regions').then((regions) => [
|
||||
return this.memoizedGetRequest<SelectableResourceValue[]>('regions').then((regions) => [
|
||||
{ label: 'default', value: 'default', text: 'default' },
|
||||
...regions.filter((r) => r.value),
|
||||
]);
|
||||
}
|
||||
|
||||
getNamespaces() {
|
||||
return this.resourceRequest('namespaces');
|
||||
return this.memoizedGetRequest<SelectableResourceValue[]>('namespaces');
|
||||
}
|
||||
|
||||
async describeLogGroups(params: DescribeLogGroupsRequest) {
|
||||
return this.resourceRequest('log-groups', {
|
||||
return this.memoizedGetRequest<SelectableResourceValue[]>('log-groups', {
|
||||
...params,
|
||||
region: this.templateSrv.replace(this.getActualRegion(params.region)),
|
||||
});
|
||||
}
|
||||
|
||||
async describeAllLogGroups(params: DescribeLogGroupsRequest) {
|
||||
return this.resourceRequest('all-log-groups', {
|
||||
return this.memoizedGetRequest<SelectableResourceValue[]>('all-log-groups', {
|
||||
...params,
|
||||
region: this.templateSrv.replace(this.getActualRegion(params.region)),
|
||||
});
|
||||
@@ -51,14 +55,14 @@ export class CloudWatchAPI extends CloudWatchRequest {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.resourceRequest('metrics', {
|
||||
return this.memoizedGetRequest<SelectableResourceValue[]>('metrics', {
|
||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||
namespace: this.templateSrv.replace(namespace),
|
||||
});
|
||||
}
|
||||
|
||||
async getAllMetrics(region: string): Promise<Array<{ metricName?: string; namespace: string }>> {
|
||||
const values = await this.resourceRequest('all-metrics', {
|
||||
const values = await this.memoizedGetRequest<SelectableResourceValue[]>('all-metrics', {
|
||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||
});
|
||||
|
||||
@@ -75,7 +79,7 @@ export class CloudWatchAPI extends CloudWatchRequest {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.resourceRequest('dimension-keys', {
|
||||
return this.memoizedGetRequest<SelectableResourceValue[]>('dimension-keys', {
|
||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||
namespace: this.templateSrv.replace(namespace),
|
||||
dimensionFilters: JSON.stringify(this.convertDimensionFormat(dimensionFilters, {})),
|
||||
@@ -94,7 +98,7 @@ export class CloudWatchAPI extends CloudWatchRequest {
|
||||
return [];
|
||||
}
|
||||
|
||||
const values = await this.resourceRequest('dimension-values', {
|
||||
const values = await this.memoizedGetRequest<SelectableResourceValue[]>('dimension-values', {
|
||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||
namespace: this.templateSrv.replace(namespace),
|
||||
metricName: this.templateSrv.replace(metricName.trim()),
|
||||
@@ -106,14 +110,14 @@ export class CloudWatchAPI extends CloudWatchRequest {
|
||||
}
|
||||
|
||||
getEbsVolumeIds(region: string, instanceId: string) {
|
||||
return this.resourceRequest('ebs-volume-ids', {
|
||||
return this.memoizedGetRequest<SelectableResourceValue[]>('ebs-volume-ids', {
|
||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||
instanceId: this.templateSrv.replace(instanceId),
|
||||
});
|
||||
}
|
||||
|
||||
getEc2InstanceAttribute(region: string, attributeName: string, filters: MultiFilters) {
|
||||
return this.resourceRequest('ec2-instance-attribute', {
|
||||
return this.memoizedGetRequest<SelectableResourceValue[]>('ec2-instance-attribute', {
|
||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||
attributeName: this.templateSrv.replace(attributeName),
|
||||
filters: JSON.stringify(this.convertMultiFilterFormat(filters, 'filter key')),
|
||||
@@ -121,7 +125,7 @@ export class CloudWatchAPI extends CloudWatchRequest {
|
||||
}
|
||||
|
||||
getResourceARNs(region: string, resourceType: string, tags: MultiFilters) {
|
||||
return this.resourceRequest('resource-arns', {
|
||||
return this.memoizedGetRequest<SelectableResourceValue[]>('resource-arns', {
|
||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||
resourceType: this.templateSrv.replace(resourceType),
|
||||
tags: JSON.stringify(this.convertMultiFilterFormat(tags, 'tag name')),
|
||||
|
||||
@@ -53,7 +53,7 @@ export const FilterItem: FunctionComponent<Props> = ({
|
||||
return datasource.api
|
||||
.getDimensionValues(region, namespace, metricName, filter.key, dimensionsExcludingCurrentKey)
|
||||
.then((result: Array<SelectableValue<string>>) => {
|
||||
if (result.length && !disableExpressions) {
|
||||
if (result.length && !disableExpressions && !result.some((o) => o.value === wildcardOption.value)) {
|
||||
result.unshift(wildcardOption);
|
||||
}
|
||||
return appendTemplateVariables(datasource, result);
|
||||
|
||||
@@ -203,17 +203,18 @@ describe('datasource', () => {
|
||||
|
||||
describe('resource requests', () => {
|
||||
it('should map resource response to metric response', async () => {
|
||||
const datasource = setupMockedDataSource().datasource;
|
||||
datasource.api.resourceRequest = jest.fn().mockResolvedValue([
|
||||
{
|
||||
text: 'AWS/EC2',
|
||||
value: 'CPUUtilization',
|
||||
},
|
||||
{
|
||||
text: 'AWS/Redshift',
|
||||
value: 'CPUPercentage',
|
||||
},
|
||||
]);
|
||||
const datasource = setupMockedDataSource({
|
||||
getMock: jest.fn().mockResolvedValue([
|
||||
{
|
||||
text: 'AWS/EC2',
|
||||
value: 'CPUUtilization',
|
||||
},
|
||||
{
|
||||
text: 'AWS/Redshift',
|
||||
value: 'CPUPercentage',
|
||||
},
|
||||
]),
|
||||
}).datasource;
|
||||
const allMetrics = await datasource.api.getAllMetrics('us-east-2');
|
||||
expect(allMetrics[0].metricName).toEqual('CPUUtilization');
|
||||
expect(allMetrics[0].namespace).toEqual('AWS/EC2');
|
||||
|
||||
Reference in New Issue
Block a user