Commit 3b383149 by kay delaney Committed by GitHub

CloudWatch Logs: Adds template variable support to log groups (#25604)

* CloudWatch Logs: Adds template variable support to log groups
Closes #25099
parent 47a2ee5b
......@@ -12,7 +12,7 @@ import { CloudWatchLanguageProvider } from '../language_provider';
import CloudWatchLink from './CloudWatchLink';
import { css } from 'emotion';
type Props = QueryEditorProps<CloudWatchDatasource, CloudWatchQuery>;
type Props = QueryEditorProps<CloudWatchDatasource, CloudWatchQuery> & { allowCustomValue?: boolean };
const labelClass = css`
margin-left: 3px;
......@@ -20,7 +20,7 @@ const labelClass = css`
`;
export const CloudWatchLogsQueryEditor = memo(function CloudWatchLogsQueryEditor(props: Props) {
const { query, data, datasource, onRunQuery, onChange, exploreId, exploreMode } = props;
const { query, data, datasource, onRunQuery, onChange, exploreId, exploreMode, allowCustomValue = false } = props;
let absolute: AbsoluteTimeRange;
if (data?.request?.range?.from) {
......@@ -55,6 +55,7 @@ export const CloudWatchLogsQueryEditor = memo(function CloudWatchLogsQueryEditor
absoluteRange={absolute}
syntaxLoaded={isSyntaxReady}
syntax={syntax}
allowCustomValue={allowCustomValue}
ExtraFieldElement={
<FormLabel className={`gf-form-label--btn ${labelClass}`} width="auto" tooltip="Link to Graph in AWS">
<CloudWatchLink query={query as CloudWatchLogsQuery} panelData={data} datasource={datasource} />
......
......@@ -42,6 +42,7 @@ export interface CloudWatchLogsQueryFieldProps extends ExploreQueryFieldProps<Cl
syntaxLoaded: boolean;
syntax: Grammar;
exploreId: ExploreId;
allowCustomValue?: boolean;
}
const containerClass = css`
......@@ -130,6 +131,14 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
return Promise.resolve();
}
// No need to fetch matching log groups if the search term isn't valid
// This is also useful for preventing searches when a user is typing out a log group with template vars
// See https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_LogGroup.html for the source of the pattern below
const logGroupNamePattern = /^[\.\-_/#A-Za-z0-9]+$/;
if (!logGroupNamePattern.test(searchTerm)) {
return Promise.resolve();
}
this.setState({
loadingLogGroups: true,
});
......@@ -158,7 +167,7 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
this.fetchLogGroupOptions(query.region).then(logGroups => {
this.setState(state => {
const selectedLogGroups = intersectionBy(state.selectedLogGroups, logGroups, 'value');
const selectedLogGroups = state.selectedLogGroups;
if (onChange) {
const nextQuery = {
...query,
......@@ -200,21 +209,22 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
}
};
setSelectedLogGroups = (v: Array<SelectableValue<string>>) => {
setSelectedLogGroups = (selectedLogGroups: Array<SelectableValue<string>>) => {
this.setState({
selectedLogGroups: v,
selectedLogGroups,
});
const { onChange, query } = this.props;
onChange?.({
...(query as CloudWatchLogsQuery),
logGroupNames: selectedLogGroups.map(logGroupName => logGroupName.value!) ?? [],
});
};
if (onChange) {
const nextQuery = {
...query,
logGroupNames: v.map(logGroupName => logGroupName.value!) ?? [],
};
onChange(nextQuery);
}
setCustomLogGroups = (v: string) => {
const customLogGroup: SelectableValue<string> = { value: v, label: v };
const selectedLogGroups = [...this.state.selectedLogGroups, customLogGroup];
this.setSelectedLogGroups(selectedLogGroups);
};
setSelectedRegion = async (v: SelectableValue<string>) => {
......@@ -327,7 +337,7 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
}, 250);
render() {
const { ExtraFieldElement, data, query, syntaxLoaded, datasource } = this.props;
const { ExtraFieldElement, data, query, syntaxLoaded, datasource, allowCustomValue } = this.props;
const {
selectedLogGroups,
availableLogGroups,
......@@ -368,11 +378,15 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
className="flex-grow-1"
inputEl={
<MultiSelect
options={availableLogGroups}
allowCustomValue={allowCustomValue}
options={unionBy(availableLogGroups, selectedLogGroups, 'value')}
value={selectedLogGroups}
onChange={v => {
this.setSelectedLogGroups(v);
}}
onCreateOption={v => {
this.setCustomLogGroups(v);
}}
className={containerClass}
closeMenuOnSelect={false}
isClearable={true}
......
......@@ -30,7 +30,11 @@ export class PanelQueryEditor extends PureComponent<Props> {
}
/>
</QueryInlineField>
{apiMode === ExploreMode.Logs ? <LogsQueryEditor {...this.props} /> : <MetricsQueryEditor {...this.props} />}
{apiMode === ExploreMode.Logs ? (
<LogsQueryEditor {...this.props} allowCustomValue />
) : (
<MetricsQueryEditor {...this.props} />
)}
</>
);
}
......
......@@ -580,6 +580,13 @@ export class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery, CloudWa
}
query.region = this.replace(query.region, scopedVars, true, 'region');
query.region = this.getActualRegion(query.region);
// interpolate log groups
if (query.logGroupNames) {
query.logGroupNames = query.logGroupNames.map((logGroup: string) =>
this.replace(logGroup, scopedVars, true, 'log groups')
);
}
});
}
......
......@@ -163,6 +163,7 @@ describe('CloudWatchDatasource', () => {
],
});
});
it('should stop querying when no more data retrieved past max attempts', async () => {
const fakeFrames = genMockFrames(10);
for (let i = 7; i < fakeFrames.length; i++) {
......@@ -243,6 +244,21 @@ describe('CloudWatchDatasource', () => {
});
expect(i).toBe(3);
});
it('should call the replace method on provided log groups', () => {
const replaceSpy = jest.spyOn(ctx.ds, 'replace').mockImplementation((target: string) => target);
ctx.ds.makeLogActionRequest('StartQuery', [
{
queryString: 'test query string',
region: 'default',
logGroupNames: ['log-group', '${my_var}Variable', 'Cool${other_var}'],
},
]);
expect(replaceSpy).toBeCalledWith('log-group', undefined, true, 'log groups');
expect(replaceSpy).toBeCalledWith('${my_var}Variable', undefined, true, 'log groups');
expect(replaceSpy).toBeCalledWith('Cool${other_var}', undefined, true, 'log groups');
});
});
describe('When performing CloudWatch metrics query', () => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment