ソースを参照

Explore: Adds ability to remove filter from <AdHocFilterField /> key dropdown (#17553)

Also adds tests to validate behaviour
Query now reruns when filter is removed
Changes <AdHocFilterField /> such that after a measurement and field have been chosen,
just a '+' button is displayed, rather than an empty <AdHocFilter />

Closes #17544
Closes #17497
kay delaney 6 年 前
コミット
6170a039e5

+ 64 - 0
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('<AdHocFilterField />', () => {
+  let mockDataSourceApi;
+
+  beforeEach(() => {
+    mockDataSourceApi = new MockDataSourceApi();
+  });
+
+  it('should initially have no filters', () => {
+    const mockOnPairsChanged = jest.fn();
+    const wrapper = shallow(<AdHocFilterField datasource={mockDataSourceApi} onPairsChanged={mockOnPairsChanged} />);
+    expect(wrapper.state('pairs')).toEqual([]);
+    expect(wrapper.find(AdHocFilter).exists()).toBeFalsy();
+  });
+
+  it('should add <AdHocFilter /> when onAddFilter is invoked', () => {
+    const mockOnPairsChanged = jest.fn();
+    const wrapper = shallow(<AdHocFilterField datasource={mockDataSourceApi} onPairsChanged={mockOnPairsChanged} />);
+    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(<AdHocFilterField datasource={mockDataSourceApi} onPairsChanged={mockOnPairsChanged} />);
+    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(<AdHocFilterField datasource={mockDataSourceApi} onPairsChanged={mockOnPairsChanged} />);
+    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);
+  });
+});

+ 20 - 15
public/app/features/explore/AdHocFilterField.tsx

@@ -2,6 +2,8 @@ import React from 'react';
 import { DataSourceApi, DataQuery, DataSourceJsonData } from '@grafana/ui';
 import { DataSourceApi, DataQuery, DataSourceJsonData } from '@grafana/ui';
 import { AdHocFilter } from './AdHocFilter';
 import { AdHocFilter } from './AdHocFilter';
 
 
+export const DEFAULT_REMOVE_FILTER_VALUE = '-- remove filter --';
+
 export interface KeyValuePair {
 export interface KeyValuePair {
   keys: string[];
   keys: string[];
   key: string;
   key: string;
@@ -25,21 +27,18 @@ export class AdHocFilterField<
 > extends React.PureComponent<Props<TQuery, TOptions>, State> {
 > extends React.PureComponent<Props<TQuery, TOptions>, State> {
   state: State = { pairs: [] };
   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) => {
   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 });
-
-    this.setState({ pairs: newPairs });
-    onPairsChanged(newPairs);
+    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);
+    } else {
+      this.onRemoveFilter(index);
+    }
   };
   };
 
 
   onValueChanged = (index: number) => (value: string) => {
   onValueChanged = (index: number) => (value: string) => {
@@ -75,6 +74,7 @@ export class AdHocFilterField<
     }, []);
     }, []);
 
 
     this.setState({ pairs: newPairs });
     this.setState({ pairs: newPairs });
+    this.props.onPairsChanged(newPairs);
   };
   };
 
 
   private updatePairAt = (index: number, pair: Partial<KeyValuePair>) => {
   private updatePairAt = (index: number, pair: Partial<KeyValuePair>) => {
@@ -104,12 +104,17 @@ export class AdHocFilterField<
     const { pairs } = this.state;
     const { pairs } = this.state;
     return (
     return (
       <>
       <>
+        {pairs.length < 1 && (
+          <button className="gf-form-label gf-form-label--btn query-part" onClick={this.onAddFilter}>
+            <i className="fa fa-plus" />
+          </button>
+        )}
         {pairs.map((pair, index) => {
         {pairs.map((pair, index) => {
           const adHocKey = `adhoc-filter-${index}-${pair.key}-${pair.value}`;
           const adHocKey = `adhoc-filter-${index}-${pair.key}-${pair.value}`;
           return (
           return (
             <div className="align-items-center flex-grow-1" key={adHocKey}>
             <div className="align-items-center flex-grow-1" key={adHocKey}>
               <AdHocFilter
               <AdHocFilter
-                keys={pair.keys}
+                keys={[DEFAULT_REMOVE_FILTER_VALUE].concat(pair.keys)}
                 values={pair.values}
                 values={pair.values}
                 initialKey={pair.key}
                 initialKey={pair.key}
                 initialOperator={pair.operator}
                 initialOperator={pair.operator}