mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Fixes bug #12972 with a new type of input that escapes and unescapes special regexp characters
This commit is contained in:
parent
1407691de2
commit
5388541fd7
@ -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 { RegExpSafeInput } from '../RegExpSafeInput/RegExpSafeInput';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
@ -23,12 +24,11 @@ export default class OrgActionBar extends PureComponent<Props> {
|
|||||||
<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">
|
<label className="gf-form--has-input-icon">
|
||||||
<input
|
<RegExpSafeInput
|
||||||
type="text"
|
className={'gf-form-input width-20'}
|
||||||
className="gf-form-input width-20"
|
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={event => setSearchQuery(event.target.value)}
|
onChange={setSearchQuery}
|
||||||
placeholder="Filter by name or type"
|
placeholder={'Filter by name or type'}
|
||||||
/>
|
/>
|
||||||
<i className="gf-form-input-icon fa fa-search" />
|
<i className="gf-form-input-icon fa fa-search" />
|
||||||
</label>
|
</label>
|
||||||
|
@ -10,11 +10,10 @@ exports[`Render should render component 1`] = `
|
|||||||
<label
|
<label
|
||||||
className="gf-form--has-input-icon"
|
className="gf-form--has-input-icon"
|
||||||
>
|
>
|
||||||
<input
|
<ForwardRef
|
||||||
className="gf-form-input width-20"
|
className="gf-form-input width-20"
|
||||||
onChange={[Function]}
|
onChange={[MockFunction]}
|
||||||
placeholder="Filter by name or type"
|
placeholder="Filter by name or type"
|
||||||
type="text"
|
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
import React, { ChangeEvent, forwardRef } from 'react';
|
||||||
|
|
||||||
|
const specialChars = ['(', '[', '{', '}', ']', ')', '|', '*', '+', '-', '.', '?', '<', '>', '#', '&', '^', '$'];
|
||||||
|
|
||||||
|
export const escapeStringForRegex = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = event.target.value;
|
||||||
|
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;
|
||||||
|
className?: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RegExpSafeInput = forwardRef<HTMLInputElement, Props>((props, ref) => (
|
||||||
|
<input
|
||||||
|
ref={ref}
|
||||||
|
type="text"
|
||||||
|
className={props.className}
|
||||||
|
value={unEscapeStringFromRegex(props.value)}
|
||||||
|
onChange={event => props.onChange(escapeStringForRegex(event))}
|
||||||
|
placeholder={props.placeholder ? props.placeholder : null}
|
||||||
|
/>
|
||||||
|
));
|
@ -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');
|
||||||
});
|
});
|
||||||
|
@ -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 { RegExpSafeInput } from 'app/core/components/RegExpSafeInput/RegExpSafeInput';
|
||||||
|
|
||||||
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>) => {
|
onSearchQueryChange = (value: string) => {
|
||||||
const { value } = evt.target;
|
|
||||||
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}
|
||||||
@ -95,8 +95,7 @@ export class AlertRuleList 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">
|
<label className="gf-form--has-input-icon gf-form--grow">
|
||||||
<input
|
<RegExpSafeInput
|
||||||
type="text"
|
|
||||||
className="gf-form-input"
|
className="gf-form-input"
|
||||||
placeholder="Search alerts"
|
placeholder="Search alerts"
|
||||||
value={search}
|
value={search}
|
||||||
@ -142,7 +141,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 = {
|
||||||
|
@ -16,11 +16,10 @@ exports[`Render should render alert rules 1`] = `
|
|||||||
<label
|
<label
|
||||||
className="gf-form--has-input-icon gf-form--grow"
|
className="gf-form--has-input-icon gf-form--grow"
|
||||||
>
|
>
|
||||||
<input
|
<ForwardRef
|
||||||
className="gf-form-input"
|
className="gf-form-input"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
placeholder="Search alerts"
|
placeholder="Search alerts"
|
||||||
type="text"
|
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
@ -170,11 +169,10 @@ exports[`Render should render component 1`] = `
|
|||||||
<label
|
<label
|
||||||
className="gf-form--has-input-icon gf-form--grow"
|
className="gf-form--has-input-icon gf-form--grow"
|
||||||
>
|
>
|
||||||
<input
|
<ForwardRef
|
||||||
className="gf-form-input"
|
className="gf-form-input"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
placeholder="Search alerts"
|
placeholder="Search alerts"
|
||||||
type="text"
|
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
|
@ -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');
|
||||||
});
|
});
|
||||||
|
@ -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 { RegExpSafeInput } from 'app/core/components/RegExpSafeInput/RegExpSafeInput';
|
||||||
|
|
||||||
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 => {
|
onSearchQueryChange = (value: string) => {
|
||||||
this.props.setSearchQuery(evt.target.value);
|
this.props.setSearchQuery(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
onToggleAdding = () => {
|
onToggleAdding = () => {
|
||||||
@ -187,8 +188,7 @@ 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">
|
<label className="gf-form--has-input-icon gf-form--grow">
|
||||||
<input
|
<RegExpSafeInput
|
||||||
type="text"
|
|
||||||
className="gf-form-input"
|
className="gf-form-input"
|
||||||
placeholder="Search keys"
|
placeholder="Search keys"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
@ -241,13 +241,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 && (
|
{hasFetched && (apiKeysCount > 0 ? this.renderApiKeyList() : this.renderEmptyList())}
|
||||||
apiKeysCount > 0 ? (
|
|
||||||
this.renderApiKeyList()
|
|
||||||
) : (
|
|
||||||
this.renderEmptyList()
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Page.Contents>
|
</Page.Contents>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
@ -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 { RegExpSafeInput } from 'app/core/components/RegExpSafeInput/RegExpSafeInput';
|
||||||
|
|
||||||
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 => {
|
onSearchQueryChange = (value: string) => {
|
||||||
const value = evt.target.value;
|
|
||||||
this.setState({
|
this.setState({
|
||||||
searchQuery: value,
|
searchQuery: value,
|
||||||
});
|
});
|
||||||
@ -185,8 +185,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<label className="gf-form--has-input-icon">
|
<label className="gf-form--has-input-icon">
|
||||||
<input
|
<RegExpSafeInput
|
||||||
type="text"
|
|
||||||
className="gf-form-input width-13"
|
className="gf-form-input width-13"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
onChange={this.onSearchQueryChange}
|
onChange={this.onSearchQueryChange}
|
||||||
|
@ -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 { RegExpSafeInput } from 'app/core/components/RegExpSafeInput/RegExpSafeInput';
|
||||||
|
|
||||||
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>) => {
|
onSearchQueryChange = (value: string) => {
|
||||||
this.props.setDataSourceTypeSearchQuery(event.target.value);
|
this.props.setDataSourceTypeSearchQuery(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -39,8 +40,7 @@ class NewDataSourcePage extends PureComponent<Props> {
|
|||||||
<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">
|
<label className="gf-form--has-input-icon">
|
||||||
<input
|
<RegExpSafeInput
|
||||||
type="text"
|
|
||||||
className="gf-form-input width-20"
|
className="gf-form-input width-20"
|
||||||
value={dataSourceTypeSearchQuery}
|
value={dataSourceTypeSearchQuery}
|
||||||
onChange={this.onSearchQueryChange}
|
onChange={this.onSearchQueryChange}
|
||||||
@ -74,7 +74,7 @@ function mapStateToProps(state: StoreState) {
|
|||||||
return {
|
return {
|
||||||
navModel: getNavModel(state.navIndex, 'datasources'),
|
navModel: getNavModel(state.navIndex, 'datasources'),
|
||||||
dataSourceTypes: getDataSourceTypes(state.dataSources),
|
dataSourceTypes: getDataSourceTypes(state.dataSources),
|
||||||
isLoading: state.dataSources.isLoadingDataSources
|
isLoading: state.dataSources.isLoadingDataSources,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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');
|
||||||
});
|
});
|
||||||
|
@ -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 { RegExpSafeInput } from 'app/core/components/RegExpSafeInput/RegExpSafeInput';
|
||||||
|
|
||||||
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 => {
|
onSearchQueryChange = (value: string) => {
|
||||||
this.props.setSearchQuery(event.target.value);
|
this.props.setSearchQuery(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderTeam(team: Team) {
|
renderTeam(team: Team) {
|
||||||
@ -90,8 +91,7 @@ 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">
|
<label className="gf-form--has-input-icon gf-form--grow">
|
||||||
<input
|
<RegExpSafeInput
|
||||||
type="text"
|
|
||||||
className="gf-form-input"
|
className="gf-form-input"
|
||||||
placeholder="Search teams"
|
placeholder="Search teams"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
@ -141,9 +141,7 @@ export class TeamList extends PureComponent<Props, any> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Page navModel={navModel}>
|
<Page navModel={navModel}>
|
||||||
<Page.Contents isLoading={!hasFetched}>
|
<Page.Contents isLoading={!hasFetched}>{hasFetched && this.renderList()}</Page.Contents>
|
||||||
{hasFetched && this.renderList()}
|
|
||||||
</Page.Contents>
|
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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');
|
||||||
});
|
});
|
||||||
|
@ -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 { RegExpSafeInput } from 'app/core/components/RegExpSafeInput/RegExpSafeInput';
|
||||||
|
|
||||||
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 => {
|
onSearchQueryChange = (value: string) => {
|
||||||
this.props.setSearchMemberQuery(event.target.value);
|
this.props.setSearchMemberQuery(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
onRemoveMember(member: TeamMember) {
|
onRemoveMember(member: TeamMember) {
|
||||||
@ -90,8 +91,7 @@ export class TeamMembers extends PureComponent<Props, State> {
|
|||||||
<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">
|
<label className="gf-form--has-input-icon gf-form--grow">
|
||||||
<input
|
<RegExpSafeInput
|
||||||
type="text"
|
|
||||||
className="gf-form-input"
|
className="gf-form-input"
|
||||||
placeholder="Search members"
|
placeholder="Search members"
|
||||||
value={searchMemberQuery}
|
value={searchMemberQuery}
|
||||||
|
@ -11,11 +11,10 @@ exports[`Render should render component 1`] = `
|
|||||||
<label
|
<label
|
||||||
className="gf-form--has-input-icon gf-form--grow"
|
className="gf-form--has-input-icon gf-form--grow"
|
||||||
>
|
>
|
||||||
<input
|
<ForwardRef
|
||||||
className="gf-form-input"
|
className="gf-form-input"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
placeholder="Search members"
|
placeholder="Search members"
|
||||||
type="text"
|
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
@ -105,11 +104,10 @@ exports[`Render should render team members 1`] = `
|
|||||||
<label
|
<label
|
||||||
className="gf-form--has-input-icon gf-form--grow"
|
className="gf-form--has-input-icon gf-form--grow"
|
||||||
>
|
>
|
||||||
<input
|
<ForwardRef
|
||||||
className="gf-form-input"
|
className="gf-form-input"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
placeholder="Search members"
|
placeholder="Search members"
|
||||||
type="text"
|
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
@ -325,11 +323,10 @@ exports[`Render should render team members when sync enabled 1`] = `
|
|||||||
<label
|
<label
|
||||||
className="gf-form--has-input-icon gf-form--grow"
|
className="gf-form--has-input-icon gf-form--grow"
|
||||||
>
|
>
|
||||||
<input
|
<ForwardRef
|
||||||
className="gf-form-input"
|
className="gf-form-input"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
placeholder="Search members"
|
placeholder="Search members"
|
||||||
type="text"
|
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
|
@ -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 { RegExpSafeInput } from 'app/core/components/RegExpSafeInput/RegExpSafeInput';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
@ -44,11 +45,10 @@ export class UsersActionBar extends PureComponent<Props> {
|
|||||||
<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">
|
<label className="gf-form--has-input-icon">
|
||||||
<input
|
<RegExpSafeInput
|
||||||
type="text"
|
|
||||||
className="gf-form-input width-20"
|
className="gf-form-input width-20"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={event => setUsersSearchQuery(event.target.value)}
|
onChange={setUsersSearchQuery}
|
||||||
placeholder="Filter by name or type"
|
placeholder="Filter by name or type"
|
||||||
/>
|
/>
|
||||||
<i className="gf-form-input-icon fa fa-search" />
|
<i className="gf-form-input-icon fa fa-search" />
|
||||||
|
@ -10,11 +10,10 @@ exports[`Render should render component 1`] = `
|
|||||||
<label
|
<label
|
||||||
className="gf-form--has-input-icon"
|
className="gf-form--has-input-icon"
|
||||||
>
|
>
|
||||||
<input
|
<ForwardRef
|
||||||
className="gf-form-input width-20"
|
className="gf-form-input width-20"
|
||||||
onChange={[Function]}
|
onChange={[MockFunction]}
|
||||||
placeholder="Filter by name or type"
|
placeholder="Filter by name or type"
|
||||||
type="text"
|
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
@ -38,11 +37,10 @@ exports[`Render should render pending invites button 1`] = `
|
|||||||
<label
|
<label
|
||||||
className="gf-form--has-input-icon"
|
className="gf-form--has-input-icon"
|
||||||
>
|
>
|
||||||
<input
|
<ForwardRef
|
||||||
className="gf-form-input width-20"
|
className="gf-form-input width-20"
|
||||||
onChange={[Function]}
|
onChange={[MockFunction]}
|
||||||
placeholder="Filter by name or type"
|
placeholder="Filter by name or type"
|
||||||
type="text"
|
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
@ -90,11 +88,10 @@ exports[`Render should show external user management button 1`] = `
|
|||||||
<label
|
<label
|
||||||
className="gf-form--has-input-icon"
|
className="gf-form--has-input-icon"
|
||||||
>
|
>
|
||||||
<input
|
<ForwardRef
|
||||||
className="gf-form-input width-20"
|
className="gf-form-input width-20"
|
||||||
onChange={[Function]}
|
onChange={[MockFunction]}
|
||||||
placeholder="Filter by name or type"
|
placeholder="Filter by name or type"
|
||||||
type="text"
|
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
@ -128,11 +125,10 @@ exports[`Render should show invite button 1`] = `
|
|||||||
<label
|
<label
|
||||||
className="gf-form--has-input-icon"
|
className="gf-form--has-input-icon"
|
||||||
>
|
>
|
||||||
<input
|
<ForwardRef
|
||||||
className="gf-form-input width-20"
|
className="gf-form-input width-20"
|
||||||
onChange={[Function]}
|
onChange={[MockFunction]}
|
||||||
placeholder="Filter by name or type"
|
placeholder="Filter by name or type"
|
||||||
type="text"
|
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
|
Loading…
Reference in New Issue
Block a user