Skip to content

Commit

Permalink
Add Search Bar components
Browse files Browse the repository at this point in the history
Integration of the Search Bar component in host and network page
Fix state URL with new Search Bar
  • Loading branch information
XavierM committed Oct 11, 2019
1 parent 525f708 commit 890076c
Show file tree
Hide file tree
Showing 88 changed files with 1,541 additions and 1,651 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import React, { useState, useEffect } from 'react';

import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink, EuiSuperDatePicker } from '@elastic/eui';
// @ts-ignore
import { EuiSuperUpdateButton } from '@elastic/eui';
import { EuiSuperUpdateButton, OnRefreshProps } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { Toast } from 'src/core/public';
import { TimeRange } from 'src/plugins/data/public';
Expand All @@ -41,10 +41,12 @@ interface Props {
query?: Query;
onSubmit: (payload: { dateRange: TimeRange; query?: Query }) => void;
onChange: (payload: { dateRange: TimeRange; query?: Query }) => void;
onRefresh?: (payload: { dateRange: TimeRange }) => void;
disableAutoFocus?: boolean;
screenTitle?: string;
indexPatterns?: Array<IndexPattern | string>;
intl: InjectedIntl;
isLoading?: boolean;
prepend?: React.ReactNode;
showQueryInput?: boolean;
showDatePicker?: boolean;
Expand Down Expand Up @@ -125,6 +127,18 @@ function QueryBarTopRowUI(props: Props) {
}
}

function onRefresh({ start, end }: OnRefreshProps) {
const retVal = {
dateRange: {
from: start,
to: end,
},
};
if (props.onRefresh) {
props.onRefresh(retVal);
}
}

function onSubmit({ query, dateRange }: { query?: Query; dateRange: TimeRange }) {
handleLuceneSyntaxWarning();

Expand Down Expand Up @@ -175,6 +189,7 @@ function QueryBarTopRowUI(props: Props) {
<EuiSuperUpdateButton
needsUpdate={props.isDirty}
isDisabled={isDateRangeInvalid}
isLoading={props.isLoading}
onClick={onClickSubmitButton}
data-test-subj="querySubmitButton"
/>
Expand Down Expand Up @@ -227,6 +242,7 @@ function QueryBarTopRowUI(props: Props) {
isPaused={props.isRefreshPaused}
refreshInterval={props.refreshInterval}
onTimeChange={onTimeChange}
onRefresh={onRefresh}
onRefreshChange={props.onRefreshChange}
showUpdateButton={false}
recentlyUsedRanges={recentlyUsedRanges}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
* under the License.
*/

import React from 'react';
import React, { useState, useEffect } from 'react';
import { Subscription } from 'rxjs';
import { Filter } from '@kbn/es-query';
import { CoreStart } from 'src/core/public';
import { DataPublicPluginStart } from 'src/plugins/data/public';
Expand Down Expand Up @@ -64,8 +65,48 @@ export function createSearchBar({
// App name should come from the core application service.
// Until it's available, we'll ask the user to provide it for the pre-wired component.
return (props: StatetfulSearchBarProps) => {
const tfRefreshInterval = timefilter.timefilter.getRefreshInterval();
const fmFilters = filterManager.getFilters();
const [refreshInterval, setRefreshInterval] = useState(tfRefreshInterval.value);
const [refreshPaused, setRefreshPaused] = useState(tfRefreshInterval.pause);

const [filters, setFilters] = useState(fmFilters);

// We do not really need to keep track of the time
// since this is just for initialization
const timeRange = timefilter.timefilter.getTime();
const refreshInterval = timefilter.timefilter.getRefreshInterval();

useEffect(() => {
let isSubscribed = true;
const subscriptions = new Subscription();
subscriptions.add(
timefilter.timefilter.getRefreshIntervalUpdate$().subscribe({
next: () => {
if (isSubscribed) {
const newRefreshInterval = timefilter.timefilter.getRefreshInterval();
setRefreshInterval(newRefreshInterval.value);
setRefreshPaused(newRefreshInterval.pause);
}
},
})
);

subscriptions.add(
filterManager.getUpdates$().subscribe({
next: () => {
if (isSubscribed) {
const newFilters = filterManager.getFilters();
setFilters(newFilters);
}
},
})
);

return () => {
isSubscribed = false;
subscriptions.unsubscribe();
};
}, []);

return (
<KibanaContextProvider
Expand All @@ -80,9 +121,9 @@ export function createSearchBar({
timeHistory={timefilter.history}
dateRangeFrom={timeRange.from}
dateRangeTo={timeRange.to}
refreshInterval={refreshInterval.value}
isRefreshPaused={refreshInterval.pause}
filters={filterManager.getFilters()}
refreshInterval={refreshInterval}
isRefreshPaused={refreshPaused}
filters={filters}
onFiltersUpdated={defaultFiltersUpdated(filterManager)}
onRefreshChange={defaultOnRefreshChange(timefilter)}
{...props}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ interface SearchBarInjectedDeps {

export interface SearchBarOwnProps {
indexPatterns?: IndexPattern[];
isLoading?: boolean;
customSubmitButton?: React.ReactNode;
screenTitle?: string;

Expand All @@ -78,6 +79,8 @@ export interface SearchBarOwnProps {
onSavedQueryUpdated?: (savedQuery: SavedQuery) => void;
// User has cleared the active query, your app should clear the entire query bar
onClearSavedQuery?: () => void;

onRefresh?: (payload: { dateRange: TimeRange }) => void;
}

export type SearchBarProps = SearchBarOwnProps & SearchBarInjectedDeps;
Expand Down Expand Up @@ -371,6 +374,7 @@ class SearchBarUI extends Component<SearchBarProps, State> {
screenTitle={this.props.screenTitle}
onSubmit={this.onQueryBarSubmit}
indexPatterns={this.props.indexPatterns}
isLoading={this.props.isLoading}
prepend={this.props.showFilterBar ? savedQueryManagement : undefined}
showDatePicker={this.props.showDatePicker}
dateRangeFrom={this.state.dateRangeFrom}
Expand All @@ -379,6 +383,7 @@ class SearchBarUI extends Component<SearchBarProps, State> {
refreshInterval={this.props.refreshInterval}
showAutoRefreshOnly={this.props.showAutoRefreshOnly}
showQueryInput={this.props.showQueryInput}
onRefresh={this.props.onRefresh}
onRefreshChange={this.props.onRefreshChange}
onChange={this.onQueryBarChange}
isDirty={this.isDirty()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { Query } from '../../query/query_bar';

export * from './components';

type SavedQueryTimeFilter = TimeRange & {
export type SavedQueryTimeFilter = TimeRange & {
refreshInterval: RefreshInterval;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import {
EuiFieldSearch,
EuiFieldSearchProps,
EuiOutsideClickDetector,
EuiPanel,
} from '@elastic/eui';
import { EuiFieldText, EuiOutsideClickDetector, EuiPanel, EuiButtonEmpty } from '@elastic/eui';
import React from 'react';
import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public';

Expand Down Expand Up @@ -60,7 +55,12 @@ export class AutocompleteField extends React.PureComponent<
return (
<EuiOutsideClickDetector onOutsideClick={this.handleBlur}>
<AutocompleteContainer>
<FixedEuiFieldSearch
<EuiFieldText
append={
<EuiButtonEmpty isDisabled={true} size="xs">
{'KQL'}
</EuiButtonEmpty>
}
data-test-subj={dataTestSubj}
fullWidth
inputRef={this.handleChangeInputRef}
Expand All @@ -70,9 +70,9 @@ export class AutocompleteField extends React.PureComponent<
onFocus={this.handleFocus}
onKeyDown={this.handleKeyDown}
onKeyUp={this.handleKeyUp}
onSearch={this.submit}
placeholder={placeholder}
placeholder={`Search ${placeholder}`}
value={value}
autoComplete="off"
/>
{areSuggestionsVisible && !isLoadingSuggestions && suggestions.length > 0 ? (
<SuggestionsPanel>
Expand Down Expand Up @@ -307,15 +307,6 @@ const withUnfocused = (state: AutocompleteFieldState) => ({
isFocused: false,
});

export const FixedEuiFieldSearch: React.SFC<
React.InputHTMLAttributes<HTMLInputElement> &
EuiFieldSearchProps & {
inputRef?: (element: HTMLInputElement | null) => void;
onSearch: (value: string) => void;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
> = EuiFieldSearch as any;

const AutocompleteContainer = euiStyled.div`
position: relative;
`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@

import { get } from 'lodash/fp';

import {
ApplySiemFilterAction,
getExpressionFromArray,
getFilterExpression,
ActionContext,
} from './apply_siem_filter_action';
import { ApplySiemFilterAction, ActionContext } from './apply_siem_filter_action';
// @ts-ignore Missing type defs as maps moves to Typescript
import { MAP_SAVED_OBJECT_TYPE } from '../../../../../maps/common/constants';
import {
Expand Down Expand Up @@ -48,19 +43,19 @@ describe('ApplySiemFilterAction', () => {
});

test('it has APPLY_SIEM_FILTER_ACTION_ID type and id', () => {
const action = new ApplySiemFilterAction({ applyFilterQueryFromKueryExpression });
const action = new ApplySiemFilterAction();
expect(action.id).toBe('APPLY_SIEM_FILTER_ACTION_ID');
expect(action.type).toBe('APPLY_SIEM_FILTER_ACTION_ID');
});

test('it has expected display name', () => {
const action = new ApplySiemFilterAction({ applyFilterQueryFromKueryExpression });
const action = new ApplySiemFilterAction();
expect(action.getDisplayName()).toMatchInlineSnapshot(`"Apply filter"`);
});

describe('#isCompatible', () => {
test('when embeddable type is MAP_SAVED_OBJECT_TYPE and filters exist, returns true', async () => {
const action = new ApplySiemFilterAction({ applyFilterQueryFromKueryExpression });
const action = new ApplySiemFilterAction();
const embeddable = {
type: MAP_SAVED_OBJECT_TYPE,
};
Expand All @@ -76,7 +71,7 @@ describe('ApplySiemFilterAction', () => {
});

test('when embeddable type is MAP_SAVED_OBJECT_TYPE and filters do not exist, returns false', async () => {
const action = new ApplySiemFilterAction({ applyFilterQueryFromKueryExpression });
const action = new ApplySiemFilterAction();
const embeddable = {
type: MAP_SAVED_OBJECT_TYPE,
};
Expand All @@ -91,7 +86,7 @@ describe('ApplySiemFilterAction', () => {
});

test('when embeddable type is not MAP_SAVED_OBJECT_TYPE, returns false', async () => {
const action = new ApplySiemFilterAction({ applyFilterQueryFromKueryExpression });
const action = new ApplySiemFilterAction();
const embeddable = {
type: 'defaultEmbeddable',
};
Expand All @@ -109,9 +104,7 @@ describe('ApplySiemFilterAction', () => {

describe('#execute', () => {
test('it throws an error when triggerContext not set', async () => {
const action = new ApplySiemFilterAction({
applyFilterQueryFromKueryExpression,
});
const action = new ApplySiemFilterAction();
const embeddable = {
type: MAP_SAVED_OBJECT_TYPE,
};
Expand All @@ -127,7 +120,7 @@ describe('ApplySiemFilterAction', () => {
});

test('it calls applyFilterQueryFromKueryExpression() with valid expression', async () => {
const action = new ApplySiemFilterAction({ applyFilterQueryFromKueryExpression });
const action = new ApplySiemFilterAction();
const embeddable = {
type: MAP_SAVED_OBJECT_TYPE,
getInput: () => ({
Expand Down Expand Up @@ -167,37 +160,3 @@ describe('ApplySiemFilterAction', () => {
});
});
});

describe('#getFilterExpression', () => {
test('it returns an empty expression if no filterValue is provided', () => {
const layerList = getFilterExpression('host.id', undefined);
expect(layerList).toEqual('(NOT host.id:*)');
});

test('it returns a valid expression when provided single filterValue', () => {
const layerList = getFilterExpression('host.id', 'aa8df15');
expect(layerList).toEqual('host.id: "aa8df15"');
});

test('it returns a valid expression when provided array filterValue', () => {
const layerList = getFilterExpression('host.id', ['xavier', 'angela', 'frank']);
expect(layerList).toEqual('(host.id: "xavier" OR host.id: "angela" OR host.id: "frank")');
});

test('it returns a valid expression when provided array filterValue with a single value', () => {
const layerList = getFilterExpression('host.id', ['xavier']);
expect(layerList).toEqual('(host.id: "xavier")');
});
});

describe('#getExpressionFromArray', () => {
test('it returns an empty expression if no filterValues are provided', () => {
const layerList = getExpressionFromArray('host.id', []);
expect(layerList).toEqual('');
});

test('it returns a valid expression when provided multiple filterValues', () => {
const layerList = getExpressionFromArray('host.id', ['xavier', 'angela', 'frank']);
expect(layerList).toEqual('(host.id: "xavier" OR host.id: "angela" OR host.id: "frank")');
});
});
Loading

0 comments on commit 890076c

Please sign in to comment.