瀏覽代碼

Merge pull request #15382 from grafana/hugoh/bug-pressing-brackets-key-in-input-fields

Bug pressing special regexp chars in input fields
Torkel Ödegaard 6 年之前
父節點
當前提交
960fa56033

+ 51 - 0
public/app/core/components/FilterInput/FilterInput.tsx

@@ -0,0 +1,51 @@
+import React, { forwardRef } from 'react';
+
+const specialChars = ['(', '[', '{', '}', ']', ')', '|', '*', '+', '-', '.', '?', '<', '>', '#', '&', '^', '$'];
+
+export const escapeStringForRegex = (value: string) => {
+  if (!value) {
+    return value;
+  }
+
+  const newValue = specialChars.reduce(
+    (escaped, currentChar) => escaped.replace(currentChar, '\\' + currentChar),
+    value
+  );
+
+  return newValue;
+};
+
+export const unEscapeStringFromRegex = (value: string) => {
+  if (!value) {
+    return value;
+  }
+
+  const newValue = specialChars.reduce(
+    (escaped, currentChar) => escaped.replace('\\' + currentChar, currentChar),
+    value
+  );
+
+  return newValue;
+};
+
+export interface Props {
+  value: string | undefined;
+  placeholder?: string;
+  labelClassName?: string;
+  inputClassName?: string;
+  onChange: (value: string) => void;
+}
+
+export const FilterInput = forwardRef<HTMLInputElement, Props>((props, ref) => (
+  <label className={props.labelClassName}>
+    <input
+      ref={ref}
+      type="text"
+      className={props.inputClassName}
+      value={unEscapeStringFromRegex(props.value)}
+      onChange={event => props.onChange(escapeStringForRegex(event.target.value))}
+      placeholder={props.placeholder ? props.placeholder : null}
+    />
+    <i className="gf-form-input-icon fa fa-search" />
+  </label>
+));

+ 8 - 10
public/app/core/components/OrgActionBar/OrgActionBar.tsx

@@ -1,5 +1,6 @@
 import React, { PureComponent } from 'react';
 import React, { PureComponent } from 'react';
 import LayoutSelector, { LayoutMode } from '../LayoutSelector/LayoutSelector';
 import LayoutSelector, { LayoutMode } from '../LayoutSelector/LayoutSelector';
+import { FilterInput } from '../FilterInput/FilterInput';
 
 
 export interface Props {
 export interface Props {
   searchQuery: string;
   searchQuery: string;
@@ -22,16 +23,13 @@ export default class OrgActionBar extends PureComponent<Props> {
     return (
     return (
       <div className="page-action-bar">
       <div className="page-action-bar">
         <div className="gf-form gf-form--grow">
         <div className="gf-form gf-form--grow">
-          <label className="gf-form--has-input-icon">
-            <input
-              type="text"
-              className="gf-form-input width-20"
-              value={searchQuery}
-              onChange={event => setSearchQuery(event.target.value)}
-              placeholder="Filter by name or type"
-            />
-            <i className="gf-form-input-icon fa fa-search" />
-          </label>
+          <FilterInput
+            labelClassName="gf-form--has-input-icon"
+            inputClassName="gf-form-input width-20"
+            value={searchQuery}
+            onChange={setSearchQuery}
+            placeholder={'Filter by name or type'}
+          />
           <LayoutSelector mode={layoutMode} onLayoutModeChanged={(mode: LayoutMode) => onSetLayoutMode(mode)} />
           <LayoutSelector mode={layoutMode} onLayoutModeChanged={(mode: LayoutMode) => onSetLayoutMode(mode)} />
         </div>
         </div>
         <div className="page-action-bar__spacer" />
         <div className="page-action-bar__spacer" />

+ 7 - 14
public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap

@@ -7,20 +7,13 @@ exports[`Render should render component 1`] = `
   <div
   <div
     className="gf-form gf-form--grow"
     className="gf-form gf-form--grow"
   >
   >
-    <label
-      className="gf-form--has-input-icon"
-    >
-      <input
-        className="gf-form-input width-20"
-        onChange={[Function]}
-        placeholder="Filter by name or type"
-        type="text"
-        value=""
-      />
-      <i
-        className="gf-form-input-icon fa fa-search"
-      />
-    </label>
+    <ForwardRef
+      inputClassName="gf-form-input width-20"
+      labelClassName="gf-form--has-input-icon"
+      onChange={[MockFunction]}
+      placeholder="Filter by name or type"
+      value=""
+    />
     <LayoutSelector
     <LayoutSelector
       onLayoutModeChanged={[Function]}
       onLayoutModeChanged={[Function]}
     />
     />

+ 2 - 1
public/app/core/components/TagFilter/TagFilter.tsx

@@ -5,6 +5,7 @@ import AsyncSelect from '@torkelo/react-select/lib/Async';
 import { TagOption } from './TagOption';
 import { TagOption } from './TagOption';
 import { TagBadge } from './TagBadge';
 import { TagBadge } from './TagBadge';
 import { components } from '@torkelo/react-select';
 import { components } from '@torkelo/react-select';
+import { escapeStringForRegex } from '../FilterInput/FilterInput';
 
 
 export interface Props {
 export interface Props {
   tags: string[];
   tags: string[];
@@ -51,7 +52,7 @@ export class TagFilter extends React.Component<Props, any> {
       value: tags,
       value: tags,
       styles: resetSelectStyles(),
       styles: resetSelectStyles(),
       filterOption: (option, searchQuery) => {
       filterOption: (option, searchQuery) => {
-        const regex = RegExp(searchQuery, 'i');
+        const regex = RegExp(escapeStringForRegex(searchQuery), 'i');
         return regex.test(option.value);
         return regex.test(option.value);
       },
       },
       components: {
       components: {

+ 2 - 3
public/app/features/alerting/AlertRuleList.test.tsx

@@ -18,7 +18,7 @@ const setup = (propOverrides?: object) => {
     togglePauseAlertRule: jest.fn(),
     togglePauseAlertRule: jest.fn(),
     stateFilter: '',
     stateFilter: '',
     search: '',
     search: '',
-    isLoading: false
+    isLoading: false,
   };
   };
 
 
   Object.assign(props, propOverrides);
   Object.assign(props, propOverrides);
@@ -147,9 +147,8 @@ describe('Functions', () => {
   describe('Search query change', () => {
   describe('Search query change', () => {
     it('should set search query', () => {
     it('should set search query', () => {
       const { instance } = setup();
       const { instance } = setup();
-      const mockEvent = { target: { value: 'dashboard' } } as React.ChangeEvent<HTMLInputElement>;
 
 
-      instance.onSearchQueryChange(mockEvent);
+      instance.onSearchQueryChange('dashboard');
 
 
       expect(instance.props.setSearchQuery).toHaveBeenCalledWith('dashboard');
       expect(instance.props.setSearchQuery).toHaveBeenCalledWith('dashboard');
     });
     });

+ 11 - 14
public/app/features/alerting/AlertRuleList.tsx

@@ -9,6 +9,7 @@ import { getNavModel } from 'app/core/selectors/navModel';
 import { NavModel, StoreState, AlertRule } from 'app/types';
 import { NavModel, StoreState, AlertRule } from 'app/types';
 import { getAlertRulesAsync, setSearchQuery, togglePauseAlertRule } from './state/actions';
 import { getAlertRulesAsync, setSearchQuery, togglePauseAlertRule } from './state/actions';
 import { getAlertRuleItems, getSearchQuery } from './state/selectors';
 import { getAlertRuleItems, getSearchQuery } from './state/selectors';
+import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
 
 
 export interface Props {
 export interface Props {
   navModel: NavModel;
   navModel: NavModel;
@@ -69,8 +70,7 @@ export class AlertRuleList extends PureComponent<Props, any> {
     });
     });
   };
   };
 
 
-  onSearchQueryChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
-    const { value } = evt.target;
+  onSearchQueryChange = (value: string) => {
     this.props.setSearchQuery(value);
     this.props.setSearchQuery(value);
   };
   };
 
 
@@ -78,7 +78,7 @@ export class AlertRuleList extends PureComponent<Props, any> {
     this.props.togglePauseAlertRule(rule.id, { paused: rule.state !== 'paused' });
     this.props.togglePauseAlertRule(rule.id, { paused: rule.state !== 'paused' });
   };
   };
 
 
-  alertStateFilterOption = ({ text, value }: { text: string; value: string; }) => {
+  alertStateFilterOption = ({ text, value }: { text: string; value: string }) => {
     return (
     return (
       <option key={value} value={value}>
       <option key={value} value={value}>
         {text}
         {text}
@@ -94,16 +94,13 @@ export class AlertRuleList extends PureComponent<Props, any> {
         <Page.Contents isLoading={isLoading}>
         <Page.Contents isLoading={isLoading}>
           <div className="page-action-bar">
           <div className="page-action-bar">
             <div className="gf-form gf-form--grow">
             <div className="gf-form gf-form--grow">
-              <label className="gf-form--has-input-icon gf-form--grow">
-                <input
-                  type="text"
-                  className="gf-form-input"
-                  placeholder="Search alerts"
-                  value={search}
-                  onChange={this.onSearchQueryChange}
-                />
-                <i className="gf-form-input-icon fa fa-search" />
-              </label>
+              <FilterInput
+                labelClassName="gf-form--has-input-icon gf-form--grow"
+                inputClassName="gf-form-input"
+                placeholder="Search alerts"
+                value={search}
+                onChange={this.onSearchQueryChange}
+              />
             </div>
             </div>
             <div className="gf-form">
             <div className="gf-form">
               <label className="gf-form-label">States</label>
               <label className="gf-form-label">States</label>
@@ -142,7 +139,7 @@ const mapStateToProps = (state: StoreState) => ({
   alertRules: getAlertRuleItems(state.alertRules),
   alertRules: getAlertRuleItems(state.alertRules),
   stateFilter: state.location.query.state,
   stateFilter: state.location.query.state,
   search: getSearchQuery(state.alertRules),
   search: getSearchQuery(state.alertRules),
-  isLoading: state.alertRules.isLoading
+  isLoading: state.alertRules.isLoading,
 });
 });
 
 
 const mapDispatchToProps = {
 const mapDispatchToProps = {

+ 14 - 28
public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap

@@ -13,20 +13,13 @@ exports[`Render should render alert rules 1`] = `
       <div
       <div
         className="gf-form gf-form--grow"
         className="gf-form gf-form--grow"
       >
       >
-        <label
-          className="gf-form--has-input-icon gf-form--grow"
-        >
-          <input
-            className="gf-form-input"
-            onChange={[Function]}
-            placeholder="Search alerts"
-            type="text"
-            value=""
-          />
-          <i
-            className="gf-form-input-icon fa fa-search"
-          />
-        </label>
+        <ForwardRef
+          inputClassName="gf-form-input"
+          labelClassName="gf-form--has-input-icon gf-form--grow"
+          onChange={[Function]}
+          placeholder="Search alerts"
+          value=""
+        />
       </div>
       </div>
       <div
       <div
         className="gf-form"
         className="gf-form"
@@ -167,20 +160,13 @@ exports[`Render should render component 1`] = `
       <div
       <div
         className="gf-form gf-form--grow"
         className="gf-form gf-form--grow"
       >
       >
-        <label
-          className="gf-form--has-input-icon gf-form--grow"
-        >
-          <input
-            className="gf-form-input"
-            onChange={[Function]}
-            placeholder="Search alerts"
-            type="text"
-            value=""
-          />
-          <i
-            className="gf-form-input-icon fa fa-search"
-          />
-        </label>
+        <ForwardRef
+          inputClassName="gf-form-input"
+          labelClassName="gf-form--has-input-icon gf-form--grow"
+          onChange={[Function]}
+          placeholder="Search alerts"
+          value=""
+        />
       </div>
       </div>
       <div
       <div
         className="gf-form"
         className="gf-form"

+ 4 - 5
public/app/features/api-keys/ApiKeysPage.test.tsx

@@ -8,11 +8,11 @@ const setup = (propOverrides?: object) => {
   const props: Props = {
   const props: Props = {
     navModel: {
     navModel: {
       main: {
       main: {
-        text: 'Configuration'
+        text: 'Configuration',
       },
       },
       node: {
       node: {
-        text: 'Api Keys'
-      }
+        text: 'Api Keys',
+      },
     } as NavModel,
     } as NavModel,
     apiKeys: [] as ApiKey[],
     apiKeys: [] as ApiKey[],
     searchQuery: '',
     searchQuery: '',
@@ -78,9 +78,8 @@ describe('Functions', () => {
   describe('on search query change', () => {
   describe('on search query change', () => {
     it('should call setSearchQuery', () => {
     it('should call setSearchQuery', () => {
       const { instance } = setup();
       const { instance } = setup();
-      const mockEvent = { target: { value: 'test' } };
 
 
-      instance.onSearchQueryChange(mockEvent);
+      instance.onSearchQueryChange('test');
 
 
       expect(instance.props.setSearchQuery).toHaveBeenCalledWith('test');
       expect(instance.props.setSearchQuery).toHaveBeenCalledWith('test');
     });
     });

+ 11 - 19
public/app/features/api-keys/ApiKeysPage.tsx

@@ -13,6 +13,7 @@ import config from 'app/core/config';
 import appEvents from 'app/core/app_events';
 import appEvents from 'app/core/app_events';
 import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
 import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
 import { DeleteButton } from '@grafana/ui';
 import { DeleteButton } from '@grafana/ui';
+import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
 
 
 export interface Props {
 export interface Props {
   navModel: NavModel;
   navModel: NavModel;
@@ -59,8 +60,8 @@ export class ApiKeysPage extends PureComponent<Props, any> {
     this.props.deleteApiKey(key.id);
     this.props.deleteApiKey(key.id);
   }
   }
 
 
-  onSearchQueryChange = evt => {
-    this.props.setSearchQuery(evt.target.value);
+  onSearchQueryChange = (value: string) => {
+    this.props.setSearchQuery(value);
   };
   };
 
 
   onToggleAdding = () => {
   onToggleAdding = () => {
@@ -186,16 +187,13 @@ export class ApiKeysPage extends PureComponent<Props, any> {
       <>
       <>
         <div className="page-action-bar">
         <div className="page-action-bar">
           <div className="gf-form gf-form--grow">
           <div className="gf-form gf-form--grow">
-            <label className="gf-form--has-input-icon gf-form--grow">
-              <input
-                type="text"
-                className="gf-form-input"
-                placeholder="Search keys"
-                value={searchQuery}
-                onChange={this.onSearchQueryChange}
-              />
-              <i className="gf-form-input-icon fa fa-search" />
-            </label>
+            <FilterInput
+              labelClassName="gf-form--has-input-icon gf-form--grow"
+              inputClassName="gf-form-input"
+              placeholder="Search keys"
+              value={searchQuery}
+              onChange={this.onSearchQueryChange}
+            />
           </div>
           </div>
 
 
           <div className="page-action-bar__spacer" />
           <div className="page-action-bar__spacer" />
@@ -241,13 +239,7 @@ export class ApiKeysPage extends PureComponent<Props, any> {
     return (
     return (
       <Page navModel={navModel}>
       <Page navModel={navModel}>
         <Page.Contents isLoading={!hasFetched}>
         <Page.Contents isLoading={!hasFetched}>
-          {hasFetched && (
-            apiKeysCount > 0 ? (
-              this.renderApiKeyList()
-            ) : (
-              this.renderEmptyList()
-            )
-          )}
+          {hasFetched && (apiKeysCount > 0 ? this.renderApiKeyList() : this.renderEmptyList())}
         </Page.Contents>
         </Page.Contents>
       </Page>
       </Page>
     );
     );

+ 10 - 13
public/app/features/dashboard/panel_editor/VisualizationTab.tsx

@@ -17,6 +17,7 @@ import { FadeIn } from 'app/core/components/Animations/FadeIn';
 import { PanelModel } from '../state/PanelModel';
 import { PanelModel } from '../state/PanelModel';
 import { DashboardModel } from '../state/DashboardModel';
 import { DashboardModel } from '../state/DashboardModel';
 import { PanelPlugin } from 'app/types/plugins';
 import { PanelPlugin } from 'app/types/plugins';
+import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
 
 
 interface Props {
 interface Props {
   panel: PanelModel;
   panel: PanelModel;
@@ -170,8 +171,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
     this.setState({ isVizPickerOpen: false });
     this.setState({ isVizPickerOpen: false });
   };
   };
 
 
-  onSearchQueryChange = evt => {
-    const value = evt.target.value;
+  onSearchQueryChange = (value: string) => {
     this.setState({
     this.setState({
       searchQuery: value,
       searchQuery: value,
     });
     });
@@ -184,17 +184,14 @@ export class VisualizationTab extends PureComponent<Props, State> {
     if (this.state.isVizPickerOpen) {
     if (this.state.isVizPickerOpen) {
       return (
       return (
         <>
         <>
-          <label className="gf-form--has-input-icon">
-            <input
-              type="text"
-              className="gf-form-input width-13"
-              placeholder=""
-              onChange={this.onSearchQueryChange}
-              value={searchQuery}
-              ref={elem => elem && elem.focus()}
-            />
-            <i className="gf-form-input-icon fa fa-search" />
-          </label>
+          <FilterInput
+            labelClassName="gf-form--has-input-icon"
+            inputClassName="gf-form-input width-13"
+            placeholder=""
+            onChange={this.onSearchQueryChange}
+            value={searchQuery}
+            ref={elem => elem && elem.focus()}
+          />
           <button className="btn btn-link toolbar__close" onClick={this.onCloseVizPicker}>
           <button className="btn btn-link toolbar__close" onClick={this.onCloseVizPicker}>
             <i className="fa fa-chevron-up" />
             <i className="fa fa-chevron-up" />
           </button>
           </button>

+ 10 - 12
public/app/features/datasources/NewDataSourcePage.tsx

@@ -6,6 +6,7 @@ import { NavModel, Plugin, StoreState } from 'app/types';
 import { addDataSource, loadDataSourceTypes, setDataSourceTypeSearchQuery } from './state/actions';
 import { addDataSource, loadDataSourceTypes, setDataSourceTypeSearchQuery } from './state/actions';
 import { getNavModel } from 'app/core/selectors/navModel';
 import { getNavModel } from 'app/core/selectors/navModel';
 import { getDataSourceTypes } from './state/selectors';
 import { getDataSourceTypes } from './state/selectors';
+import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
 
 
 export interface Props {
 export interface Props {
   navModel: NavModel;
   navModel: NavModel;
@@ -26,8 +27,8 @@ class NewDataSourcePage extends PureComponent<Props> {
     this.props.addDataSource(plugin);
     this.props.addDataSource(plugin);
   };
   };
 
 
-  onSearchQueryChange = (event: React.ChangeEvent<HTMLInputElement>) => {
-    this.props.setDataSourceTypeSearchQuery(event.target.value);
+  onSearchQueryChange = (value: string) => {
+    this.props.setDataSourceTypeSearchQuery(value);
   };
   };
 
 
   render() {
   render() {
@@ -37,16 +38,13 @@ class NewDataSourcePage extends PureComponent<Props> {
         <Page.Contents isLoading={isLoading}>
         <Page.Contents isLoading={isLoading}>
           <h2 className="add-data-source-header">Choose data source type</h2>
           <h2 className="add-data-source-header">Choose data source type</h2>
           <div className="add-data-source-search">
           <div className="add-data-source-search">
-            <label className="gf-form--has-input-icon">
-              <input
-                type="text"
-                className="gf-form-input width-20"
-                value={dataSourceTypeSearchQuery}
-                onChange={this.onSearchQueryChange}
-                placeholder="Filter by name or type"
-              />
-              <i className="gf-form-input-icon fa fa-search" />
-            </label>
+            <FilterInput
+              labelClassName="gf-form--has-input-icon"
+              inputClassName="gf-form-input width-20"
+              value={dataSourceTypeSearchQuery}
+              onChange={this.onSearchQueryChange}
+              placeholder="Filter by name or type"
+            />
           </div>
           </div>
           <div className="add-data-source-grid">
           <div className="add-data-source-grid">
             {dataSourceTypes.map((plugin, index) => {
             {dataSourceTypes.map((plugin, index) => {

+ 1 - 7
public/app/features/explore/Logs.tsx

@@ -5,13 +5,7 @@ import * as rangeUtil from 'app/core/utils/rangeutil';
 import { RawTimeRange, Switch } from '@grafana/ui';
 import { RawTimeRange, Switch } from '@grafana/ui';
 import TimeSeries from 'app/core/time_series2';
 import TimeSeries from 'app/core/time_series2';
 
 
-import {
-  LogsDedupDescription,
-  LogsDedupStrategy,
-  LogsModel,
-  LogLevel,
-  LogsMetaKind,
-} from 'app/core/logs_model';
+import { LogsDedupDescription, LogsDedupStrategy, LogsModel, LogLevel, LogsMetaKind } from 'app/core/logs_model';
 
 
 import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup';
 import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup';
 
 

+ 2 - 4
public/app/features/explore/state/actionTypes.ts

@@ -193,7 +193,7 @@ export interface ToggleLogsPayload {
   exploreId: ExploreId;
   exploreId: ExploreId;
 }
 }
 
 
-export interface UpdateUIStatePayload extends Partial<ExploreUIState>{
+export interface UpdateUIStatePayload extends Partial<ExploreUIState> {
   exploreId: ExploreId;
   exploreId: ExploreId;
 }
 }
 
 
@@ -403,9 +403,7 @@ export const updateDatasourceInstanceAction = actionCreatorFactory<UpdateDatasou
   'explore/UPDATE_DATASOURCE_INSTANCE'
   'explore/UPDATE_DATASOURCE_INSTANCE'
 ).create();
 ).create();
 
 
-export const toggleLogLevelAction = actionCreatorFactory<ToggleLogLevelPayload>(
-  'explore/TOGGLE_LOG_LEVEL'
-).create();
+export const toggleLogLevelAction = actionCreatorFactory<ToggleLogLevelPayload>('explore/TOGGLE_LOG_LEVEL').create();
 
 
 /**
 /**
  * Resets state for explore.
  * Resets state for explore.

+ 1 - 1
public/app/features/explore/state/reducers.ts

@@ -474,7 +474,7 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
       const { hiddenLogLevels } = action.payload;
       const { hiddenLogLevels } = action.payload;
       return {
       return {
         ...state,
         ...state,
-        hiddenLogLevels: Array.from(hiddenLogLevels)
+        hiddenLogLevels: Array.from(hiddenLogLevels),
       };
       };
     },
     },
   })
   })

+ 4 - 5
public/app/features/teams/TeamList.test.tsx

@@ -8,11 +8,11 @@ const setup = (propOverrides?: object) => {
   const props: Props = {
   const props: Props = {
     navModel: {
     navModel: {
       main: {
       main: {
-        text: 'Configuration'
+        text: 'Configuration',
       },
       },
       node: {
       node: {
-        text: 'Team List'
-      }
+        text: 'Team List',
+      },
     } as NavModel,
     } as NavModel,
     teams: [] as Team[],
     teams: [] as Team[],
     loadTeams: jest.fn(),
     loadTeams: jest.fn(),
@@ -74,9 +74,8 @@ describe('Functions', () => {
   describe('on search query change', () => {
   describe('on search query change', () => {
     it('should call setSearchQuery', () => {
     it('should call setSearchQuery', () => {
       const { instance } = setup();
       const { instance } = setup();
-      const mockEvent = { target: { value: 'test' } };
 
 
-      instance.onSearchQueryChange(mockEvent);
+      instance.onSearchQueryChange('test');
 
 
       expect(instance.props.setSearchQuery).toHaveBeenCalledWith('test');
       expect(instance.props.setSearchQuery).toHaveBeenCalledWith('test');
     });
     });

+ 11 - 15
public/app/features/teams/TeamList.tsx

@@ -8,6 +8,7 @@ import { NavModel, Team } from 'app/types';
 import { loadTeams, deleteTeam, setSearchQuery } from './state/actions';
 import { loadTeams, deleteTeam, setSearchQuery } from './state/actions';
 import { getSearchQuery, getTeams, getTeamsCount } from './state/selectors';
 import { getSearchQuery, getTeams, getTeamsCount } from './state/selectors';
 import { getNavModel } from 'app/core/selectors/navModel';
 import { getNavModel } from 'app/core/selectors/navModel';
+import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
 
 
 export interface Props {
 export interface Props {
   navModel: NavModel;
   navModel: NavModel;
@@ -33,8 +34,8 @@ export class TeamList extends PureComponent<Props, any> {
     this.props.deleteTeam(team.id);
     this.props.deleteTeam(team.id);
   };
   };
 
 
-  onSearchQueryChange = event => {
-    this.props.setSearchQuery(event.target.value);
+  onSearchQueryChange = (value: string) => {
+    this.props.setSearchQuery(value);
   };
   };
 
 
   renderTeam(team: Team) {
   renderTeam(team: Team) {
@@ -89,16 +90,13 @@ export class TeamList extends PureComponent<Props, any> {
       <>
       <>
         <div className="page-action-bar">
         <div className="page-action-bar">
           <div className="gf-form gf-form--grow">
           <div className="gf-form gf-form--grow">
-            <label className="gf-form--has-input-icon gf-form--grow">
-              <input
-                type="text"
-                className="gf-form-input"
-                placeholder="Search teams"
-                value={searchQuery}
-                onChange={this.onSearchQueryChange}
-              />
-              <i className="gf-form-input-icon fa fa-search" />
-            </label>
+            <FilterInput
+              labelClassName="gf-form--has-input-icon gf-form--grow"
+              inputClassName="gf-form-input"
+              placeholder="Search teams"
+              value={searchQuery}
+              onChange={this.onSearchQueryChange}
+            />
           </div>
           </div>
 
 
           <div className="page-action-bar__spacer" />
           <div className="page-action-bar__spacer" />
@@ -141,9 +139,7 @@ export class TeamList extends PureComponent<Props, any> {
 
 
     return (
     return (
       <Page navModel={navModel}>
       <Page navModel={navModel}>
-        <Page.Contents isLoading={!hasFetched}>
-          {hasFetched && this.renderList()}
-        </Page.Contents>
+        <Page.Contents isLoading={!hasFetched}>{hasFetched && this.renderList()}</Page.Contents>
       </Page>
       </Page>
     );
     );
   }
   }

+ 1 - 2
public/app/features/teams/TeamMembers.test.tsx

@@ -55,9 +55,8 @@ describe('Functions', () => {
   describe('on search member query change', () => {
   describe('on search member query change', () => {
     it('it should call setSearchMemberQuery', () => {
     it('it should call setSearchMemberQuery', () => {
       const { instance } = setup();
       const { instance } = setup();
-      const mockEvent = { target: { value: 'member' } };
 
 
-      instance.onSearchQueryChange(mockEvent);
+      instance.onSearchQueryChange('member');
 
 
       expect(instance.props.setSearchMemberQuery).toHaveBeenCalledWith('member');
       expect(instance.props.setSearchMemberQuery).toHaveBeenCalledWith('member');
     });
     });

+ 10 - 12
public/app/features/teams/TeamMembers.tsx

@@ -7,6 +7,7 @@ import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
 import { TeamMember, User } from 'app/types';
 import { TeamMember, User } from 'app/types';
 import { loadTeamMembers, addTeamMember, removeTeamMember, setSearchMemberQuery } from './state/actions';
 import { loadTeamMembers, addTeamMember, removeTeamMember, setSearchMemberQuery } from './state/actions';
 import { getSearchMemberQuery, getTeamMembers } from './state/selectors';
 import { getSearchMemberQuery, getTeamMembers } from './state/selectors';
+import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
 
 
 export interface Props {
 export interface Props {
   members: TeamMember[];
   members: TeamMember[];
@@ -33,8 +34,8 @@ export class TeamMembers extends PureComponent<Props, State> {
     this.props.loadTeamMembers();
     this.props.loadTeamMembers();
   }
   }
 
 
-  onSearchQueryChange = event => {
-    this.props.setSearchMemberQuery(event.target.value);
+  onSearchQueryChange = (value: string) => {
+    this.props.setSearchMemberQuery(value);
   };
   };
 
 
   onRemoveMember(member: TeamMember) {
   onRemoveMember(member: TeamMember) {
@@ -89,16 +90,13 @@ export class TeamMembers extends PureComponent<Props, State> {
       <div>
       <div>
         <div className="page-action-bar">
         <div className="page-action-bar">
           <div className="gf-form gf-form--grow">
           <div className="gf-form gf-form--grow">
-            <label className="gf-form--has-input-icon gf-form--grow">
-              <input
-                type="text"
-                className="gf-form-input"
-                placeholder="Search members"
-                value={searchMemberQuery}
-                onChange={this.onSearchQueryChange}
-              />
-              <i className="gf-form-input-icon fa fa-search" />
-            </label>
+            <FilterInput
+              labelClassName="gf-form--has-input-icon gf-form--grow"
+              inputClassName="gf-form-input"
+              placeholder="Search members"
+              value={searchMemberQuery}
+              onChange={this.onSearchQueryChange}
+            />
           </div>
           </div>
 
 
           <div className="page-action-bar__spacer" />
           <div className="page-action-bar__spacer" />

+ 7 - 14
public/app/features/teams/__snapshots__/TeamList.test.tsx.snap

@@ -41,20 +41,13 @@ exports[`Render should render teams table 1`] = `
       <div
       <div
         className="gf-form gf-form--grow"
         className="gf-form gf-form--grow"
       >
       >
-        <label
-          className="gf-form--has-input-icon gf-form--grow"
-        >
-          <input
-            className="gf-form-input"
-            onChange={[Function]}
-            placeholder="Search teams"
-            type="text"
-            value=""
-          />
-          <i
-            className="gf-form-input-icon fa fa-search"
-          />
-        </label>
+        <ForwardRef
+          inputClassName="gf-form-input"
+          labelClassName="gf-form--has-input-icon gf-form--grow"
+          onChange={[Function]}
+          placeholder="Search teams"
+          value=""
+        />
       </div>
       </div>
       <div
       <div
         className="page-action-bar__spacer"
         className="page-action-bar__spacer"

+ 21 - 42
public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap

@@ -8,20 +8,13 @@ exports[`Render should render component 1`] = `
     <div
     <div
       className="gf-form gf-form--grow"
       className="gf-form gf-form--grow"
     >
     >
-      <label
-        className="gf-form--has-input-icon gf-form--grow"
-      >
-        <input
-          className="gf-form-input"
-          onChange={[Function]}
-          placeholder="Search members"
-          type="text"
-          value=""
-        />
-        <i
-          className="gf-form-input-icon fa fa-search"
-        />
-      </label>
+      <ForwardRef
+        inputClassName="gf-form-input"
+        labelClassName="gf-form--has-input-icon gf-form--grow"
+        onChange={[Function]}
+        placeholder="Search members"
+        value=""
+      />
     </div>
     </div>
     <div
     <div
       className="page-action-bar__spacer"
       className="page-action-bar__spacer"
@@ -102,20 +95,13 @@ exports[`Render should render team members 1`] = `
     <div
     <div
       className="gf-form gf-form--grow"
       className="gf-form gf-form--grow"
     >
     >
-      <label
-        className="gf-form--has-input-icon gf-form--grow"
-      >
-        <input
-          className="gf-form-input"
-          onChange={[Function]}
-          placeholder="Search members"
-          type="text"
-          value=""
-        />
-        <i
-          className="gf-form-input-icon fa fa-search"
-        />
-      </label>
+      <ForwardRef
+        inputClassName="gf-form-input"
+        labelClassName="gf-form--has-input-icon gf-form--grow"
+        onChange={[Function]}
+        placeholder="Search members"
+        value=""
+      />
     </div>
     </div>
     <div
     <div
       className="page-action-bar__spacer"
       className="page-action-bar__spacer"
@@ -322,20 +308,13 @@ exports[`Render should render team members when sync enabled 1`] = `
     <div
     <div
       className="gf-form gf-form--grow"
       className="gf-form gf-form--grow"
     >
     >
-      <label
-        className="gf-form--has-input-icon gf-form--grow"
-      >
-        <input
-          className="gf-form-input"
-          onChange={[Function]}
-          placeholder="Search members"
-          type="text"
-          value=""
-        />
-        <i
-          className="gf-form-input-icon fa fa-search"
-        />
-      </label>
+      <ForwardRef
+        inputClassName="gf-form-input"
+        labelClassName="gf-form--has-input-icon gf-form--grow"
+        onChange={[Function]}
+        placeholder="Search members"
+        value=""
+      />
     </div>
     </div>
     <div
     <div
       className="page-action-bar__spacer"
       className="page-action-bar__spacer"

+ 8 - 10
public/app/features/users/UsersActionBar.tsx

@@ -3,6 +3,7 @@ import { connect } from 'react-redux';
 import classNames from 'classnames';
 import classNames from 'classnames';
 import { setUsersSearchQuery } from './state/actions';
 import { setUsersSearchQuery } from './state/actions';
 import { getInviteesCount, getUsersSearchQuery } from './state/selectors';
 import { getInviteesCount, getUsersSearchQuery } from './state/selectors';
+import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
 
 
 export interface Props {
 export interface Props {
   searchQuery: string;
   searchQuery: string;
@@ -43,16 +44,13 @@ export class UsersActionBar extends PureComponent<Props> {
     return (
     return (
       <div className="page-action-bar">
       <div className="page-action-bar">
         <div className="gf-form gf-form--grow">
         <div className="gf-form gf-form--grow">
-          <label className="gf-form--has-input-icon">
-            <input
-              type="text"
-              className="gf-form-input width-20"
-              value={searchQuery}
-              onChange={event => setUsersSearchQuery(event.target.value)}
-              placeholder="Filter by name or type"
-            />
-            <i className="gf-form-input-icon fa fa-search" />
-          </label>
+          <FilterInput
+            labelClassName="gf-form--has-input-icon"
+            inputClassName="gf-form-input width-20"
+            value={searchQuery}
+            onChange={setUsersSearchQuery}
+            placeholder="Filter by name or type"
+          />
           {pendingInvitesCount > 0 && (
           {pendingInvitesCount > 0 && (
             <div style={{ marginLeft: '1rem' }}>
             <div style={{ marginLeft: '1rem' }}>
               <button className={usersButtonStyle} key="users" onClick={onShowInvites}>
               <button className={usersButtonStyle} key="users" onClick={onShowInvites}>

+ 28 - 56
public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap

@@ -7,20 +7,13 @@ exports[`Render should render component 1`] = `
   <div
   <div
     className="gf-form gf-form--grow"
     className="gf-form gf-form--grow"
   >
   >
-    <label
-      className="gf-form--has-input-icon"
-    >
-      <input
-        className="gf-form-input width-20"
-        onChange={[Function]}
-        placeholder="Filter by name or type"
-        type="text"
-        value=""
-      />
-      <i
-        className="gf-form-input-icon fa fa-search"
-      />
-    </label>
+    <ForwardRef
+      inputClassName="gf-form-input width-20"
+      labelClassName="gf-form--has-input-icon"
+      onChange={[MockFunction]}
+      placeholder="Filter by name or type"
+      value=""
+    />
     <div
     <div
       className="page-action-bar__spacer"
       className="page-action-bar__spacer"
     />
     />
@@ -35,20 +28,13 @@ exports[`Render should render pending invites button 1`] = `
   <div
   <div
     className="gf-form gf-form--grow"
     className="gf-form gf-form--grow"
   >
   >
-    <label
-      className="gf-form--has-input-icon"
-    >
-      <input
-        className="gf-form-input width-20"
-        onChange={[Function]}
-        placeholder="Filter by name or type"
-        type="text"
-        value=""
-      />
-      <i
-        className="gf-form-input-icon fa fa-search"
-      />
-    </label>
+    <ForwardRef
+      inputClassName="gf-form-input width-20"
+      labelClassName="gf-form--has-input-icon"
+      onChange={[MockFunction]}
+      placeholder="Filter by name or type"
+      value=""
+    />
     <div
     <div
       style={
       style={
         Object {
         Object {
@@ -87,20 +73,13 @@ exports[`Render should show external user management button 1`] = `
   <div
   <div
     className="gf-form gf-form--grow"
     className="gf-form gf-form--grow"
   >
   >
-    <label
-      className="gf-form--has-input-icon"
-    >
-      <input
-        className="gf-form-input width-20"
-        onChange={[Function]}
-        placeholder="Filter by name or type"
-        type="text"
-        value=""
-      />
-      <i
-        className="gf-form-input-icon fa fa-search"
-      />
-    </label>
+    <ForwardRef
+      inputClassName="gf-form-input width-20"
+      labelClassName="gf-form--has-input-icon"
+      onChange={[MockFunction]}
+      placeholder="Filter by name or type"
+      value=""
+    />
     <div
     <div
       className="page-action-bar__spacer"
       className="page-action-bar__spacer"
     />
     />
@@ -125,20 +104,13 @@ exports[`Render should show invite button 1`] = `
   <div
   <div
     className="gf-form gf-form--grow"
     className="gf-form gf-form--grow"
   >
   >
-    <label
-      className="gf-form--has-input-icon"
-    >
-      <input
-        className="gf-form-input width-20"
-        onChange={[Function]}
-        placeholder="Filter by name or type"
-        type="text"
-        value=""
-      />
-      <i
-        className="gf-form-input-icon fa fa-search"
-      />
-    </label>
+    <ForwardRef
+      inputClassName="gf-form-input width-20"
+      labelClassName="gf-form--has-input-icon"
+      onChange={[MockFunction]}
+      placeholder="Filter by name or type"
+      value=""
+    />
     <div
     <div
       className="page-action-bar__spacer"
       className="page-action-bar__spacer"
     />
     />