diff --git a/public/app/features/explore/AdHocFilterField.test.tsx b/public/app/features/explore/AdHocFilterField.test.tsx
new file mode 100644
index 00000000000..dcd2e26ce74
--- /dev/null
+++ b/public/app/features/explore/AdHocFilterField.test.tsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { AdHocFilterField, DEFAULT_REMOVE_FILTER_VALUE } from './AdHocFilterField';
+import { AdHocFilter } from './AdHocFilter';
+import { MockDataSourceApi } from '../../../test/mocks/datasource_srv';
+
+describe('', () => {
+ let mockDataSourceApi;
+
+ beforeEach(() => {
+ mockDataSourceApi = new MockDataSourceApi();
+ });
+
+ it('should initially have no filters', () => {
+ const mockOnPairsChanged = jest.fn();
+ const wrapper = shallow();
+ expect(wrapper.state('pairs')).toEqual([]);
+ expect(wrapper.find(AdHocFilter).exists()).toBeFalsy();
+ });
+
+ it('should add when onAddFilter is invoked', () => {
+ const mockOnPairsChanged = jest.fn();
+ const wrapper = shallow();
+ expect(wrapper.state('pairs')).toEqual([]);
+ wrapper
+ .find('button')
+ .first()
+ .simulate('click');
+ expect(wrapper.find(AdHocFilter).exists()).toBeTruthy();
+ });
+
+ it(`should remove the relavant filter when the '${DEFAULT_REMOVE_FILTER_VALUE}' key is selected`, () => {
+ const mockOnPairsChanged = jest.fn();
+ const wrapper = shallow();
+ expect(wrapper.state('pairs')).toEqual([]);
+
+ wrapper
+ .find('button')
+ .first()
+ .simulate('click');
+ expect(wrapper.find(AdHocFilter).exists()).toBeTruthy();
+
+ wrapper.find(AdHocFilter).prop('onKeyChanged')(DEFAULT_REMOVE_FILTER_VALUE);
+ expect(wrapper.find(AdHocFilter).exists()).toBeFalsy();
+ });
+
+ it('it should call onPairsChanged when a filter is removed', async () => {
+ const mockOnPairsChanged = jest.fn();
+ const wrapper = shallow();
+ expect(wrapper.state('pairs')).toEqual([]);
+
+ wrapper
+ .find('button')
+ .first()
+ .simulate('click');
+ expect(wrapper.find(AdHocFilter).exists()).toBeTruthy();
+
+ wrapper.find(AdHocFilter).prop('onKeyChanged')(DEFAULT_REMOVE_FILTER_VALUE);
+ expect(wrapper.find(AdHocFilter).exists()).toBeFalsy();
+
+ expect(mockOnPairsChanged.mock.calls.length).toBe(1);
+ });
+});
diff --git a/public/app/features/explore/AdHocFilterField.tsx b/public/app/features/explore/AdHocFilterField.tsx
index e3584027af4..152e9f831c0 100644
--- a/public/app/features/explore/AdHocFilterField.tsx
+++ b/public/app/features/explore/AdHocFilterField.tsx
@@ -2,6 +2,8 @@ import React from 'react';
import { DataSourceApi, DataQuery, DataSourceJsonData } from '@grafana/ui';
import { AdHocFilter } from './AdHocFilter';
+export const DEFAULT_REMOVE_FILTER_VALUE = '-- remove filter --';
+
export interface KeyValuePair {
keys: string[];
key: string;
@@ -25,21 +27,18 @@ export class AdHocFilterField<
> extends React.PureComponent, State> {
state: State = { pairs: [] };
- async componentDidMount() {
- const tagKeys = this.props.datasource.getTagKeys ? await this.props.datasource.getTagKeys({}) : [];
- const keys = tagKeys.map(tagKey => tagKey.text);
- const pairs = [{ key: null, operator: null, value: null, keys, values: [] }];
- this.setState({ pairs });
- }
-
onKeyChanged = (index: number) => async (key: string) => {
- const { datasource, onPairsChanged } = this.props;
- const tagValues = datasource.getTagValues ? await datasource.getTagValues({ key }) : [];
- const values = tagValues.map(tagValue => tagValue.text);
- const newPairs = this.updatePairAt(index, { key, values });
+ if (key !== DEFAULT_REMOVE_FILTER_VALUE) {
+ const { datasource, onPairsChanged } = this.props;
+ const tagValues = datasource.getTagValues ? await datasource.getTagValues({ key }) : [];
+ const values = tagValues.map(tagValue => tagValue.text);
+ const newPairs = this.updatePairAt(index, { key, values });
- this.setState({ pairs: newPairs });
- onPairsChanged(newPairs);
+ this.setState({ pairs: newPairs });
+ onPairsChanged(newPairs);
+ } else {
+ this.onRemoveFilter(index);
+ }
};
onValueChanged = (index: number) => (value: string) => {
@@ -75,6 +74,7 @@ export class AdHocFilterField<
}, []);
this.setState({ pairs: newPairs });
+ this.props.onPairsChanged(newPairs);
};
private updatePairAt = (index: number, pair: Partial) => {
@@ -104,12 +104,17 @@ export class AdHocFilterField<
const { pairs } = this.state;
return (
<>
+ {pairs.length < 1 && (
+
+ )}
{pairs.map((pair, index) => {
const adHocKey = `adhoc-filter-${index}-${pair.key}-${pair.value}`;
return (