mirror of
https://github.com/grafana/grafana.git
synced 2025-09-22 23:53:09 +08:00
CloudWatch: Add multi-value template variable support for log group names in logs query builder (#49737)
* Add multi-value template variable support for log group names * add test for multi-value template variable for log group names * add test
This commit is contained in:
@ -179,8 +179,8 @@ exports[`no enzyme tests`] = {
|
||||
"public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx:1224072551": [
|
||||
[0, 19, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx:2097436158": [
|
||||
[1, 19, 13, "RegExp match", "2409514259"]
|
||||
"public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx:1501504663": [
|
||||
[2, 19, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx:3481855642": [
|
||||
[0, 26, 13, "RegExp match", "2409514259"]
|
||||
|
@ -149,3 +149,45 @@ export const dimensionVariable: CustomVariableModel = {
|
||||
],
|
||||
multi: false,
|
||||
};
|
||||
|
||||
export const logGroupNamesVariable: CustomVariableModel = {
|
||||
...initialCustomVariableModelState,
|
||||
id: 'groups',
|
||||
name: 'groups',
|
||||
current: {
|
||||
value: ['templatedGroup-1', 'templatedGroup-2'],
|
||||
text: ['templatedGroup-1', 'templatedGroup-2'],
|
||||
selected: true,
|
||||
},
|
||||
options: [
|
||||
{ value: 'templatedGroup-1', text: 'templatedGroup-1', selected: true },
|
||||
{ value: 'templatedGroup-2', text: 'templatedGroup-2', selected: true },
|
||||
],
|
||||
multi: true,
|
||||
};
|
||||
|
||||
export const regionVariable: CustomVariableModel = {
|
||||
...initialCustomVariableModelState,
|
||||
id: 'region',
|
||||
name: 'region',
|
||||
current: {
|
||||
value: 'templatedRegion',
|
||||
text: 'templatedRegion',
|
||||
selected: true,
|
||||
},
|
||||
options: [{ value: 'templatedRegion', text: 'templatedRegion', selected: true }],
|
||||
multi: false,
|
||||
};
|
||||
|
||||
export const expressionVariable: CustomVariableModel = {
|
||||
...initialCustomVariableModelState,
|
||||
id: 'fields',
|
||||
name: 'fields',
|
||||
current: {
|
||||
value: 'templatedField',
|
||||
text: 'templatedField',
|
||||
selected: true,
|
||||
},
|
||||
options: [{ value: 'templatedField', text: 'templatedField', selected: true }],
|
||||
multi: false,
|
||||
};
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { shallow } from 'enzyme';
|
||||
import _, { DebouncedFunc } from 'lodash'; // eslint-disable-line lodash/import-scope
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { openMenu, select } from 'react-select-event';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
@ -69,6 +71,7 @@ describe('CloudWatchLogsQueryField', () => {
|
||||
return Promise.resolve(['log_group_2']);
|
||||
}
|
||||
},
|
||||
getVariables: jest.fn().mockReturnValue([]),
|
||||
} as any
|
||||
}
|
||||
query={{} as any}
|
||||
@ -201,6 +204,7 @@ describe('CloudWatchLogsQueryField', () => {
|
||||
.slice(0, Math.max(params.limit ?? 50, 50));
|
||||
return Promise.resolve(theLogGroups);
|
||||
},
|
||||
getVariables: jest.fn().mockReturnValue([]),
|
||||
} as any
|
||||
}
|
||||
query={{} as any}
|
||||
@ -235,4 +239,33 @@ describe('CloudWatchLogsQueryField', () => {
|
||||
.concat(['WaterGroup', 'WaterGroup2', 'WaterGroup3', 'VelvetGroup', 'VelvetGroup2', 'VelvetGroup3'])
|
||||
);
|
||||
});
|
||||
|
||||
it('should render template variables a selectable option', async () => {
|
||||
const { datasource } = setupMockedDataSource();
|
||||
const onChange = jest.fn();
|
||||
|
||||
render(
|
||||
<CloudWatchLogsQueryField
|
||||
history={[]}
|
||||
absoluteRange={{ from: 1, to: 10 }}
|
||||
exploreId={ExploreId.left}
|
||||
datasource={datasource}
|
||||
query={{} as any}
|
||||
onRunQuery={() => {}}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const logGroupSelector = await screen.findByLabelText('Log Groups');
|
||||
expect(logGroupSelector).toBeInTheDocument();
|
||||
|
||||
await openMenu(logGroupSelector);
|
||||
const templateVariableSelector = await screen.findByText('Template Variables');
|
||||
expect(templateVariableSelector).toBeInTheDocument();
|
||||
|
||||
userEvent.click(templateVariableSelector);
|
||||
await select(await screen.findByLabelText('Select option'), 'test');
|
||||
|
||||
expect(await screen.findByText('test')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -27,6 +27,7 @@ import { CloudWatchLanguageProvider } from '../language_provider';
|
||||
import syntax from '../syntax';
|
||||
import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery } from '../types';
|
||||
import { getStatsGroups } from '../utils/query/getStatsGroups';
|
||||
import { appendTemplateVariables } from '../utils/utils';
|
||||
|
||||
import QueryHeader from './QueryHeader';
|
||||
|
||||
@ -310,7 +311,7 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
|
||||
<MultiSelect
|
||||
aria-label="Log Groups"
|
||||
allowCustomValue={allowCustomValue}
|
||||
options={unionBy(availableLogGroups, selectedLogGroups, 'value')}
|
||||
options={appendTemplateVariables(datasource, unionBy(availableLogGroups, selectedLogGroups, 'value'))}
|
||||
value={selectedLogGroups}
|
||||
onChange={(v) => {
|
||||
this.setSelectedLogGroups(v);
|
||||
|
@ -6,11 +6,14 @@ import { setDataSourceSrv } from '@grafana/runtime';
|
||||
|
||||
import {
|
||||
dimensionVariable,
|
||||
expressionVariable,
|
||||
labelsVariable,
|
||||
limitVariable,
|
||||
logGroupNamesVariable,
|
||||
metricVariable,
|
||||
namespaceVariable,
|
||||
setupMockedDataSource,
|
||||
regionVariable,
|
||||
} from './__mocks__/CloudWatchDataSource';
|
||||
import {
|
||||
CloudWatchLogsQuery,
|
||||
@ -65,6 +68,32 @@ describe('datasource', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should interpolate multi-value template variable for log group names in the query', async () => {
|
||||
const { datasource, fetchMock } = setupMockedDataSource({
|
||||
variables: [expressionVariable, logGroupNamesVariable, regionVariable],
|
||||
mockGetVariableName: false,
|
||||
});
|
||||
await lastValueFrom(
|
||||
datasource
|
||||
.query({
|
||||
targets: [
|
||||
{
|
||||
queryMode: 'Logs',
|
||||
region: '$region',
|
||||
expression: 'fields $fields',
|
||||
logGroupNames: ['$groups'],
|
||||
},
|
||||
],
|
||||
} as any)
|
||||
.pipe(toArray())
|
||||
);
|
||||
expect(fetchMock.mock.calls[0][0].data.queries[0]).toMatchObject({
|
||||
queryString: 'fields templatedField',
|
||||
logGroupNames: ['templatedGroup-1', 'templatedGroup-2'],
|
||||
region: 'templatedRegion',
|
||||
});
|
||||
});
|
||||
|
||||
it('should add links to log queries', async () => {
|
||||
const { datasource } = setupForLogs();
|
||||
const observable = datasource.query({
|
||||
|
@ -233,6 +233,7 @@ export class CloudWatchDatasource
|
||||
options,
|
||||
this.timeSrv.timeRange(),
|
||||
this.replace.bind(this),
|
||||
this.getVariableValue.bind(this),
|
||||
this.getActualRegion.bind(this),
|
||||
this.tracingDataSourceUid
|
||||
);
|
||||
@ -648,9 +649,12 @@ export class CloudWatchDatasource
|
||||
for (const fieldName of fieldsToReplace) {
|
||||
if (query.hasOwnProperty(fieldName)) {
|
||||
if (Array.isArray(anyQuery[fieldName])) {
|
||||
anyQuery[fieldName] = anyQuery[fieldName].map((val: string) =>
|
||||
this.replace(val, options.scopedVars, true, fieldName)
|
||||
);
|
||||
anyQuery[fieldName] = anyQuery[fieldName].flatMap((val: string) => {
|
||||
if (fieldName === 'logGroupNames') {
|
||||
return this.getVariableValue(val, options.scopedVars || {});
|
||||
}
|
||||
return this.replace(val, options.scopedVars, true, fieldName);
|
||||
});
|
||||
} else {
|
||||
anyQuery[fieldName] = this.replace(anyQuery[fieldName], options.scopedVars, true, fieldName);
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ describe('addDataLinksToLogsResponse', () => {
|
||||
mockOptions,
|
||||
{ ...time, raw: time },
|
||||
(s) => s ?? '',
|
||||
(v) => [v] ?? [],
|
||||
(r) => r,
|
||||
'xrayUid'
|
||||
);
|
||||
|
@ -16,10 +16,12 @@ export async function addDataLinksToLogsResponse(
|
||||
request: DataQueryRequest<CloudWatchQuery>,
|
||||
range: TimeRange,
|
||||
replaceFn: ReplaceFn,
|
||||
getVariableValueFn: (value: string, scopedVars: ScopedVars) => string[],
|
||||
getRegion: (region: string) => string,
|
||||
tracingDatasourceUid?: string
|
||||
): Promise<void> {
|
||||
const replace = (target: string, fieldName?: string) => replaceFn(target, request.scopedVars, true, fieldName);
|
||||
const getVariableValue = (target: string) => getVariableValueFn(target, request.scopedVars);
|
||||
|
||||
for (const dataFrame of response.data as DataFrame[]) {
|
||||
const curTarget = request.targets.find((target) => target.refId === dataFrame.refId) as CloudWatchLogsQuery;
|
||||
@ -35,7 +37,7 @@ export async function addDataLinksToLogsResponse(
|
||||
} else {
|
||||
// Right now we add generic link to open the query in xray console to every field so it shows in the logs row
|
||||
// details. Unfortunately this also creates link for all values inside table which look weird.
|
||||
field.config.links = [createAwsConsoleLink(curTarget, range, interpolatedRegion, replace)];
|
||||
field.config.links = [createAwsConsoleLink(curTarget, range, interpolatedRegion, replace, getVariableValue)];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -65,10 +67,11 @@ function createAwsConsoleLink(
|
||||
target: CloudWatchLogsQuery,
|
||||
range: TimeRange,
|
||||
region: string,
|
||||
replace: (target: string, fieldName?: string) => string
|
||||
replace: (target: string, fieldName?: string) => string,
|
||||
getVariableValue: (value: string) => string[]
|
||||
) {
|
||||
const interpolatedExpression = target.expression ? replace(target.expression) : '';
|
||||
const interpolatedGroups = target.logGroupNames?.map((logGroup: string) => replace(logGroup, 'log groups')) ?? [];
|
||||
const interpolatedGroups = target.logGroupNames?.flatMap(getVariableValue) ?? [];
|
||||
|
||||
const urlProps: AwsUrl = {
|
||||
end: range.to.toISOString(),
|
||||
|
Reference in New Issue
Block a user