mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
* Step-1: Add Search Bar in timeline instead of our own kql * Step-2: Add the saved query with filter in timeline savedObject * fix type * Fix unit test * fix bug when you use an exists filter * Fix bug to do a search when add filter by itself * Review I * unit tests * fix import for Filter * add range as a filter * remove comment * forget to add range in ES mapping + allow query with only filters * fix and/or with filter * review with Liza
This commit is contained in:
parent
4a336be4ae
commit
63b5fbe40b
57 changed files with 3440 additions and 263 deletions
|
@ -82,8 +82,11 @@ function QueryBarTopRowUI(props: Props) {
|
|||
|
||||
const queryLanguage = props.query && props.query.language;
|
||||
const persistedLog: PersistedLog | undefined = React.useMemo(
|
||||
() => (queryLanguage ? getQueryLog(uiSettings!, storage, appName, queryLanguage) : undefined),
|
||||
[queryLanguage]
|
||||
() =>
|
||||
queryLanguage && uiSettings && storage && appName
|
||||
? getQueryLog(uiSettings!, storage, appName, queryLanguage)
|
||||
: undefined,
|
||||
[appName, queryLanguage, uiSettings, storage]
|
||||
);
|
||||
|
||||
function onClickSubmitButton(event: React.MouseEvent<HTMLButtonElement>) {
|
||||
|
|
|
@ -76,6 +76,7 @@ export interface SearchBarOwnProps {
|
|||
// Show when user has privileges to save
|
||||
showSaveQuery?: boolean;
|
||||
savedQuery?: SavedQuery;
|
||||
onQueryChange?: (payload: { dateRange: TimeRange; query?: Query }) => void;
|
||||
onQuerySubmit?: (payload: { dateRange: TimeRange; query?: Query }) => void;
|
||||
// User has saved the current state as a saved query
|
||||
onSaved?: (savedQuery: SavedQuery) => void;
|
||||
|
@ -210,6 +211,18 @@ class SearchBarUI extends Component<SearchBarProps, State> {
|
|||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* This Function is here to show the toggle in saved query form
|
||||
* in case you the date range (from/to)
|
||||
*/
|
||||
private shouldRenderTimeFilterInSavedQueryForm() {
|
||||
const { dateRangeFrom, dateRangeTo, showDatePicker } = this.props;
|
||||
return (
|
||||
showDatePicker ||
|
||||
(!showDatePicker && dateRangeFrom !== undefined && dateRangeTo !== undefined)
|
||||
);
|
||||
}
|
||||
|
||||
public setFilterBarHeight = () => {
|
||||
requestAnimationFrame(() => {
|
||||
const height =
|
||||
|
@ -303,6 +316,9 @@ class SearchBarUI extends Component<SearchBarProps, State> {
|
|||
dateRangeFrom: queryAndDateRange.dateRange.from,
|
||||
dateRangeTo: queryAndDateRange.dateRange.to,
|
||||
});
|
||||
if (this.props.onQueryChange) {
|
||||
this.props.onQueryChange(queryAndDateRange);
|
||||
}
|
||||
};
|
||||
|
||||
public onQueryBarSubmit = (queryAndDateRange: { dateRange?: TimeRange; query?: Query }) => {
|
||||
|
@ -444,7 +460,7 @@ class SearchBarUI extends Component<SearchBarProps, State> {
|
|||
onSave={this.onSave}
|
||||
onClose={() => this.setState({ showSaveQueryModal: false })}
|
||||
showFilterOption={this.props.showFilterBar}
|
||||
showTimeFilterOption={this.props.showDatePicker}
|
||||
showTimeFilterOption={this.shouldRenderTimeFilterInSavedQueryForm()}
|
||||
/>
|
||||
) : null}
|
||||
{this.state.showSaveNewQueryModal ? (
|
||||
|
@ -453,7 +469,7 @@ class SearchBarUI extends Component<SearchBarProps, State> {
|
|||
onSave={savedQueryMeta => this.onSave(savedQueryMeta, true)}
|
||||
onClose={() => this.setState({ showSaveNewQueryModal: false })}
|
||||
showFilterOption={this.props.showFilterBar}
|
||||
showTimeFilterOption={this.props.showDatePicker}
|
||||
showTimeFilterOption={this.shouldRenderTimeFilterInSavedQueryForm()}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
@ -33,15 +33,17 @@ export interface FilterValueFormatter {
|
|||
}
|
||||
|
||||
export interface FilterMeta {
|
||||
alias: string | null;
|
||||
disabled: boolean;
|
||||
negate: boolean;
|
||||
// controlledBy is there to identify who owns the filter
|
||||
controlledBy?: string;
|
||||
// index and type are optional only because when you create a new filter, there are no defaults
|
||||
index?: string;
|
||||
type?: string;
|
||||
disabled: boolean;
|
||||
negate: boolean;
|
||||
alias: string | null;
|
||||
key?: string;
|
||||
value?: string | ((formatter?: FilterValueFormatter) => string);
|
||||
params?: any;
|
||||
value?: string | ((formatter?: FilterValueFormatter) => string);
|
||||
}
|
||||
|
||||
export interface Filter {
|
||||
|
|
|
@ -26,6 +26,7 @@ export const DEFAULT_TO = 'now';
|
|||
export const DEFAULT_INTERVAL_PAUSE = true;
|
||||
export const DEFAULT_INTERVAL_TYPE = 'manual';
|
||||
export const DEFAULT_INTERVAL_VALUE = 300000; // ms
|
||||
export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges';
|
||||
|
||||
/**
|
||||
* Id for the SIGNALS alerting type
|
||||
|
|
|
@ -9,4 +9,7 @@ import { npStart } from 'ui/new_platform';
|
|||
import { Plugin } from './plugin';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
new Plugin({ opaqueId: Symbol('siem'), env: {} as any }, chrome).start(npStart);
|
||||
new Plugin({ opaqueId: Symbol('siem'), env: {} as any }, chrome).start(
|
||||
npStart.core,
|
||||
npStart.plugins
|
||||
);
|
||||
|
|
|
@ -15,11 +15,6 @@ import template from './template.html';
|
|||
|
||||
export const ROOT_ELEMENT_ID = 'react-siem-root';
|
||||
|
||||
export interface StartObject {
|
||||
core: LegacyCoreStart;
|
||||
plugins: PluginsStart;
|
||||
}
|
||||
|
||||
export class Plugin {
|
||||
constructor(
|
||||
// @ts-ignore this is added to satisfy the New Platform typing constraint,
|
||||
|
@ -30,8 +25,7 @@ export class Plugin {
|
|||
this.chrome = chrome;
|
||||
}
|
||||
|
||||
public start(start: StartObject): void {
|
||||
const { core, plugins } = start;
|
||||
public start(core: LegacyCoreStart, plugins: PluginsStart) {
|
||||
// @ts-ignore improper type description
|
||||
this.chrome.setRootTemplate(template);
|
||||
const checkForRoot = () => {
|
||||
|
|
|
@ -9,6 +9,8 @@ import React, { memo, FC } from 'react';
|
|||
import { ApolloProvider } from 'react-apollo';
|
||||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { LegacyCoreStart } from 'kibana/public';
|
||||
import { PluginsStart } from 'ui/new_platform/new_platform';
|
||||
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
|
@ -17,6 +19,9 @@ import { BehaviorSubject } from 'rxjs';
|
|||
import { pluck } from 'rxjs/operators';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
|
||||
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
import { DEFAULT_DARK_MODE } from '../../common/constants';
|
||||
import { ErrorToastDispatcher } from '../components/error_toast_dispatcher';
|
||||
import { compose } from '../lib/compose/kibana_compose';
|
||||
|
@ -31,8 +36,6 @@ import { MlCapabilitiesProvider } from '../components/ml/permissions/ml_capabili
|
|||
|
||||
import { ApolloClientContext } from '../utils/apollo_context';
|
||||
|
||||
import { StartObject } from './plugin';
|
||||
|
||||
const StartApp: FC<AppFrontendLibs> = memo(libs => {
|
||||
const history = createHashHistory();
|
||||
|
||||
|
@ -74,10 +77,21 @@ const StartApp: FC<AppFrontendLibs> = memo(libs => {
|
|||
|
||||
export const ROOT_ELEMENT_ID = 'react-siem-root';
|
||||
|
||||
export const SiemApp = memo<StartObject>(({ core, plugins }) => (
|
||||
<KibanaCoreContextProvider core={core}>
|
||||
<KibanaPluginsContextProvider plugins={plugins}>
|
||||
<StartApp {...compose()} />
|
||||
</KibanaPluginsContextProvider>
|
||||
</KibanaCoreContextProvider>
|
||||
));
|
||||
export const SiemApp = memo<{ core: LegacyCoreStart; plugins: PluginsStart }>(
|
||||
({ core, plugins }) => (
|
||||
<KibanaContextProvider
|
||||
services={{
|
||||
appName: 'siem',
|
||||
data: plugins.data,
|
||||
storage: new Storage(localStorage),
|
||||
...core,
|
||||
}}
|
||||
>
|
||||
<KibanaCoreContextProvider core={core}>
|
||||
<KibanaPluginsContextProvider plugins={plugins}>
|
||||
<StartApp {...compose()} />
|
||||
</KibanaPluginsContextProvider>
|
||||
</KibanaCoreContextProvider>
|
||||
</KibanaContextProvider>
|
||||
)
|
||||
);
|
||||
|
|
|
@ -20,6 +20,8 @@ import { FlyoutButton } from './button';
|
|||
const testFlyoutHeight = 980;
|
||||
const usersViewing = ['elastic'];
|
||||
|
||||
jest.mock('../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('Flyout', () => {
|
||||
const state: State = mockGlobalState;
|
||||
|
||||
|
|
|
@ -10,14 +10,21 @@ import 'jest-styled-components';
|
|||
import * as React from 'react';
|
||||
|
||||
import { flyoutHeaderHeight } from '../';
|
||||
import { useKibanaCore } from '../../../lib/compose/kibana_core';
|
||||
import { TestProviders } from '../../../mock';
|
||||
|
||||
import { mockUiSettings } from '../../../mock/ui_settings';
|
||||
import { Pane } from '.';
|
||||
|
||||
const testFlyoutHeight = 980;
|
||||
const testWidth = 640;
|
||||
const usersViewing = ['elastic'];
|
||||
|
||||
const mockUseKibanaCore = useKibanaCore as jest.Mock;
|
||||
jest.mock('../../../lib/compose/kibana_core');
|
||||
mockUseKibanaCore.mockImplementation(() => ({
|
||||
uiSettings: mockUiSettings,
|
||||
}));
|
||||
|
||||
describe('Pane', () => {
|
||||
test('renders correctly against snapshot', () => {
|
||||
const EmptyComponent = shallow(
|
||||
|
|
|
@ -235,6 +235,7 @@ describe('helpers', () => {
|
|||
},
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
filters: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
id: 'savedObject-1',
|
||||
|
@ -321,6 +322,7 @@ describe('helpers', () => {
|
|||
},
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
filters: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
id: 'savedObject-1',
|
||||
|
@ -400,6 +402,165 @@ describe('helpers', () => {
|
|||
dataProviders: [],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
filters: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
isLive: false,
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
itemsPerPage: 25,
|
||||
itemsPerPageOptions: [10, 25, 50, 100],
|
||||
kqlMode: 'filter',
|
||||
kqlQuery: {
|
||||
filterQuery: null,
|
||||
filterQueryDraft: null,
|
||||
},
|
||||
title: '',
|
||||
noteIds: [],
|
||||
pinnedEventIds: {},
|
||||
pinnedEventsSaveObject: {},
|
||||
dateRange: {
|
||||
start: 0,
|
||||
end: 0,
|
||||
},
|
||||
show: false,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
sortDirection: 'desc',
|
||||
},
|
||||
width: 1100,
|
||||
id: 'savedObject-1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should merge filters object back with json object', () => {
|
||||
const timeline = {
|
||||
savedObjectId: 'savedObject-1',
|
||||
columns: timelineDefaults.columns.filter(column => column.id !== 'event.action'),
|
||||
filters: [
|
||||
{
|
||||
meta: {
|
||||
alias: null,
|
||||
controlledBy: null,
|
||||
disabled: false,
|
||||
index: null,
|
||||
key: 'event.category',
|
||||
negate: false,
|
||||
params: '{"query":"file"}',
|
||||
type: 'phrase',
|
||||
value: null,
|
||||
},
|
||||
query: '{"match_phrase":{"event.category":"file"}}',
|
||||
exists: null,
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
alias: null,
|
||||
controlledBy: null,
|
||||
disabled: false,
|
||||
index: null,
|
||||
key: '@timestamp',
|
||||
negate: false,
|
||||
params: null,
|
||||
type: 'exists',
|
||||
value: 'exists',
|
||||
},
|
||||
query: null,
|
||||
exists: '{"field":"@timestamp"}',
|
||||
},
|
||||
],
|
||||
version: '1',
|
||||
};
|
||||
|
||||
const newTimeline = defaultTimelineToTimelineModel(timeline, false);
|
||||
expect(newTimeline).toEqual({
|
||||
savedObjectId: 'savedObject-1',
|
||||
columns: [
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: '@timestamp',
|
||||
width: 190,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'message',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'event.category',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'host.name',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'source.ip',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'destination.ip',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'user.name',
|
||||
width: 180,
|
||||
},
|
||||
],
|
||||
version: '1',
|
||||
dataProviders: [],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
filters: [
|
||||
{
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
meta: {
|
||||
alias: null,
|
||||
controlledBy: null,
|
||||
disabled: false,
|
||||
index: null,
|
||||
key: 'event.category',
|
||||
negate: false,
|
||||
params: {
|
||||
query: 'file',
|
||||
},
|
||||
type: 'phrase',
|
||||
value: null,
|
||||
},
|
||||
query: {
|
||||
match_phrase: {
|
||||
'event.category': 'file',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
exists: {
|
||||
field: '@timestamp',
|
||||
},
|
||||
meta: {
|
||||
alias: null,
|
||||
controlledBy: null,
|
||||
disabled: false,
|
||||
index: null,
|
||||
key: '@timestamp',
|
||||
negate: false,
|
||||
params: null,
|
||||
type: 'exists',
|
||||
value: 'exists',
|
||||
},
|
||||
},
|
||||
],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
|
|
|
@ -61,6 +61,14 @@ const omitTypename = (key: string, value: keyof TimelineModel) =>
|
|||
const omitTypenameInTimeline = (timeline: TimelineResult): TimelineResult =>
|
||||
JSON.parse(JSON.stringify(timeline), omitTypename);
|
||||
|
||||
const parseString = (params: string) => {
|
||||
try {
|
||||
return JSON.parse(params);
|
||||
} catch {
|
||||
return params;
|
||||
}
|
||||
};
|
||||
|
||||
export const defaultTimelineToTimelineModel = (
|
||||
timeline: TimelineResult,
|
||||
duplicate: boolean
|
||||
|
@ -97,6 +105,32 @@ export const defaultTimelineToTimelineModel = (
|
|||
return acc;
|
||||
}, {})
|
||||
: {},
|
||||
filters:
|
||||
timeline.filters != null
|
||||
? timeline.filters.map(filter => ({
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
meta: {
|
||||
...filter.meta,
|
||||
...(filter.meta && filter.meta.field != null
|
||||
? { params: parseString(filter.meta.field) }
|
||||
: {}),
|
||||
...(filter.meta && filter.meta.params != null
|
||||
? { params: parseString(filter.meta.params) }
|
||||
: {}),
|
||||
...(filter.meta && filter.meta.value != null
|
||||
? { value: parseString(filter.meta.value) }
|
||||
: {}),
|
||||
},
|
||||
...(filter.exists != null ? { exists: parseString(filter.exists) } : {}),
|
||||
...(filter.match_all != null ? { exists: parseString(filter.match_all) } : {}),
|
||||
...(filter.missing != null ? { exists: parseString(filter.missing) } : {}),
|
||||
...(filter.query != null ? { query: parseString(filter.query) } : {}),
|
||||
...(filter.range != null ? { range: parseString(filter.range) } : {}),
|
||||
...(filter.script != null ? { exists: parseString(filter.script) } : {}),
|
||||
}))
|
||||
: [],
|
||||
isFavorite: duplicate
|
||||
? false
|
||||
: timeline.favorite != null
|
||||
|
|
|
@ -30,10 +30,13 @@ mockUseKibanaCore.mockImplementation(() => ({
|
|||
}));
|
||||
|
||||
// Test will fail because we will to need to mock some core services to make the test work
|
||||
// For now let's forget about SiemSearchBar
|
||||
// For now let's forget about SiemSearchBar and QueryBar
|
||||
jest.mock('../../../search_bar', () => ({
|
||||
SiemSearchBar: () => null,
|
||||
}));
|
||||
jest.mock('../../../query_bar', () => ({
|
||||
QueryBar: () => null,
|
||||
}));
|
||||
|
||||
describe('Hosts Table', () => {
|
||||
const loadPage = jest.fn();
|
||||
|
|
|
@ -0,0 +1,342 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { SearchBar } from '../../../../../../../src/legacy/core_plugins/data/public';
|
||||
import { FilterManager } from '../../../../../../../src/plugins/data/public';
|
||||
import { uiSettingsServiceMock } from '../../../../../../../src/core/public/ui_settings/ui_settings_service.mock';
|
||||
import { useKibanaCore } from '../../lib/compose/kibana_core';
|
||||
import { TestProviders, mockIndexPattern } from '../../mock';
|
||||
import { QueryBar, QueryBarComponentProps } from '.';
|
||||
import { DEFAULT_FROM, DEFAULT_TO } from '../../../common/constants';
|
||||
import { mockUiSettings } from '../../mock/ui_settings';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
const mockUseKibanaCore = useKibanaCore as jest.Mock;
|
||||
const mockUiSettingsForFilterManager = uiSettingsServiceMock.createSetupContract();
|
||||
jest.mock('../../lib/compose/kibana_core');
|
||||
mockUseKibanaCore.mockImplementation(() => ({
|
||||
uiSettings: mockUiSettings,
|
||||
savedObjects: {},
|
||||
}));
|
||||
|
||||
describe('QueryBar ', () => {
|
||||
// We are doing that because we need to wrapped this component with redux
|
||||
// and redux does not like to be updated and since we need to update our
|
||||
// child component (BODY) and we do not want to scare anyone with this error
|
||||
// we are hiding it!!!
|
||||
// eslint-disable-next-line no-console
|
||||
const originalError = console.error;
|
||||
beforeAll(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error = (...args: string[]) => {
|
||||
if (/<Provider> does not support changing `store` on the fly/.test(args[0])) {
|
||||
return;
|
||||
}
|
||||
originalError.call(console, ...args);
|
||||
};
|
||||
});
|
||||
|
||||
const mockOnChangeQuery = jest.fn();
|
||||
const mockOnSubmitQuery = jest.fn();
|
||||
const mockOnSavedQuery = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
mockOnChangeQuery.mockClear();
|
||||
mockOnSubmitQuery.mockClear();
|
||||
mockOnSavedQuery.mockClear();
|
||||
});
|
||||
|
||||
test('check if we format the appropriate props to QueryBar', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<QueryBar
|
||||
dateRangeFrom={DEFAULT_FROM}
|
||||
dateRangeTo={DEFAULT_TO}
|
||||
hideSavedQuery={false}
|
||||
indexPattern={mockIndexPattern}
|
||||
isRefreshPaused={true}
|
||||
filterQuery={{ query: 'here: query', language: 'kuery' }}
|
||||
filterManager={new FilterManager(mockUiSettingsForFilterManager)}
|
||||
filters={[]}
|
||||
onChangedQuery={mockOnChangeQuery}
|
||||
onSubmitQuery={mockOnSubmitQuery}
|
||||
onSavedQuery={mockOnSavedQuery}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
const {
|
||||
customSubmitButton,
|
||||
timeHistory,
|
||||
onClearSavedQuery,
|
||||
onFiltersUpdated,
|
||||
onQueryChange,
|
||||
onQuerySubmit,
|
||||
onSaved,
|
||||
onSavedQueryUpdated,
|
||||
...searchBarProps
|
||||
} = wrapper.find(SearchBar).props();
|
||||
|
||||
expect(searchBarProps).toEqual({
|
||||
dateRangeFrom: 'now-24h',
|
||||
dateRangeTo: 'now',
|
||||
filters: [],
|
||||
indexPatterns: [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
aggregatable: true,
|
||||
name: '@timestamp',
|
||||
searchable: true,
|
||||
type: 'date',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: '@version',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'agent.ephemeral_id',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'agent.hostname',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'agent.id',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'agent.test1',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'agent.test2',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'agent.test3',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'agent.test4',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'agent.test5',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'agent.test6',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'agent.test7',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'agent.test8',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'host.name',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
title: 'filebeat-*,auditbeat-*,packetbeat-*',
|
||||
},
|
||||
],
|
||||
isRefreshPaused: true,
|
||||
query: {
|
||||
language: 'kuery',
|
||||
query: 'here: query',
|
||||
},
|
||||
refreshInterval: undefined,
|
||||
showAutoRefreshOnly: false,
|
||||
showDatePicker: false,
|
||||
showFilterBar: true,
|
||||
showQueryBar: true,
|
||||
showQueryInput: true,
|
||||
showSaveQuery: true,
|
||||
});
|
||||
});
|
||||
|
||||
describe('#onQueryChange', () => {
|
||||
test(' is the only reference that changed when filterQueryDraft props get updated', () => {
|
||||
const Proxy = (props: QueryBarComponentProps) => (
|
||||
<TestProviders>
|
||||
<QueryBar {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
<Proxy
|
||||
dateRangeFrom={DEFAULT_FROM}
|
||||
dateRangeTo={DEFAULT_TO}
|
||||
hideSavedQuery={false}
|
||||
indexPattern={mockIndexPattern}
|
||||
isRefreshPaused={true}
|
||||
filterQuery={{ query: 'here: query', language: 'kuery' }}
|
||||
filterManager={new FilterManager(mockUiSettingsForFilterManager)}
|
||||
filters={[]}
|
||||
onChangedQuery={mockOnChangeQuery}
|
||||
onSubmitQuery={mockOnSubmitQuery}
|
||||
onSavedQuery={mockOnSavedQuery}
|
||||
/>
|
||||
);
|
||||
const searchBarProps = wrapper.find(SearchBar).props();
|
||||
const onChangedQueryRef = searchBarProps.onQueryChange;
|
||||
const onSubmitQueryRef = searchBarProps.onQuerySubmit;
|
||||
const onSavedQueryRef = searchBarProps.onSavedQueryUpdated;
|
||||
|
||||
const queryInput = wrapper.find(QueryBar).find('input[data-test-subj="queryInput"]');
|
||||
queryInput.simulate('change', { target: { value: 'hello: world' } });
|
||||
wrapper.update();
|
||||
|
||||
expect(onChangedQueryRef).not.toEqual(wrapper.find(SearchBar).props().onQueryChange);
|
||||
expect(onSubmitQueryRef).toEqual(wrapper.find(SearchBar).props().onQuerySubmit);
|
||||
expect(onSavedQueryRef).toEqual(wrapper.find(SearchBar).props().onSavedQueryUpdated);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#onQuerySubmit', () => {
|
||||
test(' is the only reference that changed when filterQuery props get updated', () => {
|
||||
const Proxy = (props: QueryBarComponentProps) => (
|
||||
<TestProviders>
|
||||
<QueryBar {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
<Proxy
|
||||
dateRangeFrom={DEFAULT_FROM}
|
||||
dateRangeTo={DEFAULT_TO}
|
||||
hideSavedQuery={false}
|
||||
indexPattern={mockIndexPattern}
|
||||
isRefreshPaused={true}
|
||||
filterQuery={{ query: 'here: query', language: 'kuery' }}
|
||||
filterManager={new FilterManager(mockUiSettingsForFilterManager)}
|
||||
filters={[]}
|
||||
onChangedQuery={mockOnChangeQuery}
|
||||
onSubmitQuery={mockOnSubmitQuery}
|
||||
onSavedQuery={mockOnSavedQuery}
|
||||
/>
|
||||
);
|
||||
const searchBarProps = wrapper.find(SearchBar).props();
|
||||
const onChangedQueryRef = searchBarProps.onQueryChange;
|
||||
const onSubmitQueryRef = searchBarProps.onQuerySubmit;
|
||||
const onSavedQueryRef = searchBarProps.onSavedQueryUpdated;
|
||||
|
||||
wrapper.setProps({ filterQuery: { expression: 'new: one', kind: 'kuery' } });
|
||||
wrapper.update();
|
||||
|
||||
expect(onSubmitQueryRef).not.toEqual(wrapper.find(SearchBar).props().onQuerySubmit);
|
||||
expect(onChangedQueryRef).not.toEqual(wrapper.find(SearchBar).props().onQueryChange);
|
||||
expect(onSavedQueryRef).toEqual(wrapper.find(SearchBar).props().onSavedQueryUpdated);
|
||||
});
|
||||
|
||||
test(' is only reference that changed when timelineId props get updated', () => {
|
||||
const Proxy = (props: QueryBarComponentProps) => (
|
||||
<TestProviders>
|
||||
<QueryBar {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
<Proxy
|
||||
dateRangeFrom={DEFAULT_FROM}
|
||||
dateRangeTo={DEFAULT_TO}
|
||||
hideSavedQuery={false}
|
||||
indexPattern={mockIndexPattern}
|
||||
isRefreshPaused={true}
|
||||
filterQuery={{ query: 'here: query', language: 'kuery' }}
|
||||
filterManager={new FilterManager(mockUiSettingsForFilterManager)}
|
||||
filters={[]}
|
||||
onChangedQuery={mockOnChangeQuery}
|
||||
onSubmitQuery={mockOnSubmitQuery}
|
||||
onSavedQuery={mockOnSavedQuery}
|
||||
/>
|
||||
);
|
||||
const searchBarProps = wrapper.find(SearchBar).props();
|
||||
const onChangedQueryRef = searchBarProps.onQueryChange;
|
||||
const onSubmitQueryRef = searchBarProps.onQuerySubmit;
|
||||
const onSavedQueryRef = searchBarProps.onSavedQueryUpdated;
|
||||
|
||||
wrapper.setProps({ onSubmitQuery: jest.fn() });
|
||||
wrapper.update();
|
||||
|
||||
expect(onSubmitQueryRef).not.toEqual(wrapper.find(SearchBar).props().onQuerySubmit);
|
||||
expect(onChangedQueryRef).toEqual(wrapper.find(SearchBar).props().onQueryChange);
|
||||
expect(onSavedQueryRef).not.toEqual(wrapper.find(SearchBar).props().onSavedQueryUpdated);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#onSavedQueryUpdated', () => {
|
||||
test('is only reference that changed when dataProviders props get updated', () => {
|
||||
const Proxy = (props: QueryBarComponentProps) => (
|
||||
<TestProviders>
|
||||
<QueryBar {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
<Proxy
|
||||
dateRangeFrom={DEFAULT_FROM}
|
||||
dateRangeTo={DEFAULT_TO}
|
||||
hideSavedQuery={false}
|
||||
indexPattern={mockIndexPattern}
|
||||
isRefreshPaused={true}
|
||||
filterQuery={{ query: 'here: query', language: 'kuery' }}
|
||||
filterManager={new FilterManager(mockUiSettingsForFilterManager)}
|
||||
filters={[]}
|
||||
onChangedQuery={mockOnChangeQuery}
|
||||
onSubmitQuery={mockOnSubmitQuery}
|
||||
onSavedQuery={mockOnSavedQuery}
|
||||
/>
|
||||
);
|
||||
const searchBarProps = wrapper.find(SearchBar).props();
|
||||
const onChangedQueryRef = searchBarProps.onQueryChange;
|
||||
const onSubmitQueryRef = searchBarProps.onQuerySubmit;
|
||||
const onSavedQueryRef = searchBarProps.onSavedQueryUpdated;
|
||||
|
||||
wrapper.setProps({ onSavedQuery: jest.fn() });
|
||||
wrapper.update();
|
||||
|
||||
expect(onSavedQueryRef).not.toEqual(wrapper.find(SearchBar).props().onSavedQueryUpdated);
|
||||
expect(onChangedQueryRef).toEqual(wrapper.find(SearchBar).props().onQueryChange);
|
||||
expect(onSubmitQueryRef).toEqual(wrapper.find(SearchBar).props().onQuerySubmit);
|
||||
});
|
||||
});
|
||||
});
|
146
x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx
Normal file
146
x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { isEqual } from 'lodash/fp';
|
||||
import React, { memo, useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { StaticIndexPattern, IndexPattern } from 'ui/index_patterns';
|
||||
|
||||
import { SavedQuery, SearchBar } from '../../../../../../../src/legacy/core_plugins/data/public';
|
||||
import {
|
||||
esFilters,
|
||||
FilterManager,
|
||||
Query,
|
||||
TimeHistory,
|
||||
TimeRange,
|
||||
} from '../../../../../../../src/plugins/data/public';
|
||||
import { SavedQueryTimeFilter } from '../../../../../../../src/legacy/core_plugins/data/public/search';
|
||||
import { Storage } from '../../../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
export interface QueryBarComponentProps {
|
||||
dateRangeFrom?: string;
|
||||
dateRangeTo?: string;
|
||||
hideSavedQuery?: boolean;
|
||||
indexPattern: StaticIndexPattern;
|
||||
isRefreshPaused?: boolean;
|
||||
filterQuery: Query;
|
||||
filterManager: FilterManager;
|
||||
filters: esFilters.Filter[];
|
||||
onChangedQuery: (query: Query) => void;
|
||||
onSubmitQuery: (query: Query, timefilter?: SavedQueryTimeFilter) => void;
|
||||
refreshInterval?: number;
|
||||
savedQuery?: SavedQuery | null;
|
||||
onSavedQuery: (savedQuery: SavedQuery | null) => void;
|
||||
}
|
||||
|
||||
export const QueryBar = memo<QueryBarComponentProps>(
|
||||
({
|
||||
dateRangeFrom,
|
||||
dateRangeTo,
|
||||
hideSavedQuery = false,
|
||||
indexPattern,
|
||||
isRefreshPaused,
|
||||
filterQuery,
|
||||
filterManager,
|
||||
filters,
|
||||
onChangedQuery,
|
||||
onSubmitQuery,
|
||||
refreshInterval,
|
||||
savedQuery,
|
||||
onSavedQuery,
|
||||
}) => {
|
||||
const [draftQuery, setDraftQuery] = useState(filterQuery);
|
||||
|
||||
useEffect(() => {
|
||||
setDraftQuery(filterQuery);
|
||||
}, [filterQuery]);
|
||||
|
||||
const onQuerySubmit = useCallback(
|
||||
(payload: { dateRange: TimeRange; query?: Query }) => {
|
||||
if (payload.query != null && !isEqual(payload.query, filterQuery)) {
|
||||
onSubmitQuery(payload.query);
|
||||
}
|
||||
},
|
||||
[filterQuery, onSubmitQuery]
|
||||
);
|
||||
|
||||
const onQueryChange = useCallback(
|
||||
(payload: { dateRange: TimeRange; query?: Query }) => {
|
||||
if (payload.query != null && !isEqual(payload.query, draftQuery)) {
|
||||
setDraftQuery(payload.query);
|
||||
onChangedQuery(payload.query);
|
||||
}
|
||||
},
|
||||
[draftQuery, onChangedQuery, setDraftQuery]
|
||||
);
|
||||
|
||||
const onSaved = useCallback(
|
||||
(newSavedQuery: SavedQuery) => {
|
||||
onSavedQuery(newSavedQuery);
|
||||
},
|
||||
[onSavedQuery]
|
||||
);
|
||||
|
||||
const onSavedQueryUpdated = useCallback(
|
||||
(savedQueryUpdated: SavedQuery) => {
|
||||
const { query: newQuery, filters: newFilters, timefilter } = savedQueryUpdated.attributes;
|
||||
onSubmitQuery(newQuery, timefilter);
|
||||
filterManager.setFilters(newFilters || []);
|
||||
onSavedQuery(savedQueryUpdated);
|
||||
},
|
||||
[filterManager, onSubmitQuery, onSavedQuery]
|
||||
);
|
||||
|
||||
const onClearSavedQuery = useCallback(() => {
|
||||
if (savedQuery != null) {
|
||||
onSubmitQuery({
|
||||
query: '',
|
||||
language: savedQuery.attributes.query.language,
|
||||
});
|
||||
filterManager.setFilters([]);
|
||||
onSavedQuery(null);
|
||||
}
|
||||
}, [filterManager, onSubmitQuery, onSavedQuery, savedQuery]);
|
||||
|
||||
const onFiltersUpdated = useCallback(
|
||||
(newFilters: esFilters.Filter[]) => {
|
||||
filterManager.setFilters(newFilters);
|
||||
},
|
||||
[filterManager]
|
||||
);
|
||||
|
||||
const CustomButton = <>{null}</>;
|
||||
const indexPatterns = useMemo(() => [indexPattern as IndexPattern], [indexPattern]);
|
||||
|
||||
const searchBarProps = savedQuery != null ? { savedQuery } : {};
|
||||
|
||||
return (
|
||||
<SearchBar
|
||||
customSubmitButton={CustomButton}
|
||||
dateRangeFrom={dateRangeFrom}
|
||||
dateRangeTo={dateRangeTo}
|
||||
filters={filters}
|
||||
indexPatterns={indexPatterns}
|
||||
isRefreshPaused={isRefreshPaused}
|
||||
query={draftQuery}
|
||||
onClearSavedQuery={onClearSavedQuery}
|
||||
onFiltersUpdated={onFiltersUpdated}
|
||||
onQueryChange={onQueryChange}
|
||||
onQuerySubmit={onQuerySubmit}
|
||||
onSaved={onSaved}
|
||||
onSavedQueryUpdated={onSavedQueryUpdated}
|
||||
refreshInterval={refreshInterval}
|
||||
showAutoRefreshOnly={false}
|
||||
showFilterBar={!hideSavedQuery}
|
||||
showDatePicker={false}
|
||||
showQueryBar={true}
|
||||
showQueryInput={true}
|
||||
showSaveQuery={true}
|
||||
timeHistory={new TimeHistory(new Storage(localStorage))}
|
||||
{...searchBarProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
|
@ -232,7 +232,7 @@ const SearchBarComponent = memo<SiemSearchBarProps & SiemSearchBarRedux & SiemSe
|
|||
savedQuery: undefined,
|
||||
});
|
||||
}
|
||||
}, [id, end, fromStr, start, toStr]);
|
||||
}, [id, end, fromStr, start, toStr, savedQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
let isSubscribed = true;
|
||||
|
@ -256,13 +256,13 @@ const SearchBarComponent = memo<SiemSearchBarProps & SiemSearchBarRedux & SiemSe
|
|||
subscriptions.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
const IndexPatterns = useMemo(() => [indexPattern as IndexPattern], [indexPattern]);
|
||||
const indexPatterns = useMemo(() => [indexPattern as IndexPattern], [indexPattern]);
|
||||
return (
|
||||
<SearchBarContainer data-test-subj={`${id}DatePicker`}>
|
||||
<SearchBar
|
||||
appName="siem"
|
||||
isLoading={isLoading}
|
||||
indexPatterns={IndexPatterns}
|
||||
indexPatterns={indexPatterns}
|
||||
query={filterQuery}
|
||||
onClearSavedQuery={onClearSavedQuery}
|
||||
onQuerySubmit={onQuerySubmit}
|
||||
|
|
|
@ -14,6 +14,8 @@ import { createStore, State } from '../../store';
|
|||
import { SuperDatePicker, makeMapStateToProps } from '.';
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
|
||||
jest.mock('../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('SIEM Super Date Picker', () => {
|
||||
describe('#SuperDatePicker', () => {
|
||||
const state: State = mockGlobalState;
|
||||
|
@ -74,38 +76,6 @@ describe('SIEM Super Date Picker', () => {
|
|||
expect(store.getState().inputs.global.timerange.toStr).toBe('now/d');
|
||||
});
|
||||
|
||||
test('Make Sure it is this week', () => {
|
||||
wrapper
|
||||
.find('[data-test-subj="superDatePickerToggleQuickMenuButton"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="superDatePickerCommonlyUsed_This_week"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
expect(store.getState().inputs.global.timerange.fromStr).toBe('now/w');
|
||||
expect(store.getState().inputs.global.timerange.toStr).toBe('now/w');
|
||||
});
|
||||
|
||||
test('Make Sure it is week to date', () => {
|
||||
wrapper
|
||||
.find('[data-test-subj="superDatePickerToggleQuickMenuButton"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="superDatePickerCommonlyUsed_Week_to date"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
expect(store.getState().inputs.global.timerange.fromStr).toBe('now/w');
|
||||
expect(store.getState().inputs.global.timerange.toStr).toBe('now');
|
||||
});
|
||||
|
||||
test('Make Sure to (end date) is superior than from (start date)', () => {
|
||||
expect(store.getState().inputs.global.timerange.to).toBeGreaterThan(
|
||||
store.getState().inputs.global.timerange.from
|
||||
|
@ -168,60 +138,6 @@ describe('SIEM Super Date Picker', () => {
|
|||
).toBe('Last 15 minutesToday');
|
||||
});
|
||||
|
||||
test('Today and Year to date is in Recently used date ranges', () => {
|
||||
wrapper
|
||||
.find('[data-test-subj="superDatePickerToggleQuickMenuButton"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="superDatePickerCommonlyUsed_Year_to date"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('div.euiQuickSelectPopover__section')
|
||||
.at(1)
|
||||
.text()
|
||||
).toBe('Year to dateToday');
|
||||
});
|
||||
|
||||
test('Today and Last 15 minutes and Year to date is in Recently used date ranges', () => {
|
||||
wrapper
|
||||
.find('[data-test-subj="superDatePickerToggleQuickMenuButton"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('button.euiQuickSelect__applyButton')
|
||||
.first()
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="superDatePickerToggleQuickMenuButton"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="superDatePickerCommonlyUsed_Year_to date"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('div.euiQuickSelectPopover__section')
|
||||
.at(1)
|
||||
.text()
|
||||
).toBe('Year to dateLast 15 minutesToday');
|
||||
});
|
||||
|
||||
test('Make sure that it does not add any duplicate if you click again on today', () => {
|
||||
wrapper
|
||||
.find('[data-test-subj="superDatePickerToggleQuickMenuButton"]')
|
||||
|
|
|
@ -12,11 +12,13 @@ import {
|
|||
OnRefreshProps,
|
||||
OnTimeChangeProps,
|
||||
} from '@elastic/eui';
|
||||
import { getOr, take } from 'lodash/fp';
|
||||
import { getOr, take, isEmpty } from 'lodash/fp';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
import { DEFAULT_TIMEPICKER_QUICK_RANGES } from '../../../common/constants';
|
||||
import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting';
|
||||
import { inputsModel, State } from '../../store';
|
||||
import { inputsActions, timelineActions } from '../../store/actions';
|
||||
import { InputsModelId } from '../../store/inputs/constants';
|
||||
|
@ -194,8 +196,18 @@ export const SuperDatePickerComponent = React.memo<SuperDatePickerProps>(
|
|||
const endDate = kind === 'relative' ? toStr : new Date(end).toISOString();
|
||||
const startDate = kind === 'relative' ? fromStr : new Date(start).toISOString();
|
||||
|
||||
const [quickRanges] = useKibanaUiSetting(DEFAULT_TIMEPICKER_QUICK_RANGES);
|
||||
const commonlyUsedRanges = isEmpty(quickRanges)
|
||||
? []
|
||||
: quickRanges.map(({ from, to, display }: { from: string; to: string; display: string }) => ({
|
||||
start: from,
|
||||
end: to,
|
||||
label: display,
|
||||
}));
|
||||
|
||||
return (
|
||||
<EuiSuperDatePicker
|
||||
commonlyUsedRanges={commonlyUsedRanges}
|
||||
end={endDate}
|
||||
isLoading={isLoading}
|
||||
isPaused={policy === 'manual'}
|
||||
|
|
|
@ -50,7 +50,6 @@ const TimelineKqlFetchComponent = memo<OwnProps>(
|
|||
kueryFilterQuery,
|
||||
kueryFilterQueryDraft,
|
||||
storeType: 'timelineType',
|
||||
type: null,
|
||||
timelineId: id,
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -150,6 +150,7 @@ exports[`Header rendering renders correctly against snapshot 1`] = `
|
|||
show={true}
|
||||
/>
|
||||
<Connect(StatefulSearchOrFilterComponent)
|
||||
browserFields={Object {}}
|
||||
indexPattern={
|
||||
Object {
|
||||
"fields": Array [
|
||||
|
|
|
@ -9,12 +9,21 @@ import toJson from 'enzyme-to-json';
|
|||
import * as React from 'react';
|
||||
|
||||
import { Direction } from '../../../graphql/types';
|
||||
import { useKibanaCore } from '../../../lib/compose/kibana_core';
|
||||
import { mockIndexPattern } from '../../../mock';
|
||||
import { TestProviders } from '../../../mock/test_providers';
|
||||
import { mockUiSettings } from '../../../mock/ui_settings';
|
||||
import { mockDataProviders } from '../data_providers/mock/mock_data_providers';
|
||||
|
||||
import { TimelineHeader } from '.';
|
||||
|
||||
const mockUseKibanaCore = useKibanaCore as jest.Mock;
|
||||
jest.mock('../../../lib/compose/kibana_core');
|
||||
mockUseKibanaCore.mockImplementation(() => ({
|
||||
uiSettings: mockUiSettings,
|
||||
savedObjects: {},
|
||||
}));
|
||||
|
||||
describe('Header', () => {
|
||||
const indexPattern = mockIndexPattern;
|
||||
|
||||
|
|
|
@ -84,7 +84,11 @@ export const TimelineHeader = React.memo<Props>(
|
|||
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
|
||||
show={show}
|
||||
/>
|
||||
<StatefulSearchOrFilter timelineId={id} indexPattern={indexPattern} />
|
||||
<StatefulSearchOrFilter
|
||||
browserFields={browserFields}
|
||||
indexPattern={indexPattern}
|
||||
timelineId={id}
|
||||
/>
|
||||
</TimelineHeaderContainer>
|
||||
)
|
||||
);
|
||||
|
|
|
@ -6,12 +6,13 @@
|
|||
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
|
||||
import { FilterStateStore } from '../../../../../../../src/plugins/data/common/es_query/filters';
|
||||
import { mockIndexPattern } from '../../mock';
|
||||
|
||||
import { mockDataProviders } from './data_providers/mock/mock_data_providers';
|
||||
import { buildGlobalQuery, combineQueries } from './helpers';
|
||||
import { mockBrowserFields } from '../../containers/source/mock';
|
||||
import { esQuery } from '../../../../../../../src/plugins/data/public';
|
||||
import { esQuery, esFilters } from '../../../../../../../src/plugins/data/public';
|
||||
|
||||
const cleanUpKqlQuery = (str: string) => str.replace(/\n/g, '').replace(/\s\s+/g, ' ');
|
||||
const startDate = new Date('2018-03-23T18:49:23.132Z').valueOf();
|
||||
|
@ -160,6 +161,52 @@ describe('Combined Queries', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('No Data Provider & No kqlQuery & with Filters', () => {
|
||||
const isEventViewer = true;
|
||||
expect(
|
||||
combineQueries({
|
||||
config,
|
||||
dataProviders: [],
|
||||
indexPattern: mockIndexPattern,
|
||||
browserFields: mockBrowserFields,
|
||||
filters: [
|
||||
{
|
||||
$state: { store: FilterStateStore.APP_STATE },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'event.category',
|
||||
negate: false,
|
||||
params: { query: 'file' },
|
||||
type: 'phrase',
|
||||
},
|
||||
query: { match_phrase: { 'event.category': 'file' } },
|
||||
},
|
||||
{
|
||||
$state: { store: FilterStateStore.APP_STATE },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'host.name',
|
||||
negate: false,
|
||||
type: 'exists',
|
||||
value: 'exists',
|
||||
},
|
||||
exists: { field: 'host.name' },
|
||||
} as esFilters.Filter,
|
||||
],
|
||||
kqlQuery: { query: '', language: 'kuery' },
|
||||
kqlMode: 'search',
|
||||
start: startDate,
|
||||
end: endDate,
|
||||
isEventViewer,
|
||||
})
|
||||
).toEqual({
|
||||
filterQuery:
|
||||
'{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}},{"exists":{"field":"host.name"}}],"should":[],"must_not":[]}}',
|
||||
});
|
||||
});
|
||||
|
||||
test('Only Data Provider', () => {
|
||||
const dataProviders = mockDataProviders.slice(0, 1);
|
||||
const { filterQuery } = combineQueries({
|
||||
|
|
|
@ -113,13 +113,18 @@ export const combineQueries = ({
|
|||
isEventViewer?: boolean;
|
||||
}): { filterQuery: string } | null => {
|
||||
const kuery: Query = { query: '', language: kqlQuery.language };
|
||||
if (isEmpty(dataProviders) && isEmpty(kqlQuery.query) && !isEventViewer) {
|
||||
if (isEmpty(dataProviders) && isEmpty(kqlQuery.query) && isEmpty(filters) && !isEventViewer) {
|
||||
return null;
|
||||
} else if (isEmpty(dataProviders) && isEmpty(kqlQuery.query) && isEventViewer) {
|
||||
kuery.query = `@timestamp >= ${start} and @timestamp <= ${end}`;
|
||||
return {
|
||||
filterQuery: convertToBuildEsQuery({ config, queries: [kuery], indexPattern, filters }),
|
||||
};
|
||||
} else if (isEmpty(dataProviders) && isEmpty(kqlQuery.query) && !isEmpty(filters)) {
|
||||
kuery.query = `@timestamp >= ${start} and @timestamp <= ${end}`;
|
||||
return {
|
||||
filterQuery: convertToBuildEsQuery({ config, queries: [kuery], indexPattern, filters }),
|
||||
};
|
||||
} else if (isEmpty(dataProviders) && !isEmpty(kqlQuery.query)) {
|
||||
kuery.query = `(${kqlQuery.query}) and @timestamp >= ${start} and @timestamp <= ${end}`;
|
||||
return {
|
||||
|
|
|
@ -9,6 +9,8 @@ import React, { useEffect, useCallback } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
|
||||
import { esFilters } from '../../../../../../../src/plugins/data/public';
|
||||
|
||||
import { WithSource } from '../../containers/source';
|
||||
import { inputsModel, inputsSelectors, State, timelineSelectors } from '../../store';
|
||||
import { timelineActions } from '../../store/actions';
|
||||
|
@ -40,6 +42,7 @@ interface StateReduxProps {
|
|||
columns: ColumnHeader[];
|
||||
dataProviders?: DataProvider[];
|
||||
end: number;
|
||||
filters: esFilters.Filter[];
|
||||
isLive: boolean;
|
||||
itemsPerPage?: number;
|
||||
itemsPerPageOptions?: number[];
|
||||
|
@ -137,6 +140,7 @@ const StatefulTimelineComponent = React.memo<Props>(
|
|||
createTimeline,
|
||||
dataProviders,
|
||||
end,
|
||||
filters,
|
||||
flyoutHeaderHeight,
|
||||
flyoutHeight,
|
||||
id,
|
||||
|
@ -252,6 +256,7 @@ const StatefulTimelineComponent = React.memo<Props>(
|
|||
columns={columns}
|
||||
dataProviders={dataProviders!}
|
||||
end={end}
|
||||
filters={filters}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
flyoutHeight={flyoutHeight}
|
||||
id={id}
|
||||
|
@ -295,6 +300,7 @@ const StatefulTimelineComponent = React.memo<Props>(
|
|||
prevProps.start === nextProps.start &&
|
||||
isEqual(prevProps.columns, nextProps.columns) &&
|
||||
isEqual(prevProps.dataProviders, nextProps.dataProviders) &&
|
||||
isEqual(prevProps.filters, nextProps.filters) &&
|
||||
isEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) &&
|
||||
isEqual(prevProps.sort, nextProps.sort)
|
||||
);
|
||||
|
@ -314,6 +320,7 @@ const makeMapStateToProps = () => {
|
|||
const {
|
||||
columns,
|
||||
dataProviders,
|
||||
filters,
|
||||
itemsPerPage,
|
||||
itemsPerPageOptions,
|
||||
kqlMode,
|
||||
|
@ -322,10 +329,13 @@ const makeMapStateToProps = () => {
|
|||
} = timeline;
|
||||
const kqlQueryExpression = getKqlQueryTimeline(state, id);
|
||||
|
||||
const timelineFilter = kqlMode === 'filter' ? filters || [] : [];
|
||||
|
||||
return {
|
||||
columns,
|
||||
dataProviders,
|
||||
end: input.timerange.to,
|
||||
filters: timelineFilter,
|
||||
id,
|
||||
isLive: input.policy.kind === 'interval',
|
||||
itemsPerPage,
|
||||
|
|
|
@ -8,11 +8,19 @@ import { mount } from 'enzyme';
|
|||
import * as React from 'react';
|
||||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
|
||||
import { useKibanaCore } from '../../../lib/compose/kibana_core';
|
||||
import { mockGlobalState, apolloClientObservable } from '../../../mock';
|
||||
import { mockUiSettings } from '../../../mock/ui_settings';
|
||||
import { createStore, State } from '../../../store';
|
||||
|
||||
import { Properties, showDescriptionThreshold, showNotesThreshold } from '.';
|
||||
|
||||
const mockUseKibanaCore = useKibanaCore as jest.Mock;
|
||||
jest.mock('../../../lib/compose/kibana_core');
|
||||
mockUseKibanaCore.mockImplementation(() => ({
|
||||
uiSettings: mockUiSettings,
|
||||
}));
|
||||
|
||||
describe('Properties', () => {
|
||||
const usersViewing = ['elastic'];
|
||||
|
||||
|
|
|
@ -0,0 +1,406 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { DEFAULT_FROM, DEFAULT_TO } from '../../../../common/constants';
|
||||
import { mockBrowserFields } from '../../../containers/source/mock';
|
||||
import { useKibanaCore } from '../../../lib/compose/kibana_core';
|
||||
import { convertKueryToElasticSearchQuery } from '../../../lib/keury';
|
||||
import { mockIndexPattern, TestProviders } from '../../../mock';
|
||||
import { mockUiSettings } from '../../../mock/ui_settings';
|
||||
import { QueryBar } from '../../query_bar';
|
||||
import { mockDataProviders } from '../data_providers/mock/mock_data_providers';
|
||||
import { buildGlobalQuery } from '../helpers';
|
||||
|
||||
import { QueryBarTimeline, QueryBarTimelineComponentProps, getDataProviderFilter } from './index';
|
||||
|
||||
const mockUseKibanaCore = useKibanaCore as jest.Mock;
|
||||
jest.mock('../../../lib/compose/kibana_core');
|
||||
mockUseKibanaCore.mockImplementation(() => ({
|
||||
uiSettings: mockUiSettings,
|
||||
savedObjects: {},
|
||||
}));
|
||||
|
||||
describe('Timeline QueryBar ', () => {
|
||||
// We are doing that because we need to wrapped this component with redux
|
||||
// and redux does not like to be updated and since we need to update our
|
||||
// child component (BODY) and we do not want to scare anyone with this error
|
||||
// we are hiding it!!!
|
||||
// eslint-disable-next-line no-console
|
||||
const originalError = console.error;
|
||||
beforeAll(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error = (...args: string[]) => {
|
||||
if (/<Provider> does not support changing `store` on the fly/.test(args[0])) {
|
||||
return;
|
||||
}
|
||||
originalError.call(console, ...args);
|
||||
};
|
||||
});
|
||||
|
||||
const mockApplyKqlFilterQuery = jest.fn();
|
||||
const mockSetFilters = jest.fn();
|
||||
const mockSetKqlFilterQueryDraft = jest.fn();
|
||||
const mockSetSavedQueryId = jest.fn();
|
||||
const mockUpdateReduxTime = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
mockApplyKqlFilterQuery.mockClear();
|
||||
mockSetFilters.mockClear();
|
||||
mockSetKqlFilterQueryDraft.mockClear();
|
||||
mockSetSavedQueryId.mockClear();
|
||||
mockUpdateReduxTime.mockClear();
|
||||
});
|
||||
|
||||
test('check if we format the appropriate props to QueryBar', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<QueryBarTimeline
|
||||
applyKqlFilterQuery={mockApplyKqlFilterQuery}
|
||||
browserFields={mockBrowserFields}
|
||||
dataProviders={mockDataProviders}
|
||||
filters={[]}
|
||||
filterQuery={{ expression: 'here: query', kind: 'kuery' }}
|
||||
filterQueryDraft={{ expression: 'here: query', kind: 'kuery' }}
|
||||
from={0}
|
||||
fromStr={DEFAULT_FROM}
|
||||
to={1}
|
||||
toStr={DEFAULT_TO}
|
||||
kqlMode="search"
|
||||
indexPattern={mockIndexPattern}
|
||||
isRefreshPaused={true}
|
||||
refreshInterval={3000}
|
||||
savedQueryId={null}
|
||||
setFilters={mockSetFilters}
|
||||
setKqlFilterQueryDraft={mockSetKqlFilterQueryDraft}
|
||||
setSavedQueryId={mockSetSavedQueryId}
|
||||
timelineId="timline-real-id"
|
||||
updateReduxTime={mockUpdateReduxTime}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
const queryBarProps = wrapper.find(QueryBar).props();
|
||||
|
||||
expect(queryBarProps.dateRangeFrom).toEqual('now-24h');
|
||||
expect(queryBarProps.dateRangeTo).toEqual('now');
|
||||
expect(queryBarProps.filterQuery).toEqual({ query: 'here: query', language: 'kuery' });
|
||||
expect(queryBarProps.savedQuery).toEqual(null);
|
||||
});
|
||||
|
||||
describe('#onChangeQuery', () => {
|
||||
test(' is the only reference that changed when filterQueryDraft props get updated', () => {
|
||||
const Proxy = (props: QueryBarTimelineComponentProps) => (
|
||||
<TestProviders>
|
||||
<QueryBarTimeline {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
<Proxy
|
||||
applyKqlFilterQuery={mockApplyKqlFilterQuery}
|
||||
browserFields={mockBrowserFields}
|
||||
dataProviders={mockDataProviders}
|
||||
filters={[]}
|
||||
filterQuery={{ expression: 'here: query', kind: 'kuery' }}
|
||||
filterQueryDraft={{ expression: 'here: query', kind: 'kuery' }}
|
||||
from={0}
|
||||
fromStr={DEFAULT_FROM}
|
||||
to={1}
|
||||
toStr={DEFAULT_TO}
|
||||
kqlMode="search"
|
||||
indexPattern={mockIndexPattern}
|
||||
isRefreshPaused={true}
|
||||
refreshInterval={3000}
|
||||
savedQueryId={null}
|
||||
setFilters={mockSetFilters}
|
||||
setKqlFilterQueryDraft={mockSetKqlFilterQueryDraft}
|
||||
setSavedQueryId={mockSetSavedQueryId}
|
||||
timelineId="timeline-real-id"
|
||||
updateReduxTime={mockUpdateReduxTime}
|
||||
/>
|
||||
);
|
||||
const queryBarProps = wrapper.find(QueryBar).props();
|
||||
const onChangedQueryRef = queryBarProps.onChangedQuery;
|
||||
const onSubmitQueryRef = queryBarProps.onSubmitQuery;
|
||||
const onSavedQueryRef = queryBarProps.onSavedQuery;
|
||||
|
||||
wrapper.setProps({ filterQueryDraft: { expression: 'new: one', kind: 'kuery' } });
|
||||
wrapper.update();
|
||||
|
||||
expect(onChangedQueryRef).not.toEqual(wrapper.find(QueryBar).props().onChangedQuery);
|
||||
expect(onSubmitQueryRef).toEqual(wrapper.find(QueryBar).props().onSubmitQuery);
|
||||
expect(onSavedQueryRef).toEqual(wrapper.find(QueryBar).props().onSavedQuery);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#onSubmitQuery', () => {
|
||||
test(' is the only reference that changed when filterQuery props get updated', () => {
|
||||
const Proxy = (props: QueryBarTimelineComponentProps) => (
|
||||
<TestProviders>
|
||||
<QueryBarTimeline {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
<Proxy
|
||||
applyKqlFilterQuery={mockApplyKqlFilterQuery}
|
||||
browserFields={mockBrowserFields}
|
||||
dataProviders={mockDataProviders}
|
||||
filters={[]}
|
||||
filterQuery={{ expression: 'here: query', kind: 'kuery' }}
|
||||
filterQueryDraft={{ expression: 'here: query', kind: 'kuery' }}
|
||||
from={0}
|
||||
fromStr={DEFAULT_FROM}
|
||||
to={1}
|
||||
toStr={DEFAULT_TO}
|
||||
kqlMode="search"
|
||||
indexPattern={mockIndexPattern}
|
||||
isRefreshPaused={true}
|
||||
refreshInterval={3000}
|
||||
savedQueryId={null}
|
||||
setFilters={mockSetFilters}
|
||||
setKqlFilterQueryDraft={mockSetKqlFilterQueryDraft}
|
||||
setSavedQueryId={mockSetSavedQueryId}
|
||||
timelineId="timeline-real-id"
|
||||
updateReduxTime={mockUpdateReduxTime}
|
||||
/>
|
||||
);
|
||||
const queryBarProps = wrapper.find(QueryBar).props();
|
||||
const onChangedQueryRef = queryBarProps.onChangedQuery;
|
||||
const onSubmitQueryRef = queryBarProps.onSubmitQuery;
|
||||
const onSavedQueryRef = queryBarProps.onSavedQuery;
|
||||
|
||||
wrapper.setProps({ filterQuery: { expression: 'new: one', kind: 'kuery' } });
|
||||
wrapper.update();
|
||||
|
||||
expect(onSubmitQueryRef).not.toEqual(wrapper.find(QueryBar).props().onSubmitQuery);
|
||||
expect(onChangedQueryRef).toEqual(wrapper.find(QueryBar).props().onChangedQuery);
|
||||
expect(onSavedQueryRef).toEqual(wrapper.find(QueryBar).props().onSavedQuery);
|
||||
});
|
||||
|
||||
test(' is only reference that changed when timelineId props get updated', () => {
|
||||
const Proxy = (props: QueryBarTimelineComponentProps) => (
|
||||
<TestProviders>
|
||||
<QueryBarTimeline {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
<Proxy
|
||||
applyKqlFilterQuery={mockApplyKqlFilterQuery}
|
||||
browserFields={mockBrowserFields}
|
||||
dataProviders={mockDataProviders}
|
||||
filters={[]}
|
||||
filterQuery={{ expression: 'here: query', kind: 'kuery' }}
|
||||
filterQueryDraft={{ expression: 'here: query', kind: 'kuery' }}
|
||||
from={0}
|
||||
fromStr={DEFAULT_FROM}
|
||||
to={1}
|
||||
toStr={DEFAULT_TO}
|
||||
kqlMode="search"
|
||||
indexPattern={mockIndexPattern}
|
||||
isRefreshPaused={true}
|
||||
refreshInterval={3000}
|
||||
savedQueryId={null}
|
||||
setFilters={mockSetFilters}
|
||||
setKqlFilterQueryDraft={mockSetKqlFilterQueryDraft}
|
||||
setSavedQueryId={mockSetSavedQueryId}
|
||||
timelineId="timeline-real-id"
|
||||
updateReduxTime={mockUpdateReduxTime}
|
||||
/>
|
||||
);
|
||||
const queryBarProps = wrapper.find(QueryBar).props();
|
||||
const onChangedQueryRef = queryBarProps.onChangedQuery;
|
||||
const onSubmitQueryRef = queryBarProps.onSubmitQuery;
|
||||
const onSavedQueryRef = queryBarProps.onSavedQuery;
|
||||
|
||||
wrapper.setProps({ timelineId: 'new-timeline' });
|
||||
wrapper.update();
|
||||
|
||||
expect(onSubmitQueryRef).not.toEqual(wrapper.find(QueryBar).props().onSubmitQuery);
|
||||
expect(onChangedQueryRef).toEqual(wrapper.find(QueryBar).props().onChangedQuery);
|
||||
expect(onSavedQueryRef).toEqual(wrapper.find(QueryBar).props().onSavedQuery);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#onSavedQuery', () => {
|
||||
test('is only reference that changed when dataProviders props get updated', () => {
|
||||
const Proxy = (props: QueryBarTimelineComponentProps) => (
|
||||
<TestProviders>
|
||||
<QueryBarTimeline {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
<Proxy
|
||||
applyKqlFilterQuery={mockApplyKqlFilterQuery}
|
||||
browserFields={mockBrowserFields}
|
||||
dataProviders={mockDataProviders}
|
||||
filters={[]}
|
||||
filterQuery={{ expression: 'here: query', kind: 'kuery' }}
|
||||
filterQueryDraft={{ expression: 'here: query', kind: 'kuery' }}
|
||||
from={0}
|
||||
fromStr={DEFAULT_FROM}
|
||||
to={1}
|
||||
toStr={DEFAULT_TO}
|
||||
kqlMode="search"
|
||||
indexPattern={mockIndexPattern}
|
||||
isRefreshPaused={true}
|
||||
refreshInterval={3000}
|
||||
savedQueryId={null}
|
||||
setFilters={mockSetFilters}
|
||||
setKqlFilterQueryDraft={mockSetKqlFilterQueryDraft}
|
||||
setSavedQueryId={mockSetSavedQueryId}
|
||||
timelineId="timeline-real-id"
|
||||
updateReduxTime={mockUpdateReduxTime}
|
||||
/>
|
||||
);
|
||||
const queryBarProps = wrapper.find(QueryBar).props();
|
||||
const onChangedQueryRef = queryBarProps.onChangedQuery;
|
||||
const onSubmitQueryRef = queryBarProps.onSubmitQuery;
|
||||
const onSavedQueryRef = queryBarProps.onSavedQuery;
|
||||
|
||||
wrapper.setProps({ dataProviders: mockDataProviders.slice(1, 0) });
|
||||
wrapper.update();
|
||||
|
||||
expect(onSavedQueryRef).not.toEqual(wrapper.find(QueryBar).props().onSavedQuery);
|
||||
expect(onChangedQueryRef).toEqual(wrapper.find(QueryBar).props().onChangedQuery);
|
||||
expect(onSubmitQueryRef).toEqual(wrapper.find(QueryBar).props().onSubmitQuery);
|
||||
});
|
||||
|
||||
test('is only reference that changed when savedQueryId props get updated', () => {
|
||||
const Proxy = (props: QueryBarTimelineComponentProps) => (
|
||||
<TestProviders>
|
||||
<QueryBarTimeline {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
<Proxy
|
||||
applyKqlFilterQuery={mockApplyKqlFilterQuery}
|
||||
browserFields={mockBrowserFields}
|
||||
dataProviders={mockDataProviders}
|
||||
filters={[]}
|
||||
filterQuery={{ expression: 'here: query', kind: 'kuery' }}
|
||||
filterQueryDraft={{ expression: 'here: query', kind: 'kuery' }}
|
||||
from={0}
|
||||
fromStr={DEFAULT_FROM}
|
||||
to={1}
|
||||
toStr={DEFAULT_TO}
|
||||
kqlMode="search"
|
||||
indexPattern={mockIndexPattern}
|
||||
isRefreshPaused={true}
|
||||
refreshInterval={3000}
|
||||
savedQueryId={null}
|
||||
setFilters={mockSetFilters}
|
||||
setKqlFilterQueryDraft={mockSetKqlFilterQueryDraft}
|
||||
setSavedQueryId={mockSetSavedQueryId}
|
||||
timelineId="timeline-real-id"
|
||||
updateReduxTime={mockUpdateReduxTime}
|
||||
/>
|
||||
);
|
||||
const queryBarProps = wrapper.find(QueryBar).props();
|
||||
const onChangedQueryRef = queryBarProps.onChangedQuery;
|
||||
const onSubmitQueryRef = queryBarProps.onSubmitQuery;
|
||||
const onSavedQueryRef = queryBarProps.onSavedQuery;
|
||||
|
||||
wrapper.setProps({
|
||||
savedQueryId: 'new',
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
expect(onSavedQueryRef).not.toEqual(wrapper.find(QueryBar).props().onSavedQuery);
|
||||
expect(onChangedQueryRef).toEqual(wrapper.find(QueryBar).props().onChangedQuery);
|
||||
expect(onSubmitQueryRef).toEqual(wrapper.find(QueryBar).props().onSubmitQuery);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getDataProviderFilter', () => {
|
||||
test('returns valid data provider filter with a simple bool data provider', () => {
|
||||
const dataProvidersDsl = convertKueryToElasticSearchQuery(
|
||||
buildGlobalQuery(mockDataProviders.slice(0, 1), mockBrowserFields),
|
||||
mockIndexPattern
|
||||
);
|
||||
const filter = getDataProviderFilter(dataProvidersDsl);
|
||||
expect(filter).toEqual({
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
name: 'Provider 1',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
meta: {
|
||||
alias: 'timeline-filter-drop-area',
|
||||
controlledBy: 'timeline-filter-drop-area',
|
||||
disabled: false,
|
||||
key: 'bool',
|
||||
negate: false,
|
||||
type: 'custom',
|
||||
value:
|
||||
'{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}}',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('returns valid data provider filter with an exists operator', () => {
|
||||
const dataProvidersDsl = convertKueryToElasticSearchQuery(
|
||||
buildGlobalQuery(
|
||||
[
|
||||
{
|
||||
id: `id-exists`,
|
||||
name,
|
||||
enabled: true,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: 'host.name',
|
||||
value: '',
|
||||
operator: ':*',
|
||||
},
|
||||
and: [],
|
||||
},
|
||||
],
|
||||
mockBrowserFields
|
||||
),
|
||||
mockIndexPattern
|
||||
);
|
||||
const filter = getDataProviderFilter(dataProvidersDsl);
|
||||
expect(filter).toEqual({
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
exists: {
|
||||
field: 'host.name',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
meta: {
|
||||
alias: 'timeline-filter-drop-area',
|
||||
controlledBy: 'timeline-filter-drop-area',
|
||||
disabled: false,
|
||||
key: 'bool',
|
||||
negate: false,
|
||||
type: 'custom',
|
||||
value: '{"bool":{"should":[{"exists":{"field":"host.name"}}],"minimum_should_match":1}}',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,310 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { isEqual, isEmpty } from 'lodash/fp';
|
||||
import React, { memo, useCallback, useState, useEffect } from 'react';
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
import { Query } from 'src/plugins/data/common/types';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { SavedQueryTimeFilter } from '../../../../../../../../src/legacy/core_plugins/data/public/search';
|
||||
import { SavedQuery } from '../../../../../../../../src/legacy/core_plugins/data/public';
|
||||
import { FilterManager } from '../../../../../../../../src/plugins/data/public';
|
||||
import {
|
||||
Filter,
|
||||
FilterStateStore,
|
||||
} from '../../../../../../../../src/plugins/data/common/es_query/filters';
|
||||
|
||||
import { BrowserFields } from '../../../containers/source';
|
||||
import { convertKueryToElasticSearchQuery } from '../../../lib/keury';
|
||||
import { useKibanaCore } from '../../../lib/compose/kibana_core';
|
||||
import { KueryFilterQuery, KueryFilterQueryKind } from '../../../store';
|
||||
import { KqlMode } from '../../../store/timeline/model';
|
||||
import { useSavedQueryServices } from '../../../utils/saved_query_services';
|
||||
import { DispatchUpdateReduxTime } from '../../super_date_picker';
|
||||
import { QueryBar } from '../../query_bar';
|
||||
import { DataProvider } from '../data_providers/data_provider';
|
||||
import { buildGlobalQuery } from '../helpers';
|
||||
|
||||
export interface QueryBarTimelineComponentProps {
|
||||
applyKqlFilterQuery: (expression: string, kind: KueryFilterQueryKind) => void;
|
||||
browserFields: BrowserFields;
|
||||
dataProviders: DataProvider[];
|
||||
filters: Filter[];
|
||||
filterQuery: KueryFilterQuery;
|
||||
filterQueryDraft: KueryFilterQuery;
|
||||
from: number;
|
||||
fromStr: string;
|
||||
kqlMode: KqlMode;
|
||||
indexPattern: StaticIndexPattern;
|
||||
isRefreshPaused: boolean;
|
||||
refreshInterval: number;
|
||||
savedQueryId: string | null;
|
||||
setFilters: (filters: Filter[]) => void;
|
||||
setKqlFilterQueryDraft: (expression: string, kind: KueryFilterQueryKind) => void;
|
||||
setSavedQueryId: (savedQueryId: string | null) => void;
|
||||
timelineId: string;
|
||||
to: number;
|
||||
toStr: string;
|
||||
updateReduxTime: DispatchUpdateReduxTime;
|
||||
}
|
||||
|
||||
const timelineFilterDropArea = 'timeline-filter-drop-area';
|
||||
|
||||
export const QueryBarTimeline = memo<QueryBarTimelineComponentProps>(
|
||||
({
|
||||
applyKqlFilterQuery,
|
||||
browserFields,
|
||||
dataProviders,
|
||||
filters,
|
||||
filterQuery,
|
||||
filterQueryDraft,
|
||||
from,
|
||||
fromStr,
|
||||
kqlMode,
|
||||
indexPattern,
|
||||
isRefreshPaused,
|
||||
savedQueryId,
|
||||
setFilters,
|
||||
setKqlFilterQueryDraft,
|
||||
setSavedQueryId,
|
||||
refreshInterval,
|
||||
timelineId,
|
||||
to,
|
||||
toStr,
|
||||
updateReduxTime,
|
||||
}) => {
|
||||
const [dateRangeFrom, setDateRangeFrom] = useState<string>(
|
||||
fromStr != null ? fromStr : new Date(from).toISOString()
|
||||
);
|
||||
const [dateRangeTo, setDateRangTo] = useState<string>(
|
||||
toStr != null ? toStr : new Date(to).toISOString()
|
||||
);
|
||||
|
||||
const [savedQuery, setSavedQuery] = useState<SavedQuery | null>(null);
|
||||
const [filterQueryConverted, setFilterQueryConverted] = useState<Query>({
|
||||
query: filterQuery != null ? filterQuery.expression : '',
|
||||
language: filterQuery != null ? filterQuery.kind : 'kuery',
|
||||
});
|
||||
const [queryBarFilters, setQueryBarFilters] = useState<Filter[]>([]);
|
||||
const [dataProvidersDsl, setDataProvidersDsl] = useState<string>(
|
||||
convertKueryToElasticSearchQuery(buildGlobalQuery(dataProviders, browserFields), indexPattern)
|
||||
);
|
||||
const core = useKibanaCore();
|
||||
const [filterManager] = useState<FilterManager>(new FilterManager(core.uiSettings));
|
||||
|
||||
const savedQueryServices = useSavedQueryServices();
|
||||
|
||||
useEffect(() => {
|
||||
let isSubscribed = true;
|
||||
const subscriptions = new Subscription();
|
||||
filterManager.setFilters(filters);
|
||||
|
||||
subscriptions.add(
|
||||
filterManager.getUpdates$().subscribe({
|
||||
next: () => {
|
||||
if (isSubscribed) {
|
||||
const filterWithoutDropArea = filterManager
|
||||
.getFilters()
|
||||
.filter((f: Filter) => f.meta.controlledBy !== timelineFilterDropArea);
|
||||
setFilters(filterWithoutDropArea);
|
||||
setQueryBarFilters(filterWithoutDropArea);
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return () => {
|
||||
isSubscribed = false;
|
||||
subscriptions.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const filterWithoutDropArea = filterManager
|
||||
.getFilters()
|
||||
.filter((f: Filter) => f.meta.controlledBy !== timelineFilterDropArea);
|
||||
if (!isEqual(filters, filterWithoutDropArea)) {
|
||||
filterManager.setFilters(filters);
|
||||
}
|
||||
}, [filters]);
|
||||
|
||||
useEffect(() => {
|
||||
setFilterQueryConverted({
|
||||
query: filterQuery != null ? filterQuery.expression : '',
|
||||
language: filterQuery != null ? filterQuery.kind : 'kuery',
|
||||
});
|
||||
}, [filterQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
setDataProvidersDsl(
|
||||
convertKueryToElasticSearchQuery(
|
||||
buildGlobalQuery(dataProviders, browserFields),
|
||||
indexPattern
|
||||
)
|
||||
);
|
||||
}, [dataProviders, browserFields, indexPattern]);
|
||||
|
||||
useEffect(() => {
|
||||
if (fromStr != null && toStr != null) {
|
||||
setDateRangeFrom(fromStr);
|
||||
setDateRangTo(toStr);
|
||||
} else if (from != null && to != null) {
|
||||
setDateRangeFrom(new Date(from).toISOString());
|
||||
setDateRangTo(new Date(to).toISOString());
|
||||
}
|
||||
}, [from, fromStr, to, toStr]);
|
||||
|
||||
useEffect(() => {
|
||||
let isSubscribed = true;
|
||||
async function setSavedQueryByServices() {
|
||||
if (savedQueryId != null && savedQueryServices != null) {
|
||||
const mySavedQuery = await savedQueryServices.getSavedQuery(savedQueryId);
|
||||
if (isSubscribed) {
|
||||
setSavedQuery({
|
||||
...mySavedQuery,
|
||||
attributes: {
|
||||
...mySavedQuery.attributes,
|
||||
filters: filters.filter(f => f.meta.controlledBy !== timelineFilterDropArea),
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (isSubscribed) {
|
||||
setSavedQuery(null);
|
||||
}
|
||||
}
|
||||
setSavedQueryByServices();
|
||||
return () => {
|
||||
isSubscribed = false;
|
||||
};
|
||||
}, [savedQueryId]);
|
||||
|
||||
const onChangedQuery = useCallback(
|
||||
(newQuery: Query) => {
|
||||
if (
|
||||
filterQueryDraft == null ||
|
||||
(filterQueryDraft != null && filterQueryDraft.expression !== newQuery.query) ||
|
||||
filterQueryDraft.kind !== newQuery.language
|
||||
) {
|
||||
setKqlFilterQueryDraft(
|
||||
newQuery.query as string,
|
||||
newQuery.language as KueryFilterQueryKind
|
||||
);
|
||||
}
|
||||
},
|
||||
[filterQueryDraft]
|
||||
);
|
||||
|
||||
const onSubmitQuery = useCallback(
|
||||
(newQuery: Query, timefilter?: SavedQueryTimeFilter) => {
|
||||
if (
|
||||
filterQuery == null ||
|
||||
(filterQuery != null && filterQuery.expression !== newQuery.query) ||
|
||||
filterQuery.kind !== newQuery.language
|
||||
) {
|
||||
setKqlFilterQueryDraft(
|
||||
newQuery.query as string,
|
||||
newQuery.language as KueryFilterQueryKind
|
||||
);
|
||||
applyKqlFilterQuery(newQuery.query as string, newQuery.language as KueryFilterQueryKind);
|
||||
}
|
||||
if (timefilter != null) {
|
||||
const isQuickSelection = timefilter.from.includes('now') || timefilter.to.includes('now');
|
||||
|
||||
updateReduxTime({
|
||||
id: 'timeline',
|
||||
end: timefilter.to,
|
||||
start: timefilter.from,
|
||||
isInvalid: false,
|
||||
isQuickSelection,
|
||||
timelineId,
|
||||
});
|
||||
}
|
||||
},
|
||||
[filterQuery, timelineId]
|
||||
);
|
||||
|
||||
const onSavedQuery = useCallback(
|
||||
(newSavedQuery: SavedQuery | null) => {
|
||||
if (newSavedQuery != null) {
|
||||
if (newSavedQuery.id !== savedQueryId) {
|
||||
setSavedQueryId(newSavedQuery.id);
|
||||
}
|
||||
if (savedQueryServices != null && dataProvidersDsl !== '') {
|
||||
const dataProviderFilterExists =
|
||||
newSavedQuery.attributes.filters != null
|
||||
? newSavedQuery.attributes.filters.findIndex(
|
||||
f => f.meta.controlledBy === timelineFilterDropArea
|
||||
)
|
||||
: -1;
|
||||
savedQueryServices.saveQuery(
|
||||
{
|
||||
...newSavedQuery.attributes,
|
||||
filters:
|
||||
newSavedQuery.attributes.filters != null
|
||||
? dataProviderFilterExists > -1
|
||||
? [
|
||||
...newSavedQuery.attributes.filters.slice(0, dataProviderFilterExists),
|
||||
getDataProviderFilter(dataProvidersDsl),
|
||||
...newSavedQuery.attributes.filters.slice(dataProviderFilterExists + 1),
|
||||
]
|
||||
: [
|
||||
...newSavedQuery.attributes.filters,
|
||||
getDataProviderFilter(dataProvidersDsl),
|
||||
]
|
||||
: [],
|
||||
},
|
||||
{
|
||||
overwrite: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setSavedQueryId(null);
|
||||
}
|
||||
},
|
||||
[dataProvidersDsl, savedQueryId, savedQueryServices]
|
||||
);
|
||||
|
||||
return (
|
||||
<QueryBar
|
||||
dateRangeFrom={dateRangeFrom}
|
||||
dateRangeTo={dateRangeTo}
|
||||
hideSavedQuery={kqlMode === 'search'}
|
||||
indexPattern={indexPattern}
|
||||
isRefreshPaused={isRefreshPaused}
|
||||
filterQuery={filterQueryConverted}
|
||||
filterManager={filterManager}
|
||||
filters={queryBarFilters}
|
||||
onChangedQuery={onChangedQuery}
|
||||
onSubmitQuery={onSubmitQuery}
|
||||
refreshInterval={refreshInterval}
|
||||
savedQuery={savedQuery}
|
||||
onSavedQuery={onSavedQuery}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const getDataProviderFilter = (dataProviderDsl: string): Filter => {
|
||||
const dslObject = JSON.parse(dataProviderDsl);
|
||||
const key = Object.keys(dslObject);
|
||||
return {
|
||||
...dslObject,
|
||||
meta: {
|
||||
alias: timelineFilterDropArea,
|
||||
controlledBy: timelineFilterDropArea,
|
||||
negate: false,
|
||||
disabled: false,
|
||||
type: 'custom',
|
||||
key: isEmpty(key) ? 'bool' : key[0],
|
||||
value: dataProviderDsl,
|
||||
},
|
||||
$state: {
|
||||
store: FilterStateStore.APP_STATE,
|
||||
},
|
||||
};
|
||||
};
|
|
@ -4,43 +4,69 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { getOr } from 'lodash/fp';
|
||||
import { getOr, isEqual } from 'lodash/fp';
|
||||
import React, { useCallback } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
import { Dispatch } from 'redux';
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
|
||||
import { esFilters } from '../../../../../../../../src/plugins/data/public';
|
||||
import { BrowserFields } from '../../../containers/source';
|
||||
import { convertKueryToElasticSearchQuery } from '../../../lib/keury';
|
||||
import { KueryFilterQuery, SerializedFilterQuery, State, timelineSelectors } from '../../../store';
|
||||
|
||||
import { SearchOrFilter } from './search_or_filter';
|
||||
import {
|
||||
KueryFilterQuery,
|
||||
SerializedFilterQuery,
|
||||
State,
|
||||
timelineSelectors,
|
||||
inputsModel,
|
||||
inputsSelectors,
|
||||
} from '../../../store';
|
||||
import { timelineActions } from '../../../store/actions';
|
||||
import { KqlMode, TimelineModel } from '../../../store/timeline/model';
|
||||
import { DispatchUpdateReduxTime, dispatchUpdateReduxTime } from '../../super_date_picker';
|
||||
import { DataProvider } from '../data_providers/data_provider';
|
||||
import { SearchOrFilter } from './search_or_filter';
|
||||
|
||||
interface OwnProps {
|
||||
browserFields: BrowserFields;
|
||||
indexPattern: StaticIndexPattern;
|
||||
timelineId: string;
|
||||
}
|
||||
|
||||
interface StateReduxProps {
|
||||
dataProviders: DataProvider[];
|
||||
filters: esFilters.Filter[];
|
||||
filterQuery: KueryFilterQuery;
|
||||
filterQueryDraft: KueryFilterQuery;
|
||||
isFilterQueryDraftValid: boolean;
|
||||
kqlMode?: KqlMode;
|
||||
from: number;
|
||||
fromStr: string;
|
||||
isRefreshPaused: boolean;
|
||||
kqlMode: KqlMode;
|
||||
refreshInterval: number;
|
||||
savedQueryId: string | null;
|
||||
to: number;
|
||||
toStr: string;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
applyKqlFilterQuery: ActionCreator<{
|
||||
applyKqlFilterQuery: ({
|
||||
id,
|
||||
filterQuery,
|
||||
}: {
|
||||
id: string;
|
||||
filterQuery: SerializedFilterQuery;
|
||||
}>;
|
||||
updateKqlMode: ActionCreator<{
|
||||
id: string;
|
||||
kqlMode: KqlMode;
|
||||
}>;
|
||||
setKqlFilterQueryDraft: ActionCreator<{
|
||||
}) => void;
|
||||
updateKqlMode: ({ id, kqlMode }: { id: string; kqlMode: KqlMode }) => void;
|
||||
setKqlFilterQueryDraft: ({
|
||||
id,
|
||||
filterQueryDraft,
|
||||
}: {
|
||||
id: string;
|
||||
filterQueryDraft: KueryFilterQuery;
|
||||
}>;
|
||||
}) => void;
|
||||
setSavedQueryId: ({ id, savedQueryId }: { id: string; savedQueryId: string | null }) => void;
|
||||
setFilters: ({ id, filters }: { id: string; filters: esFilters.Filter[] }) => void;
|
||||
updateReduxTime: DispatchUpdateReduxTime;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateReduxProps & DispatchProps;
|
||||
|
@ -48,21 +74,34 @@ type Props = OwnProps & StateReduxProps & DispatchProps;
|
|||
const StatefulSearchOrFilterComponent = React.memo<Props>(
|
||||
({
|
||||
applyKqlFilterQuery,
|
||||
browserFields,
|
||||
dataProviders,
|
||||
filters,
|
||||
filterQuery,
|
||||
filterQueryDraft,
|
||||
from,
|
||||
fromStr,
|
||||
indexPattern,
|
||||
isFilterQueryDraftValid,
|
||||
isRefreshPaused,
|
||||
kqlMode,
|
||||
refreshInterval,
|
||||
savedQueryId,
|
||||
setFilters,
|
||||
setKqlFilterQueryDraft,
|
||||
setSavedQueryId,
|
||||
timelineId,
|
||||
to,
|
||||
toStr,
|
||||
updateKqlMode,
|
||||
updateReduxTime,
|
||||
}) => {
|
||||
const applyFilterQueryFromKueryExpression = useCallback(
|
||||
(expression: string) =>
|
||||
(expression: string, kind) =>
|
||||
applyKqlFilterQuery({
|
||||
id: timelineId,
|
||||
filterQuery: {
|
||||
kuery: {
|
||||
kind: 'kuery',
|
||||
kind,
|
||||
expression,
|
||||
},
|
||||
serializedQuery: convertKueryToElasticSearchQuery(expression, indexPattern),
|
||||
|
@ -72,29 +111,80 @@ const StatefulSearchOrFilterComponent = React.memo<Props>(
|
|||
);
|
||||
|
||||
const setFilterQueryDraftFromKueryExpression = useCallback(
|
||||
(expression: string) =>
|
||||
(expression: string, kind) =>
|
||||
setKqlFilterQueryDraft({
|
||||
id: timelineId,
|
||||
filterQueryDraft: {
|
||||
kind: 'kuery',
|
||||
kind,
|
||||
expression,
|
||||
},
|
||||
}),
|
||||
[timelineId]
|
||||
);
|
||||
|
||||
const setFiltersInTimeline = useCallback(
|
||||
(newFilters: esFilters.Filter[]) =>
|
||||
setFilters({
|
||||
id: timelineId,
|
||||
filters: newFilters,
|
||||
}),
|
||||
[timelineId]
|
||||
);
|
||||
|
||||
const setSavedQueryInTimeline = useCallback(
|
||||
(newSavedQueryId: string | null) =>
|
||||
setSavedQueryId({
|
||||
id: timelineId,
|
||||
savedQueryId: newSavedQueryId,
|
||||
}),
|
||||
[timelineId]
|
||||
);
|
||||
|
||||
return (
|
||||
<SearchOrFilter
|
||||
applyKqlFilterQuery={applyFilterQueryFromKueryExpression}
|
||||
browserFields={browserFields}
|
||||
dataProviders={dataProviders}
|
||||
filters={filters}
|
||||
filterQuery={filterQuery}
|
||||
filterQueryDraft={filterQueryDraft}
|
||||
from={from}
|
||||
fromStr={fromStr}
|
||||
indexPattern={indexPattern}
|
||||
isFilterQueryDraftValid={isFilterQueryDraftValid}
|
||||
isRefreshPaused={isRefreshPaused}
|
||||
kqlMode={kqlMode!}
|
||||
refreshInterval={refreshInterval}
|
||||
savedQueryId={savedQueryId}
|
||||
setFilters={setFiltersInTimeline}
|
||||
setKqlFilterQueryDraft={setFilterQueryDraftFromKueryExpression!}
|
||||
setSavedQueryId={setSavedQueryInTimeline}
|
||||
timelineId={timelineId}
|
||||
to={to}
|
||||
toStr={toStr}
|
||||
updateKqlMode={updateKqlMode!}
|
||||
updateReduxTime={updateReduxTime}
|
||||
/>
|
||||
);
|
||||
},
|
||||
(prevProps, nextProps) => {
|
||||
return (
|
||||
prevProps.from === nextProps.from &&
|
||||
prevProps.fromStr === nextProps.fromStr &&
|
||||
prevProps.to === nextProps.to &&
|
||||
prevProps.toStr === nextProps.toStr &&
|
||||
prevProps.isRefreshPaused === nextProps.isRefreshPaused &&
|
||||
prevProps.refreshInterval === nextProps.refreshInterval &&
|
||||
prevProps.timelineId === nextProps.timelineId &&
|
||||
isEqual(prevProps.browserFields, nextProps.browserFields) &&
|
||||
isEqual(prevProps.dataProviders, nextProps.dataProviders) &&
|
||||
isEqual(prevProps.filters, nextProps.filters) &&
|
||||
isEqual(prevProps.filterQuery, nextProps.filterQuery) &&
|
||||
isEqual(prevProps.filterQueryDraft, nextProps.filterQueryDraft) &&
|
||||
isEqual(prevProps.indexPattern, nextProps.indexPattern) &&
|
||||
isEqual(prevProps.kqlMode, nextProps.kqlMode) &&
|
||||
isEqual(prevProps.savedQueryId, nextProps.savedQueryId) &&
|
||||
isEqual(prevProps.timelineId, nextProps.timelineId)
|
||||
);
|
||||
}
|
||||
);
|
||||
StatefulSearchOrFilterComponent.displayName = 'StatefulSearchOrFilterComponent';
|
||||
|
@ -102,20 +192,62 @@ StatefulSearchOrFilterComponent.displayName = 'StatefulSearchOrFilterComponent';
|
|||
const makeMapStateToProps = () => {
|
||||
const getTimeline = timelineSelectors.getTimelineByIdSelector();
|
||||
const getKqlFilterQueryDraft = timelineSelectors.getKqlFilterQueryDraftSelector();
|
||||
const isFilterQueryDraftValid = timelineSelectors.isFilterQueryDraftValidSelector();
|
||||
const getKqlFilterQuery = timelineSelectors.getKqlFilterKuerySelector();
|
||||
const getInputsTimeline = inputsSelectors.getTimelineSelector();
|
||||
const getInputsPolicy = inputsSelectors.getTimelinePolicySelector();
|
||||
const mapStateToProps = (state: State, { timelineId }: OwnProps) => {
|
||||
const timeline: TimelineModel | {} = getTimeline(state, timelineId);
|
||||
const timeline: TimelineModel = getTimeline(state, timelineId);
|
||||
const input: inputsModel.InputsRange = getInputsTimeline(state);
|
||||
const policy: inputsModel.Policy = getInputsPolicy(state);
|
||||
return {
|
||||
dataProviders: timeline.dataProviders,
|
||||
filterQuery: getKqlFilterQuery(state, timelineId),
|
||||
filterQueryDraft: getKqlFilterQueryDraft(state, timelineId),
|
||||
isFilterQueryDraftValid: isFilterQueryDraftValid(state, timelineId),
|
||||
filters: timeline.filters,
|
||||
from: input.timerange.from,
|
||||
fromStr: input.timerange.fromStr,
|
||||
isRefreshPaused: policy.kind === 'manual',
|
||||
kqlMode: getOr('filter', 'kqlMode', timeline),
|
||||
refreshInterval: policy.duration,
|
||||
savedQueryId: getOr(null, 'savedQueryId', timeline),
|
||||
to: input.timerange.to,
|
||||
toStr: input.timerange.toStr,
|
||||
};
|
||||
};
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export const StatefulSearchOrFilter = connect(makeMapStateToProps, {
|
||||
applyKqlFilterQuery: timelineActions.applyKqlFilterQuery,
|
||||
setKqlFilterQueryDraft: timelineActions.setKqlFilterQueryDraft,
|
||||
updateKqlMode: timelineActions.updateKqlMode,
|
||||
})(StatefulSearchOrFilterComponent);
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
applyKqlFilterQuery: ({ id, filterQuery }: { id: string; filterQuery: SerializedFilterQuery }) =>
|
||||
dispatch(
|
||||
timelineActions.applyKqlFilterQuery({
|
||||
id,
|
||||
filterQuery,
|
||||
})
|
||||
),
|
||||
updateKqlMode: ({ id, kqlMode }: { id: string; kqlMode: KqlMode }) =>
|
||||
dispatch(timelineActions.updateKqlMode({ id, kqlMode })),
|
||||
setKqlFilterQueryDraft: ({
|
||||
id,
|
||||
filterQueryDraft,
|
||||
}: {
|
||||
id: string;
|
||||
filterQueryDraft: KueryFilterQuery;
|
||||
}) =>
|
||||
dispatch(
|
||||
timelineActions.setKqlFilterQueryDraft({
|
||||
id,
|
||||
filterQueryDraft,
|
||||
})
|
||||
),
|
||||
setSavedQueryId: ({ id, savedQueryId }: { id: string; savedQueryId: string | null }) =>
|
||||
dispatch(timelineActions.setSavedQueryId({ id, savedQueryId })),
|
||||
setFilters: ({ id, filters }: { id: string; filters: esFilters.Filter[] }) =>
|
||||
dispatch(timelineActions.setFilters({ id, filters })),
|
||||
updateReduxTime: dispatchUpdateReduxTime(dispatch),
|
||||
});
|
||||
|
||||
export const StatefulSearchOrFilter = connect(
|
||||
makeMapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(StatefulSearchOrFilterComponent);
|
||||
|
|
|
@ -10,13 +10,16 @@ import { pure } from 'recompose';
|
|||
import styled, { injectGlobal } from 'styled-components';
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
|
||||
import { KueryAutocompletion } from '../../../containers/kuery_autocompletion';
|
||||
import { KueryFilterQuery } from '../../../store';
|
||||
import { AutocompleteField } from '../../autocomplete_field';
|
||||
|
||||
import { getPlaceholderText, modes, options } from './helpers';
|
||||
import * as i18n from './translations';
|
||||
import { esFilters } from '../../../../../../../../src/plugins/data/public';
|
||||
import { BrowserFields } from '../../../containers/source';
|
||||
import { KueryFilterQuery, KueryFilterQueryKind } from '../../../store';
|
||||
import { KqlMode } from '../../../store/timeline/model';
|
||||
import { DataProvider } from '../data_providers/data_provider';
|
||||
import { QueryBarTimeline } from '../query_bar';
|
||||
|
||||
import { options } from './helpers';
|
||||
import * as i18n from './translations';
|
||||
import { DispatchUpdateReduxTime } from '../../super_date_picker';
|
||||
|
||||
const timelineSelectModeItemsClassName = 'timelineSelectModeItemsClassName';
|
||||
const searchOrFilterPopoverClassName = 'searchOrFilterPopover';
|
||||
|
@ -39,19 +42,40 @@ injectGlobal`
|
|||
`;
|
||||
|
||||
interface Props {
|
||||
applyKqlFilterQuery: (expression: string) => void;
|
||||
applyKqlFilterQuery: (expression: string, kind: KueryFilterQueryKind) => void;
|
||||
browserFields: BrowserFields;
|
||||
dataProviders: DataProvider[];
|
||||
filterQuery: KueryFilterQuery;
|
||||
filterQueryDraft: KueryFilterQuery;
|
||||
from: number;
|
||||
fromStr: string;
|
||||
indexPattern: StaticIndexPattern;
|
||||
isFilterQueryDraftValid: boolean;
|
||||
isRefreshPaused: boolean;
|
||||
kqlMode: KqlMode;
|
||||
timelineId: string;
|
||||
updateKqlMode: ({ id, kqlMode }: { id: string; kqlMode: KqlMode }) => void;
|
||||
setKqlFilterQueryDraft: (expression: string) => void;
|
||||
refreshInterval: number;
|
||||
setFilters: (filters: esFilters.Filter[]) => void;
|
||||
setKqlFilterQueryDraft: (expression: string, kind: KueryFilterQueryKind) => void;
|
||||
setSavedQueryId: (savedQueryId: string | null) => void;
|
||||
filters: esFilters.Filter[];
|
||||
savedQueryId: string | null;
|
||||
to: number;
|
||||
toStr: string;
|
||||
updateReduxTime: DispatchUpdateReduxTime;
|
||||
}
|
||||
|
||||
const SearchOrFilterContainer = styled.div`
|
||||
margin: 5px 0 10px 0;
|
||||
user-select: none;
|
||||
.globalQueryBar {
|
||||
padding: 0px;
|
||||
.kbnQueryBar {
|
||||
div:first-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
SearchOrFilterContainer.displayName = 'SearchOrFilterContainer';
|
||||
|
@ -65,13 +89,26 @@ ModeFlexItem.displayName = 'ModeFlexItem';
|
|||
export const SearchOrFilter = pure<Props>(
|
||||
({
|
||||
applyKqlFilterQuery,
|
||||
browserFields,
|
||||
dataProviders,
|
||||
indexPattern,
|
||||
isFilterQueryDraftValid,
|
||||
isRefreshPaused,
|
||||
filters,
|
||||
filterQuery,
|
||||
filterQueryDraft,
|
||||
from,
|
||||
fromStr,
|
||||
kqlMode,
|
||||
timelineId,
|
||||
refreshInterval,
|
||||
savedQueryId,
|
||||
setFilters,
|
||||
setKqlFilterQueryDraft,
|
||||
setSavedQueryId,
|
||||
to,
|
||||
toStr,
|
||||
updateKqlMode,
|
||||
updateReduxTime,
|
||||
}) => (
|
||||
<SearchOrFilterContainer>
|
||||
<EuiFlexGroup data-test-subj="timeline-search-or-filter" gutterSize="xs">
|
||||
|
@ -90,22 +127,28 @@ export const SearchOrFilter = pure<Props>(
|
|||
</EuiToolTip>
|
||||
</ModeFlexItem>
|
||||
<EuiFlexItem data-test-subj="timeline-search-or-filter-search-container">
|
||||
<EuiToolTip content={modes[kqlMode].kqlBarTooltip}>
|
||||
<KueryAutocompletion indexPattern={indexPattern}>
|
||||
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
|
||||
<AutocompleteField
|
||||
isLoadingSuggestions={isLoadingSuggestions}
|
||||
isValid={isFilterQueryDraftValid}
|
||||
loadSuggestions={loadSuggestions}
|
||||
onChange={setKqlFilterQueryDraft}
|
||||
onSubmit={applyKqlFilterQuery}
|
||||
placeholder={getPlaceholderText(kqlMode)}
|
||||
suggestions={suggestions}
|
||||
value={filterQueryDraft ? filterQueryDraft.expression : ''}
|
||||
/>
|
||||
)}
|
||||
</KueryAutocompletion>
|
||||
</EuiToolTip>
|
||||
<QueryBarTimeline
|
||||
applyKqlFilterQuery={applyKqlFilterQuery}
|
||||
browserFields={browserFields}
|
||||
dataProviders={dataProviders}
|
||||
filters={filters}
|
||||
filterQuery={filterQuery}
|
||||
filterQueryDraft={filterQueryDraft}
|
||||
from={from}
|
||||
fromStr={fromStr}
|
||||
kqlMode={kqlMode}
|
||||
indexPattern={indexPattern}
|
||||
isRefreshPaused={isRefreshPaused}
|
||||
refreshInterval={refreshInterval}
|
||||
savedQueryId={savedQueryId}
|
||||
setFilters={setFilters}
|
||||
setKqlFilterQueryDraft={setKqlFilterQueryDraft}
|
||||
setSavedQueryId={setSavedQueryId}
|
||||
timelineId={timelineId}
|
||||
to={to}
|
||||
toStr={toStr}
|
||||
updateReduxTime={updateReduxTime}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</SearchOrFilterContainer>
|
||||
|
|
|
@ -33,6 +33,7 @@ const mockUseKibanaCore = useKibanaCore as jest.Mock;
|
|||
jest.mock('../../lib/compose/kibana_core');
|
||||
mockUseKibanaCore.mockImplementation(() => ({
|
||||
uiSettings: mockUiSettings,
|
||||
savedObjects: {},
|
||||
}));
|
||||
|
||||
describe('Timeline', () => {
|
||||
|
@ -58,6 +59,7 @@ describe('Timeline', () => {
|
|||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
end={endDate}
|
||||
filters={[]}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
indexPattern={indexPattern}
|
||||
|
@ -93,6 +95,7 @@ describe('Timeline', () => {
|
|||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
end={endDate}
|
||||
filters={[]}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
indexPattern={indexPattern}
|
||||
|
@ -131,6 +134,7 @@ describe('Timeline', () => {
|
|||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
end={endDate}
|
||||
filters={[]}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
indexPattern={indexPattern}
|
||||
|
@ -169,6 +173,7 @@ describe('Timeline', () => {
|
|||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
end={endDate}
|
||||
filters={[]}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
indexPattern={indexPattern}
|
||||
|
@ -212,6 +217,7 @@ describe('Timeline', () => {
|
|||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
end={endDate}
|
||||
filters={[]}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
indexPattern={indexPattern}
|
||||
|
@ -257,6 +263,7 @@ describe('Timeline', () => {
|
|||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
end={endDate}
|
||||
filters={[]}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
indexPattern={indexPattern}
|
||||
|
@ -310,6 +317,7 @@ describe('Timeline', () => {
|
|||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
end={endDate}
|
||||
filters={[]}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
indexPattern={indexPattern}
|
||||
|
@ -367,6 +375,7 @@ describe('Timeline', () => {
|
|||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
end={endDate}
|
||||
filters={[]}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
indexPattern={indexPattern}
|
||||
|
@ -427,6 +436,7 @@ describe('Timeline', () => {
|
|||
id="foo"
|
||||
dataProviders={dataProviders}
|
||||
end={endDate}
|
||||
filters={[]}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
indexPattern={indexPattern}
|
||||
|
@ -477,6 +487,7 @@ describe('Timeline', () => {
|
|||
id="foo"
|
||||
dataProviders={dataProviders}
|
||||
end={endDate}
|
||||
filters={[]}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
indexPattern={indexPattern}
|
||||
|
@ -533,6 +544,7 @@ describe('Timeline', () => {
|
|||
id="foo"
|
||||
dataProviders={dataProviders}
|
||||
end={endDate}
|
||||
filters={[]}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
indexPattern={indexPattern}
|
||||
|
@ -593,6 +605,7 @@ describe('Timeline', () => {
|
|||
id="foo"
|
||||
dataProviders={dataProviders}
|
||||
end={endDate}
|
||||
filters={[]}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
indexPattern={indexPattern}
|
||||
|
|
|
@ -36,7 +36,7 @@ import { TimelineHeader } from './header';
|
|||
import { calculateBodyHeight, combineQueries } from './helpers';
|
||||
import { TimelineRefetch } from './refetch_timeline';
|
||||
import { ManageTimelineContext } from './timeline_context';
|
||||
import { esQuery } from '../../../../../../../src/plugins/data/public';
|
||||
import { esQuery, esFilters } from '../../../../../../../src/plugins/data/public';
|
||||
|
||||
const WrappedByAutoSizer = styled.div`
|
||||
width: 100%;
|
||||
|
@ -61,6 +61,7 @@ interface Props {
|
|||
columns: ColumnHeader[];
|
||||
dataProviders: DataProvider[];
|
||||
end: number;
|
||||
filters: esFilters.Filter[];
|
||||
flyoutHeaderHeight: number;
|
||||
flyoutHeight: number;
|
||||
id: string;
|
||||
|
@ -91,6 +92,7 @@ export const Timeline = React.memo<Props>(
|
|||
columns,
|
||||
dataProviders,
|
||||
end,
|
||||
filters,
|
||||
flyoutHeaderHeight,
|
||||
flyoutHeight,
|
||||
id,
|
||||
|
@ -119,7 +121,7 @@ export const Timeline = React.memo<Props>(
|
|||
dataProviders,
|
||||
indexPattern,
|
||||
browserFields,
|
||||
filters: [],
|
||||
filters,
|
||||
kqlQuery: { query: kqlQueryExpression, language: 'kuery' },
|
||||
kqlMode,
|
||||
start,
|
||||
|
|
|
@ -72,6 +72,27 @@ export const oneTimelineQuery = gql`
|
|||
userName
|
||||
favoriteDate
|
||||
}
|
||||
filters {
|
||||
meta {
|
||||
alias
|
||||
controlledBy
|
||||
disabled
|
||||
field
|
||||
formattedValue
|
||||
index
|
||||
key
|
||||
negate
|
||||
params
|
||||
type
|
||||
value
|
||||
}
|
||||
query
|
||||
exists
|
||||
match_all
|
||||
missing
|
||||
range
|
||||
script
|
||||
}
|
||||
kqlMode
|
||||
kqlQuery {
|
||||
filterQuery {
|
||||
|
@ -107,6 +128,7 @@ export const oneTimelineQuery = gql`
|
|||
version
|
||||
}
|
||||
title
|
||||
savedQueryId
|
||||
sort {
|
||||
columnId
|
||||
sortDirection
|
||||
|
|
|
@ -60,6 +60,27 @@ export const persistTimelineMutation = gql`
|
|||
userName
|
||||
favoriteDate
|
||||
}
|
||||
filters {
|
||||
meta {
|
||||
alias
|
||||
controlledBy
|
||||
disabled
|
||||
field
|
||||
formattedValue
|
||||
index
|
||||
key
|
||||
negate
|
||||
params
|
||||
type
|
||||
value
|
||||
}
|
||||
query
|
||||
exists
|
||||
match_all
|
||||
missing
|
||||
range
|
||||
script
|
||||
}
|
||||
kqlMode
|
||||
kqlQuery {
|
||||
filterQuery {
|
||||
|
@ -75,6 +96,7 @@ export const persistTimelineMutation = gql`
|
|||
start
|
||||
end
|
||||
}
|
||||
savedQueryId
|
||||
sort {
|
||||
columnId
|
||||
sortDirection
|
||||
|
|
|
@ -9044,18 +9044,6 @@
|
|||
"name": "TimelineResult",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "savedObjectId",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "columns",
|
||||
"description": "",
|
||||
|
@ -9072,6 +9060,22 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "createdBy",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "dataProviders",
|
||||
"description": "",
|
||||
|
@ -9136,6 +9140,22 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "filters",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "OBJECT", "name": "FilterTimelineResult", "ofType": null }
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "kqlMode",
|
||||
"description": "",
|
||||
|
@ -9217,13 +9237,25 @@
|
|||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"name": "savedQueryId",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "savedObjectId",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "sort",
|
||||
"description": "",
|
||||
|
@ -9233,15 +9265,7 @@
|
|||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "createdBy",
|
||||
"name": "title",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
|
@ -9577,6 +9601,172 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "FilterTimelineResult",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "exists",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "meta",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "OBJECT", "name": "FilterMetaTimelineResult", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "match_all",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "missing",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "query",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "range",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "script",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "FilterMetaTimelineResult",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "alias",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "controlledBy",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "disabled",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Boolean", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "formattedValue",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "index",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "negate",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Boolean", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "params",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "SerializedFilterQueryResult",
|
||||
|
@ -10175,6 +10365,20 @@
|
|||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "filters",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "INPUT_OBJECT", "name": "FilterTimelineInput", "ofType": null }
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "kqlMode",
|
||||
"description": "",
|
||||
|
@ -10203,6 +10407,12 @@
|
|||
"type": { "kind": "INPUT_OBJECT", "name": "DateRangePickerInput", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "savedQueryId",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "sort",
|
||||
"description": "",
|
||||
|
@ -10401,6 +10611,136 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "FilterTimelineInput",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "exists",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "meta",
|
||||
"description": "",
|
||||
"type": { "kind": "INPUT_OBJECT", "name": "FilterMetaTimelineInput", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "match_all",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "missing",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "query",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "range",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "script",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "FilterMetaTimelineInput",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "alias",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "controlledBy",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "disabled",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "Boolean", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "formattedValue",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "index",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "negate",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "Boolean", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "params",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "SerializedFilterQueryInput",
|
||||
|
|
|
@ -122,6 +122,8 @@ export interface TimelineInput {
|
|||
|
||||
description?: Maybe<string>;
|
||||
|
||||
filters?: Maybe<FilterTimelineInput[]>;
|
||||
|
||||
kqlMode?: Maybe<string>;
|
||||
|
||||
kqlQuery?: Maybe<SerializedFilterQueryInput>;
|
||||
|
@ -130,6 +132,8 @@ export interface TimelineInput {
|
|||
|
||||
dateRange?: Maybe<DateRangePickerInput>;
|
||||
|
||||
savedQueryId?: Maybe<string>;
|
||||
|
||||
sort?: Maybe<SortTimelineInput>;
|
||||
}
|
||||
|
||||
|
@ -185,6 +189,46 @@ export interface QueryMatchInput {
|
|||
operator?: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface FilterTimelineInput {
|
||||
exists?: Maybe<string>;
|
||||
|
||||
meta?: Maybe<FilterMetaTimelineInput>;
|
||||
|
||||
match_all?: Maybe<string>;
|
||||
|
||||
missing?: Maybe<string>;
|
||||
|
||||
query?: Maybe<string>;
|
||||
|
||||
range?: Maybe<string>;
|
||||
|
||||
script?: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface FilterMetaTimelineInput {
|
||||
alias?: Maybe<string>;
|
||||
|
||||
controlledBy?: Maybe<string>;
|
||||
|
||||
disabled?: Maybe<boolean>;
|
||||
|
||||
field?: Maybe<string>;
|
||||
|
||||
formattedValue?: Maybe<string>;
|
||||
|
||||
index?: Maybe<string>;
|
||||
|
||||
key?: Maybe<string>;
|
||||
|
||||
negate?: Maybe<boolean>;
|
||||
|
||||
params?: Maybe<string>;
|
||||
|
||||
type?: Maybe<string>;
|
||||
|
||||
value?: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface SerializedFilterQueryInput {
|
||||
filterQuery?: Maybe<SerializedKueryQueryInput>;
|
||||
}
|
||||
|
@ -1760,10 +1804,12 @@ export interface SayMyName {
|
|||
}
|
||||
|
||||
export interface TimelineResult {
|
||||
savedObjectId: string;
|
||||
|
||||
columns?: Maybe<ColumnHeaderResult[]>;
|
||||
|
||||
created?: Maybe<number>;
|
||||
|
||||
createdBy?: Maybe<string>;
|
||||
|
||||
dataProviders?: Maybe<DataProviderResult[]>;
|
||||
|
||||
dateRange?: Maybe<DateRangePickerResult>;
|
||||
|
@ -1774,6 +1820,8 @@ export interface TimelineResult {
|
|||
|
||||
favorite?: Maybe<FavoriteTimelineResult[]>;
|
||||
|
||||
filters?: Maybe<FilterTimelineResult[]>;
|
||||
|
||||
kqlMode?: Maybe<string>;
|
||||
|
||||
kqlQuery?: Maybe<SerializedFilterQueryResult>;
|
||||
|
@ -1786,13 +1834,13 @@ export interface TimelineResult {
|
|||
|
||||
pinnedEventsSaveObject?: Maybe<PinnedEvent[]>;
|
||||
|
||||
title?: Maybe<string>;
|
||||
savedQueryId?: Maybe<string>;
|
||||
|
||||
savedObjectId: string;
|
||||
|
||||
sort?: Maybe<SortTimelineResult>;
|
||||
|
||||
created?: Maybe<number>;
|
||||
|
||||
createdBy?: Maybe<string>;
|
||||
title?: Maybe<string>;
|
||||
|
||||
updated?: Maybe<number>;
|
||||
|
||||
|
@ -1867,6 +1915,46 @@ export interface FavoriteTimelineResult {
|
|||
favoriteDate?: Maybe<number>;
|
||||
}
|
||||
|
||||
export interface FilterTimelineResult {
|
||||
exists?: Maybe<string>;
|
||||
|
||||
meta?: Maybe<FilterMetaTimelineResult>;
|
||||
|
||||
match_all?: Maybe<string>;
|
||||
|
||||
missing?: Maybe<string>;
|
||||
|
||||
query?: Maybe<string>;
|
||||
|
||||
range?: Maybe<string>;
|
||||
|
||||
script?: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface FilterMetaTimelineResult {
|
||||
alias?: Maybe<string>;
|
||||
|
||||
controlledBy?: Maybe<string>;
|
||||
|
||||
disabled?: Maybe<boolean>;
|
||||
|
||||
field?: Maybe<string>;
|
||||
|
||||
formattedValue?: Maybe<string>;
|
||||
|
||||
index?: Maybe<string>;
|
||||
|
||||
key?: Maybe<string>;
|
||||
|
||||
negate?: Maybe<boolean>;
|
||||
|
||||
params?: Maybe<string>;
|
||||
|
||||
type?: Maybe<string>;
|
||||
|
||||
value?: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface SerializedFilterQueryResult {
|
||||
filterQuery?: Maybe<SerializedKueryQueryResult>;
|
||||
}
|
||||
|
@ -4874,6 +4962,8 @@ export namespace GetOneTimeline {
|
|||
|
||||
favorite: Maybe<Favorite[]>;
|
||||
|
||||
filters: Maybe<Filters[]>;
|
||||
|
||||
kqlMode: Maybe<string>;
|
||||
|
||||
kqlQuery: Maybe<KqlQuery>;
|
||||
|
@ -4888,6 +4978,8 @@ export namespace GetOneTimeline {
|
|||
|
||||
title: Maybe<string>;
|
||||
|
||||
savedQueryId: Maybe<string>;
|
||||
|
||||
sort: Maybe<Sort>;
|
||||
|
||||
created: Maybe<number>;
|
||||
|
@ -5029,6 +5121,50 @@ export namespace GetOneTimeline {
|
|||
favoriteDate: Maybe<number>;
|
||||
};
|
||||
|
||||
export type Filters = {
|
||||
__typename?: 'FilterTimelineResult';
|
||||
|
||||
meta: Maybe<Meta>;
|
||||
|
||||
query: Maybe<string>;
|
||||
|
||||
exists: Maybe<string>;
|
||||
|
||||
match_all: Maybe<string>;
|
||||
|
||||
missing: Maybe<string>;
|
||||
|
||||
range: Maybe<string>;
|
||||
|
||||
script: Maybe<string>;
|
||||
};
|
||||
|
||||
export type Meta = {
|
||||
__typename?: 'FilterMetaTimelineResult';
|
||||
|
||||
alias: Maybe<string>;
|
||||
|
||||
controlledBy: Maybe<string>;
|
||||
|
||||
disabled: Maybe<boolean>;
|
||||
|
||||
field: Maybe<string>;
|
||||
|
||||
formattedValue: Maybe<string>;
|
||||
|
||||
index: Maybe<string>;
|
||||
|
||||
key: Maybe<string>;
|
||||
|
||||
negate: Maybe<boolean>;
|
||||
|
||||
params: Maybe<string>;
|
||||
|
||||
type: Maybe<string>;
|
||||
|
||||
value: Maybe<string>;
|
||||
};
|
||||
|
||||
export type KqlQuery = {
|
||||
__typename?: 'SerializedFilterQueryResult';
|
||||
|
||||
|
@ -5142,6 +5278,8 @@ export namespace PersistTimelineMutation {
|
|||
|
||||
favorite: Maybe<Favorite[]>;
|
||||
|
||||
filters: Maybe<Filters[]>;
|
||||
|
||||
kqlMode: Maybe<string>;
|
||||
|
||||
kqlQuery: Maybe<KqlQuery>;
|
||||
|
@ -5150,6 +5288,8 @@ export namespace PersistTimelineMutation {
|
|||
|
||||
dateRange: Maybe<DateRange>;
|
||||
|
||||
savedQueryId: Maybe<string>;
|
||||
|
||||
sort: Maybe<Sort>;
|
||||
|
||||
created: Maybe<number>;
|
||||
|
@ -5257,6 +5397,50 @@ export namespace PersistTimelineMutation {
|
|||
favoriteDate: Maybe<number>;
|
||||
};
|
||||
|
||||
export type Filters = {
|
||||
__typename?: 'FilterTimelineResult';
|
||||
|
||||
meta: Maybe<Meta>;
|
||||
|
||||
query: Maybe<string>;
|
||||
|
||||
exists: Maybe<string>;
|
||||
|
||||
match_all: Maybe<string>;
|
||||
|
||||
missing: Maybe<string>;
|
||||
|
||||
range: Maybe<string>;
|
||||
|
||||
script: Maybe<string>;
|
||||
};
|
||||
|
||||
export type Meta = {
|
||||
__typename?: 'FilterMetaTimelineResult';
|
||||
|
||||
alias: Maybe<string>;
|
||||
|
||||
controlledBy: Maybe<string>;
|
||||
|
||||
disabled: Maybe<boolean>;
|
||||
|
||||
field: Maybe<string>;
|
||||
|
||||
formattedValue: Maybe<string>;
|
||||
|
||||
index: Maybe<string>;
|
||||
|
||||
key: Maybe<string>;
|
||||
|
||||
negate: Maybe<boolean>;
|
||||
|
||||
params: Maybe<string>;
|
||||
|
||||
type: Maybe<string>;
|
||||
|
||||
value: Maybe<string>;
|
||||
};
|
||||
|
||||
export type KqlQuery = {
|
||||
__typename?: 'SerializedFilterQueryResult';
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
DEFAULT_BYTES_FORMAT,
|
||||
DEFAULT_KBN_VERSION,
|
||||
DEFAULT_TIMEZONE_BROWSER,
|
||||
DEFAULT_TIMEPICKER_QUICK_RANGES,
|
||||
} from '../../common/constants';
|
||||
|
||||
export interface MockFrameworks {
|
||||
|
@ -29,6 +30,61 @@ export const getMockKibanaUiSetting = (config: MockFrameworks) => (key: string)
|
|||
return ['8.0.0'];
|
||||
} else if (key === DEFAULT_TIMEZONE_BROWSER) {
|
||||
return config && config.timezone ? [config.timezone] : ['America/New_York'];
|
||||
} else if (key === DEFAULT_TIMEPICKER_QUICK_RANGES) {
|
||||
return [
|
||||
[
|
||||
{
|
||||
from: 'now/d',
|
||||
to: 'now/d',
|
||||
display: 'Today',
|
||||
},
|
||||
{
|
||||
from: 'now/w',
|
||||
to: 'now/w',
|
||||
display: 'This week',
|
||||
},
|
||||
{
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
display: 'Last 15 minutes',
|
||||
},
|
||||
{
|
||||
from: 'now-30m',
|
||||
to: 'now',
|
||||
display: 'Last 30 minutes',
|
||||
},
|
||||
{
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
display: 'Last 1 hour',
|
||||
},
|
||||
{
|
||||
from: 'now-24h',
|
||||
to: 'now',
|
||||
display: 'Last 24 hours',
|
||||
},
|
||||
{
|
||||
from: 'now-7d',
|
||||
to: 'now',
|
||||
display: 'Last 7 days',
|
||||
},
|
||||
{
|
||||
from: 'now-30d',
|
||||
to: 'now',
|
||||
display: 'Last 30 days',
|
||||
},
|
||||
{
|
||||
from: 'now-90d',
|
||||
to: 'now',
|
||||
display: 'Last 90 days',
|
||||
},
|
||||
{
|
||||
from: 'now-1y',
|
||||
to: 'now',
|
||||
display: 'Last 1 year',
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
return [null];
|
||||
};
|
||||
|
|
|
@ -18,8 +18,14 @@ import { Store } from 'redux';
|
|||
import { BehaviorSubject } from 'rxjs';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
import { createStore, State } from '../store';
|
||||
import { mockGlobalState } from './global_state';
|
||||
import { mockUiSettings } from './ui_settings';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
const state: State = mockGlobalState;
|
||||
|
||||
|
@ -36,17 +42,57 @@ export const apolloClient = new ApolloClient({
|
|||
|
||||
export const apolloClientObservable = new BehaviorSubject(apolloClient);
|
||||
|
||||
const services = {
|
||||
uiSettings: mockUiSettings,
|
||||
savedObjects: {} as CoreStart['savedObjects'],
|
||||
notifications: {} as CoreStart['notifications'],
|
||||
docLinks: {
|
||||
links: {
|
||||
query: {
|
||||
kueryQuerySyntax: '',
|
||||
},
|
||||
},
|
||||
} as CoreStart['docLinks'],
|
||||
http: {} as CoreStart['http'],
|
||||
overlays: {} as CoreStart['overlays'],
|
||||
storage: {
|
||||
get: () => {},
|
||||
},
|
||||
};
|
||||
|
||||
const localStorageMock = () => {
|
||||
let store: Record<string, unknown> = {};
|
||||
|
||||
return {
|
||||
getItem: (key: string) => {
|
||||
return store[key] || null;
|
||||
},
|
||||
setItem: (key: string, value: unknown) => {
|
||||
store[key] = value;
|
||||
},
|
||||
clear() {
|
||||
store = {};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: localStorageMock(),
|
||||
});
|
||||
|
||||
/** A utility for wrapping children in the providers required to run most tests */
|
||||
export const TestProviders = pure<Props>(
|
||||
({ children, store = createStore(state, apolloClientObservable), onDragEnd = jest.fn() }) => (
|
||||
<I18nProvider>
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
|
||||
</ThemeProvider>
|
||||
</ReduxStoreProvider>
|
||||
</ApolloProvider>
|
||||
<KibanaContextProvider services={services}>
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
|
||||
</ThemeProvider>
|
||||
</ReduxStoreProvider>
|
||||
</ApolloProvider>
|
||||
</KibanaContextProvider>
|
||||
</I18nProvider>
|
||||
)
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
DEFAULT_SIEM_TIME_RANGE,
|
||||
DEFAULT_SIEM_REFRESH_INTERVAL,
|
||||
DEFAULT_INDEX_KEY,
|
||||
DEFAULT_DATE_FORMAT,
|
||||
DEFAULT_DATE_FORMAT_TZ,
|
||||
DEFAULT_DARK_MODE,
|
||||
DEFAULT_TIME_RANGE,
|
||||
|
@ -40,6 +41,8 @@ chrome.getUiSettingsClient().get.mockImplementation((key: string) => {
|
|||
return defaultIndexPattern;
|
||||
case DEFAULT_DATE_FORMAT_TZ:
|
||||
return 'Asia/Taipei';
|
||||
case DEFAULT_DATE_FORMAT:
|
||||
return 'MMM D, YYYY @ HH:mm:ss.SSS';
|
||||
case DEFAULT_DARK_MODE:
|
||||
return false;
|
||||
default:
|
||||
|
@ -62,6 +65,9 @@ export const mockUiSettings = {
|
|||
get: (item: Config) => {
|
||||
return mockUiSettings[item];
|
||||
},
|
||||
get$: () => ({
|
||||
subscribe: jest.fn(),
|
||||
}),
|
||||
'query:allowLeadingWildcards': true,
|
||||
'query:queryString:options': {},
|
||||
'courier:ignoreFilterIfFieldNotInIndex': true,
|
||||
|
|
|
@ -39,10 +39,13 @@ jest.mock('../../../containers/source', () => ({
|
|||
}));
|
||||
|
||||
// Test will fail because we will to need to mock some core services to make the test work
|
||||
// For now let's forget about SiemSearchBar
|
||||
// For now let's forget about SiemSearchBar and QueryBar
|
||||
jest.mock('../../../components/search_bar', () => ({
|
||||
SiemSearchBar: () => null,
|
||||
}));
|
||||
jest.mock('../../../components/query_bar', () => ({
|
||||
QueryBar: () => null,
|
||||
}));
|
||||
|
||||
describe('body', () => {
|
||||
const scenariosMap = {
|
||||
|
|
|
@ -38,10 +38,13 @@ jest.mock('ui/documentation_links', () => ({
|
|||
}));
|
||||
|
||||
// Test will fail because we will to need to mock some core services to make the test work
|
||||
// For now let's forget about SiemSearchBar
|
||||
// For now let's forget about SiemSearchBar and QueryBar
|
||||
jest.mock('../../components/search_bar', () => ({
|
||||
SiemSearchBar: () => null,
|
||||
}));
|
||||
jest.mock('../../components/query_bar', () => ({
|
||||
QueryBar: () => null,
|
||||
}));
|
||||
|
||||
let localSource: Array<{
|
||||
request: {};
|
||||
|
|
|
@ -38,10 +38,13 @@ mockUseKibanaCore.mockImplementation(() => ({
|
|||
}));
|
||||
|
||||
// Test will fail because we will to need to mock some core services to make the test work
|
||||
// For now let's forget about SiemSearchBar
|
||||
// For now let's forget about SiemSearchBar and QueryBar
|
||||
jest.mock('../../../components/search_bar', () => ({
|
||||
SiemSearchBar: () => null,
|
||||
}));
|
||||
jest.mock('../../../components/query_bar', () => ({
|
||||
QueryBar: () => null,
|
||||
}));
|
||||
|
||||
let localSource: Array<{
|
||||
request: {};
|
||||
|
|
|
@ -33,10 +33,13 @@ mockUseKibanaCore.mockImplementation(() => ({
|
|||
}));
|
||||
|
||||
// Test will fail because we will to need to mock some core services to make the test work
|
||||
// For now let's forget about SiemSearchBar
|
||||
// For now let's forget about SiemSearchBar and QueryBar
|
||||
jest.mock('../../components/search_bar', () => ({
|
||||
SiemSearchBar: () => null,
|
||||
}));
|
||||
jest.mock('../../components/query_bar', () => ({
|
||||
QueryBar: () => null,
|
||||
}));
|
||||
|
||||
let localSource: Array<{
|
||||
request: {};
|
||||
|
|
|
@ -73,3 +73,6 @@ export const globalFiltersQuerySelector = () =>
|
|||
createSelector(selectGlobal, global => global.filters || []);
|
||||
|
||||
export const getTimelineSelector = () => createSelector(selectTimeline, timeline => timeline);
|
||||
|
||||
export const getTimelinePolicySelector = () =>
|
||||
createSelector(selectTimeline, timeline => timeline.policy);
|
||||
|
|
|
@ -10,8 +10,10 @@ export { hostsModel } from './hosts';
|
|||
export { dragAndDropModel } from './drag_and_drop';
|
||||
export { networkModel } from './network';
|
||||
|
||||
export type KueryFilterQueryKind = 'kuery' | 'lucene';
|
||||
|
||||
export interface KueryFilterQuery {
|
||||
kind: 'kuery';
|
||||
kind: KueryFilterQueryKind;
|
||||
expression: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import actionCreatorFactory from 'typescript-fsa';
|
||||
|
||||
import { esFilters } from '../../../../../../../src/plugins/data/public';
|
||||
import { ColumnHeader } from '../../components/timeline/body/column_headers/column_header';
|
||||
import { Sort } from '../../components/timeline/body/sort';
|
||||
import {
|
||||
|
@ -187,3 +188,13 @@ export const updateAutoSaveMsg = actionCreator<{
|
|||
}>('UPDATE_AUTO_SAVE');
|
||||
|
||||
export const showCallOutUnauthorizedMsg = actionCreator('SHOW_CALL_OUT_UNAUTHORIZED_MSG');
|
||||
|
||||
export const setSavedQueryId = actionCreator<{
|
||||
id: string;
|
||||
savedQueryId: string | null;
|
||||
}>('SET_TIMELINE_SAVED_QUERY');
|
||||
|
||||
export const setFilters = actionCreator<{
|
||||
id: string;
|
||||
filters: esFilters.Filter[];
|
||||
}>('SET_TIMELINE_FILTERS');
|
||||
|
|
283
x-pack/legacy/plugins/siem/public/store/timeline/epic.test.ts
Normal file
283
x-pack/legacy/plugins/siem/public/store/timeline/epic.test.ts
Normal file
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { TimelineModel } from './model';
|
||||
import { Direction } from '../../graphql/types';
|
||||
import { convertTimelineAsInput } from './epic';
|
||||
|
||||
import { esFilters } from '../../../../../../../src/plugins/data/public';
|
||||
import { FilterStateStore } from '../../../../../../../src/plugins/data/common/es_query/filters';
|
||||
|
||||
describe('Epic Timeline', () => {
|
||||
describe('#convertTimelineAsInput ', () => {
|
||||
test('should return a TimelineInput instead of TimelineModel ', () => {
|
||||
const timelineModel: TimelineModel = {
|
||||
columns: [
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: '@timestamp',
|
||||
width: 190,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'message',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'event.category',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'event.action',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'host.name',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'source.ip',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'destination.ip',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'user.name',
|
||||
width: 180,
|
||||
},
|
||||
],
|
||||
dataProviders: [
|
||||
{
|
||||
id: 'hosts-table-hostName-DESKTOP-QBBSCUT',
|
||||
name: 'DESKTOP-QBBSCUT',
|
||||
enabled: true,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: 'host.name',
|
||||
value: 'DESKTOP-QBBSCUT',
|
||||
operator: ':',
|
||||
},
|
||||
and: [
|
||||
{
|
||||
id:
|
||||
'plain-column-renderer-data-provider-hosts-page-event_module-CQg7I24BHe9nqdOi_LYL-event_module-endgame',
|
||||
name: 'event.module: endgame',
|
||||
enabled: true,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: 'event.module',
|
||||
value: 'endgame',
|
||||
operator: ':',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
filters: [
|
||||
{
|
||||
$state: { store: FilterStateStore.APP_STATE },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'event.category',
|
||||
negate: false,
|
||||
params: { query: 'file' },
|
||||
type: 'phrase',
|
||||
},
|
||||
query: { match_phrase: { 'event.category': 'file' } },
|
||||
},
|
||||
{
|
||||
$state: { store: FilterStateStore.APP_STATE },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: '@timestamp',
|
||||
negate: false,
|
||||
type: 'exists',
|
||||
value: 'exists',
|
||||
},
|
||||
exists: { field: '@timestamp' },
|
||||
} as esFilters.Filter,
|
||||
],
|
||||
isFavorite: false,
|
||||
isLive: false,
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
itemsPerPage: 25,
|
||||
itemsPerPageOptions: [10, 25, 50, 100],
|
||||
kqlMode: 'filter',
|
||||
kqlQuery: {
|
||||
filterQuery: {
|
||||
kuery: { kind: 'kuery', expression: 'endgame.user_name : "zeus" ' },
|
||||
serializedQuery:
|
||||
'{"bool":{"should":[{"match_phrase":{"endgame.user_name":"zeus"}}],"minimum_should_match":1}}',
|
||||
},
|
||||
filterQueryDraft: { kind: 'kuery', expression: 'endgame.user_name : "zeus" ' },
|
||||
},
|
||||
title: 'saved',
|
||||
noteIds: [],
|
||||
pinnedEventIds: {},
|
||||
pinnedEventsSaveObject: {},
|
||||
dateRange: { start: 1572469587644, end: 1572555987644 },
|
||||
savedObjectId: '11169110-fc22-11e9-8ca9-072f15ce2685',
|
||||
show: true,
|
||||
sort: { columnId: '@timestamp', sortDirection: Direction.desc },
|
||||
width: 1100,
|
||||
version: 'WzM4LDFd',
|
||||
id: '11169110-fc22-11e9-8ca9-072f15ce2685',
|
||||
savedQueryId: 'my endgame timeline query',
|
||||
};
|
||||
|
||||
expect(
|
||||
convertTimelineAsInput(timelineModel, {
|
||||
kind: 'absolute',
|
||||
from: 1572469587644,
|
||||
fromStr: undefined,
|
||||
to: 1572555987644,
|
||||
toStr: undefined,
|
||||
})
|
||||
).toEqual({
|
||||
columns: [
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: '@timestamp',
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'message',
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'event.category',
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'event.action',
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'host.name',
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'source.ip',
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'destination.ip',
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'user.name',
|
||||
},
|
||||
],
|
||||
dataProviders: [
|
||||
{
|
||||
and: [
|
||||
{
|
||||
enabled: true,
|
||||
excluded: false,
|
||||
id:
|
||||
'plain-column-renderer-data-provider-hosts-page-event_module-CQg7I24BHe9nqdOi_LYL-event_module-endgame',
|
||||
kqlQuery: '',
|
||||
name: 'event.module: endgame',
|
||||
queryMatch: {
|
||||
field: 'event.module',
|
||||
operator: ':',
|
||||
value: 'endgame',
|
||||
},
|
||||
},
|
||||
],
|
||||
enabled: true,
|
||||
excluded: false,
|
||||
id: 'hosts-table-hostName-DESKTOP-QBBSCUT',
|
||||
kqlQuery: '',
|
||||
name: 'DESKTOP-QBBSCUT',
|
||||
queryMatch: {
|
||||
field: 'host.name',
|
||||
operator: ':',
|
||||
value: 'DESKTOP-QBBSCUT',
|
||||
},
|
||||
},
|
||||
],
|
||||
dateRange: {
|
||||
end: 1572555987644,
|
||||
start: 1572469587644,
|
||||
},
|
||||
description: '',
|
||||
filters: [
|
||||
{
|
||||
exists: null,
|
||||
match_all: null,
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
field: null,
|
||||
key: 'event.category',
|
||||
negate: false,
|
||||
params: '{"query":"file"}',
|
||||
type: 'phrase',
|
||||
value: null,
|
||||
},
|
||||
missing: null,
|
||||
query: '{"match_phrase":{"event.category":"file"}}',
|
||||
range: null,
|
||||
script: null,
|
||||
},
|
||||
{
|
||||
exists: '{"field":"@timestamp"}',
|
||||
match_all: null,
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
field: null,
|
||||
key: '@timestamp',
|
||||
negate: false,
|
||||
params: null,
|
||||
type: 'exists',
|
||||
value: 'exists',
|
||||
},
|
||||
missing: null,
|
||||
query: null,
|
||||
range: null,
|
||||
script: null,
|
||||
},
|
||||
],
|
||||
kqlMode: 'filter',
|
||||
kqlQuery: {
|
||||
filterQuery: {
|
||||
kuery: {
|
||||
expression: 'endgame.user_name : "zeus" ',
|
||||
kind: 'kuery',
|
||||
},
|
||||
serializedQuery:
|
||||
'{"bool":{"should":[{"match_phrase":{"endgame.user_name":"zeus"}}],"minimum_should_match":1}}',
|
||||
},
|
||||
},
|
||||
savedQueryId: 'my endgame timeline query',
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
sortDirection: 'desc',
|
||||
},
|
||||
title: 'saved',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,7 +4,15 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { get, has, merge as mergeObject, set, omit } from 'lodash/fp';
|
||||
import {
|
||||
get,
|
||||
has,
|
||||
merge as mergeObject,
|
||||
set,
|
||||
omit,
|
||||
isObject,
|
||||
toString as fpToString,
|
||||
} from 'lodash/fp';
|
||||
import { Action } from 'redux';
|
||||
import { Epic } from 'redux-observable';
|
||||
import { from, Observable, empty, merge } from 'rxjs';
|
||||
|
@ -20,6 +28,7 @@ import {
|
|||
takeUntil,
|
||||
} from 'rxjs/operators';
|
||||
|
||||
import { esFilters } from '../../../../../../../src/plugins/data/public';
|
||||
import { ColumnHeader } from '../../components/timeline/body/column_headers/column_header';
|
||||
import { persistTimelineMutation } from '../../containers/timeline/persist.gql_query';
|
||||
import {
|
||||
|
@ -52,6 +61,8 @@ import {
|
|||
updateTimeline,
|
||||
updateTitle,
|
||||
updateAutoSaveMsg,
|
||||
setFilters,
|
||||
setSavedQueryId,
|
||||
startTimelineSaving,
|
||||
endTimelineSaving,
|
||||
createTimeline,
|
||||
|
@ -81,6 +92,8 @@ const timelineActionsType = [
|
|||
dataProviderEdited.type,
|
||||
removeColumn.type,
|
||||
removeProvider.type,
|
||||
setFilters.type,
|
||||
setSavedQueryId.type,
|
||||
updateColumns.type,
|
||||
updateDataProviderEnabled.type,
|
||||
updateDataProviderExcluded.type,
|
||||
|
@ -235,14 +248,16 @@ const timelineInput: TimelineInput = {
|
|||
columns: null,
|
||||
dataProviders: null,
|
||||
description: null,
|
||||
filters: null,
|
||||
kqlMode: null,
|
||||
kqlQuery: null,
|
||||
title: null,
|
||||
dateRange: null,
|
||||
savedQueryId: null,
|
||||
sort: null,
|
||||
};
|
||||
|
||||
const convertTimelineAsInput = (
|
||||
export const convertTimelineAsInput = (
|
||||
timeline: TimelineModel,
|
||||
timelineTimeRange: TimeRange
|
||||
): TimelineInput =>
|
||||
|
@ -258,6 +273,65 @@ const convertTimelineAsInput = (
|
|||
get(key, timeline).map((col: ColumnHeader) => omit(['width', '__typename'], col)),
|
||||
acc
|
||||
);
|
||||
} else if (key === 'filters' && get(key, timeline) != null) {
|
||||
const filters = get(key, timeline);
|
||||
return set(
|
||||
key,
|
||||
filters != null
|
||||
? filters.map((myFilter: esFilters.Filter) => {
|
||||
const basicFilter = omit(['$state'], myFilter);
|
||||
return {
|
||||
...basicFilter,
|
||||
meta: {
|
||||
...basicFilter.meta,
|
||||
field:
|
||||
(esFilters.isMatchAllFilter(basicFilter) ||
|
||||
esFilters.isPhraseFilter(basicFilter) ||
|
||||
esFilters.isPhrasesFilter(basicFilter) ||
|
||||
esFilters.isRangeFilter(basicFilter)) &&
|
||||
basicFilter.meta.field != null
|
||||
? convertToString(basicFilter.meta.field)
|
||||
: null,
|
||||
value:
|
||||
basicFilter.meta.value != null
|
||||
? convertToString(basicFilter.meta.value)
|
||||
: null,
|
||||
params:
|
||||
basicFilter.meta.params != null
|
||||
? convertToString(basicFilter.meta.params)
|
||||
: null,
|
||||
},
|
||||
...(esFilters.isMatchAllFilter(basicFilter)
|
||||
? {
|
||||
match_all: convertToString(
|
||||
(basicFilter as esFilters.MatchAllFilter).match_all
|
||||
),
|
||||
}
|
||||
: { match_all: null }),
|
||||
...(esFilters.isMissingFilter(basicFilter) && basicFilter.missing != null
|
||||
? { missing: convertToString(basicFilter.missing) }
|
||||
: { missing: null }),
|
||||
...(esFilters.isExistsFilter(basicFilter) && basicFilter.exists != null
|
||||
? { exists: convertToString(basicFilter.exists) }
|
||||
: { exists: null }),
|
||||
...((esFilters.isQueryStringFilter(basicFilter) ||
|
||||
get('query', basicFilter) != null) &&
|
||||
basicFilter.query != null
|
||||
? { query: convertToString(basicFilter.query) }
|
||||
: { query: null }),
|
||||
...(esFilters.isRangeFilter(basicFilter) && basicFilter.range != null
|
||||
? { range: convertToString(basicFilter.range) }
|
||||
: { range: null }),
|
||||
...(esFilters.isRangeFilter(basicFilter) &&
|
||||
basicFilter.script !=
|
||||
null /* TODO remove it when PR50713 is merged || esFilters.isPhraseFilter(basicFilter) */
|
||||
? { script: convertToString(basicFilter.script) }
|
||||
: { script: null }),
|
||||
};
|
||||
})
|
||||
: [],
|
||||
acc
|
||||
);
|
||||
}
|
||||
return set(key, get(key, timeline), acc);
|
||||
}
|
||||
|
@ -271,3 +345,14 @@ const omitTypenameInTimeline = (
|
|||
oldTimeline: TimelineModel,
|
||||
newTimeline: TimelineResult
|
||||
): TimelineModel => JSON.parse(JSON.stringify(mergeObject(oldTimeline, newTimeline)), omitTypename);
|
||||
|
||||
const convertToString = (obj: unknown) => {
|
||||
try {
|
||||
if (isObject(obj)) {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
return fpToString(obj);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { getOr, omit, uniq, isEmpty, isEqualWith } from 'lodash/fp';
|
||||
|
||||
import { esFilters } from '../../../../../../../src/plugins/data/public';
|
||||
import { ColumnHeader } from '../../components/timeline/body/column_headers/column_header';
|
||||
import { getColumnWidthFromType } from '../../components/timeline/body/helpers';
|
||||
import { Sort } from '../../components/timeline/body/sort';
|
||||
|
@ -1135,3 +1137,43 @@ export const updateHighlightedDropAndProvider = ({
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface UpdateSavedQueryParams {
|
||||
id: string;
|
||||
savedQueryId: string | null;
|
||||
timelineById: TimelineById;
|
||||
}
|
||||
|
||||
export const updateSavedQuery = ({
|
||||
id,
|
||||
savedQueryId,
|
||||
timelineById,
|
||||
}: UpdateSavedQueryParams): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
|
||||
return {
|
||||
...timelineById,
|
||||
[id]: {
|
||||
...timeline,
|
||||
savedQueryId,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface UpdateFiltersParams {
|
||||
id: string;
|
||||
filters: esFilters.Filter[];
|
||||
timelineById: TimelineById;
|
||||
}
|
||||
|
||||
export const updateFilters = ({ id, filters, timelineById }: UpdateFiltersParams): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
|
||||
return {
|
||||
...timelineById,
|
||||
[id]: {
|
||||
...timeline,
|
||||
filters,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { esFilters } from '../../../../../../../src/plugins/data/public';
|
||||
import { ColumnHeader } from '../../components/timeline/body/column_headers/column_header';
|
||||
import { DataProvider } from '../../components/timeline/data_providers/data_provider';
|
||||
import { DEFAULT_TIMELINE_WIDTH } from '../../components/timeline/body/helpers';
|
||||
|
@ -25,6 +26,7 @@ export interface TimelineModel {
|
|||
description: string;
|
||||
/** A map of events in this timeline to the chronologically ordered notes (in this timeline) associated with the event */
|
||||
eventIdToNoteIds: Record<string, string[]>;
|
||||
filters?: esFilters.Filter[];
|
||||
/** The chronological history of actions related to this timeline */
|
||||
historyIds: string[];
|
||||
/** The chronological history of actions related to this timeline */
|
||||
|
@ -59,6 +61,7 @@ export interface TimelineModel {
|
|||
start: number;
|
||||
end: number;
|
||||
};
|
||||
savedQueryId?: string | null;
|
||||
/** When true, show the timeline flyover */
|
||||
show: boolean;
|
||||
/** Specifies which column the timeline is sorted on, and the direction (ascending / descending) */
|
||||
|
@ -77,6 +80,7 @@ export const timelineDefaults: Readonly<Pick<
|
|||
| 'dataProviders'
|
||||
| 'description'
|
||||
| 'eventIdToNoteIds'
|
||||
| 'filters'
|
||||
| 'highlightedDropAndProviderId'
|
||||
| 'historyIds'
|
||||
| 'isFavorite'
|
||||
|
@ -104,6 +108,7 @@ export const timelineDefaults: Readonly<Pick<
|
|||
eventIdToNoteIds: {},
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
filters: [],
|
||||
isFavorite: false,
|
||||
isLive: false,
|
||||
isLoading: false,
|
||||
|
|
|
@ -45,6 +45,8 @@ import {
|
|||
updateTitle,
|
||||
upsertColumn,
|
||||
updateIsLoading,
|
||||
setSavedQueryId,
|
||||
setFilters,
|
||||
} from './actions';
|
||||
import {
|
||||
addNewTimeline,
|
||||
|
@ -80,6 +82,8 @@ import {
|
|||
updateTimelineSort,
|
||||
updateTimelineTitle,
|
||||
upsertTimelineColumn,
|
||||
updateSavedQuery,
|
||||
updateFilters,
|
||||
} from './helpers';
|
||||
|
||||
import { TimelineState, EMPTY_TIMELINE_BY_ID } from './types';
|
||||
|
@ -132,7 +136,11 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
}))
|
||||
.case(applyKqlFilterQuery, (state, { id, filterQuery }) => ({
|
||||
...state,
|
||||
timelineById: applyKqlFilterQueryDraft({ id, filterQuery, timelineById: state.timelineById }),
|
||||
timelineById: applyKqlFilterQueryDraft({
|
||||
id,
|
||||
filterQuery,
|
||||
timelineById: state.timelineById,
|
||||
}),
|
||||
}))
|
||||
.case(setKqlFilterQueryDraft, (state, { id, filterQueryDraft }) => ({
|
||||
...state,
|
||||
|
@ -361,4 +369,20 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
...state,
|
||||
showCallOutUnauthorizedMsg: true,
|
||||
}))
|
||||
.case(setSavedQueryId, (state, { id, savedQueryId }) => ({
|
||||
...state,
|
||||
timelineById: updateSavedQuery({
|
||||
id,
|
||||
savedQueryId,
|
||||
timelineById: state.timelineById,
|
||||
}),
|
||||
}))
|
||||
.case(setFilters, (state, { id, filters }) => ({
|
||||
...state,
|
||||
timelineById: updateFilters({
|
||||
id,
|
||||
filters,
|
||||
timelineById: state.timelineById,
|
||||
}),
|
||||
}))
|
||||
.build();
|
||||
|
|
|
@ -14,12 +14,6 @@ mockDispatch.mockImplementation(fn => fn);
|
|||
|
||||
const applyTimelineKqlMock: jest.Mock = (dispatchApplyTimelineFilterQuery as unknown) as jest.Mock;
|
||||
|
||||
jest.mock('../../store/hosts/actions', () => ({
|
||||
applyHostsFilterQuery: jest.fn(),
|
||||
}));
|
||||
jest.mock('../../store/network/actions', () => ({
|
||||
applyNetworkFilterQuery: jest.fn(),
|
||||
}));
|
||||
jest.mock('../../store/timeline/actions', () => ({
|
||||
applyKqlFilterQuery: jest.fn(),
|
||||
}));
|
||||
|
@ -36,7 +30,6 @@ describe('#useUpdateKql', () => {
|
|||
kueryFilterQuery: { expression: '', kind: 'kuery' },
|
||||
kueryFilterQueryDraft: { expression: 'host.name: "myLove"', kind: 'kuery' },
|
||||
storeType: 'timelineType',
|
||||
type: null,
|
||||
timelineId: 'myTimelineId',
|
||||
})(mockDispatch);
|
||||
expect(applyTimelineKqlMock).toHaveBeenCalledWith({
|
||||
|
|
|
@ -18,7 +18,6 @@ interface UseUpdateKqlProps {
|
|||
kueryFilterQuery: KueryFilterQuery | null;
|
||||
kueryFilterQueryDraft: KueryFilterQuery | null;
|
||||
storeType: 'timelineType';
|
||||
type: null;
|
||||
timelineId?: string;
|
||||
}
|
||||
|
||||
|
@ -28,7 +27,6 @@ export const useUpdateKql = ({
|
|||
kueryFilterQueryDraft,
|
||||
storeType,
|
||||
timelineId,
|
||||
type,
|
||||
}: UseUpdateKqlProps): RefetchKql => {
|
||||
const updateKql: RefetchKql = (dispatch: Dispatch) => {
|
||||
if (kueryFilterQueryDraft != null && !isEqual(kueryFilterQuery, kueryFilterQueryDraft)) {
|
||||
|
@ -37,10 +35,7 @@ export const useUpdateKql = ({
|
|||
dispatchApplyTimelineFilterQuery({
|
||||
id: timelineId,
|
||||
filterQuery: {
|
||||
kuery: {
|
||||
kind: 'kuery',
|
||||
expression: kueryFilterQueryDraft.expression,
|
||||
},
|
||||
kuery: kueryFilterQueryDraft,
|
||||
serializedQuery: convertKueryToElasticSearchQuery(
|
||||
kueryFilterQueryDraft.expression,
|
||||
indexPattern
|
||||
|
@ -49,7 +44,6 @@ export const useUpdateKql = ({
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
SavedQueryService,
|
||||
createSavedQueryService,
|
||||
} from '../../../../../../../src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service';
|
||||
|
||||
import { useKibanaCore } from '../../lib/compose/kibana_core';
|
||||
|
||||
export const useSavedQueryServices = () => {
|
||||
const core = useKibanaCore();
|
||||
const [savedQueryService, setSavedQueryService] = useState<SavedQueryService>(
|
||||
createSavedQueryService(core.savedObjects.client)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setSavedQueryService(createSavedQueryService(core.savedObjects.client));
|
||||
}, [core.savedObjects.client]);
|
||||
return savedQueryService;
|
||||
};
|
|
@ -49,6 +49,20 @@ const sortTimeline = `
|
|||
sortDirection: String
|
||||
`;
|
||||
|
||||
const filtersMetaTimeline = `
|
||||
alias: String
|
||||
controlledBy: String
|
||||
disabled: Boolean
|
||||
field: String
|
||||
formattedValue: String
|
||||
index: String
|
||||
key: String
|
||||
negate: Boolean
|
||||
params: String
|
||||
type: String
|
||||
value: String
|
||||
`;
|
||||
|
||||
export const timelineSchema = gql`
|
||||
###############
|
||||
#### INPUT ####
|
||||
|
@ -97,14 +111,30 @@ export const timelineSchema = gql`
|
|||
${sortTimeline}
|
||||
}
|
||||
|
||||
input FilterMetaTimelineInput {
|
||||
${filtersMetaTimeline}
|
||||
}
|
||||
|
||||
input FilterTimelineInput {
|
||||
exists: String
|
||||
meta: FilterMetaTimelineInput
|
||||
match_all: String
|
||||
missing: String
|
||||
query: String
|
||||
range: String
|
||||
script: String
|
||||
}
|
||||
|
||||
input TimelineInput {
|
||||
columns: [ColumnHeaderInput!]
|
||||
dataProviders: [DataProviderInput!]
|
||||
description: String
|
||||
filters: [FilterTimelineInput!]
|
||||
kqlMode: String
|
||||
kqlQuery: SerializedFilterQueryInput
|
||||
title: String
|
||||
dateRange: DateRangePickerInput
|
||||
savedQueryId: String
|
||||
sort: SortTimelineInput
|
||||
}
|
||||
|
||||
|
@ -171,24 +201,40 @@ export const timelineSchema = gql`
|
|||
${sortTimeline}
|
||||
}
|
||||
|
||||
type FilterMetaTimelineResult {
|
||||
${filtersMetaTimeline}
|
||||
}
|
||||
|
||||
type FilterTimelineResult {
|
||||
exists: String
|
||||
meta: FilterMetaTimelineResult
|
||||
match_all: String
|
||||
missing: String
|
||||
query: String
|
||||
range: String
|
||||
script: String
|
||||
}
|
||||
|
||||
type TimelineResult {
|
||||
savedObjectId: String!
|
||||
columns: [ColumnHeaderResult!]
|
||||
created: Float
|
||||
createdBy: String
|
||||
dataProviders: [DataProviderResult!]
|
||||
dateRange: DateRangePickerResult
|
||||
description: String
|
||||
eventIdToNoteIds: [NoteResult!]
|
||||
favorite: [FavoriteTimelineResult!]
|
||||
filters: [FilterTimelineResult!]
|
||||
kqlMode: String
|
||||
kqlQuery: SerializedFilterQueryResult
|
||||
notes: [NoteResult!]
|
||||
noteIds: [String!]
|
||||
pinnedEventIds: [String!]
|
||||
pinnedEventsSaveObject: [PinnedEvent!]
|
||||
title: String
|
||||
savedQueryId: String
|
||||
savedObjectId: String!
|
||||
sort: SortTimelineResult
|
||||
created: Float
|
||||
createdBy: String
|
||||
title: String
|
||||
updated: Float
|
||||
updatedBy: String
|
||||
version: String!
|
||||
|
|
|
@ -124,6 +124,8 @@ export interface TimelineInput {
|
|||
|
||||
description?: Maybe<string>;
|
||||
|
||||
filters?: Maybe<FilterTimelineInput[]>;
|
||||
|
||||
kqlMode?: Maybe<string>;
|
||||
|
||||
kqlQuery?: Maybe<SerializedFilterQueryInput>;
|
||||
|
@ -132,6 +134,8 @@ export interface TimelineInput {
|
|||
|
||||
dateRange?: Maybe<DateRangePickerInput>;
|
||||
|
||||
savedQueryId?: Maybe<string>;
|
||||
|
||||
sort?: Maybe<SortTimelineInput>;
|
||||
}
|
||||
|
||||
|
@ -187,6 +191,46 @@ export interface QueryMatchInput {
|
|||
operator?: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface FilterTimelineInput {
|
||||
exists?: Maybe<string>;
|
||||
|
||||
meta?: Maybe<FilterMetaTimelineInput>;
|
||||
|
||||
match_all?: Maybe<string>;
|
||||
|
||||
missing?: Maybe<string>;
|
||||
|
||||
query?: Maybe<string>;
|
||||
|
||||
range?: Maybe<string>;
|
||||
|
||||
script?: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface FilterMetaTimelineInput {
|
||||
alias?: Maybe<string>;
|
||||
|
||||
controlledBy?: Maybe<string>;
|
||||
|
||||
disabled?: Maybe<boolean>;
|
||||
|
||||
field?: Maybe<string>;
|
||||
|
||||
formattedValue?: Maybe<string>;
|
||||
|
||||
index?: Maybe<string>;
|
||||
|
||||
key?: Maybe<string>;
|
||||
|
||||
negate?: Maybe<boolean>;
|
||||
|
||||
params?: Maybe<string>;
|
||||
|
||||
type?: Maybe<string>;
|
||||
|
||||
value?: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface SerializedFilterQueryInput {
|
||||
filterQuery?: Maybe<SerializedKueryQueryInput>;
|
||||
}
|
||||
|
@ -1762,10 +1806,12 @@ export interface SayMyName {
|
|||
}
|
||||
|
||||
export interface TimelineResult {
|
||||
savedObjectId: string;
|
||||
|
||||
columns?: Maybe<ColumnHeaderResult[]>;
|
||||
|
||||
created?: Maybe<number>;
|
||||
|
||||
createdBy?: Maybe<string>;
|
||||
|
||||
dataProviders?: Maybe<DataProviderResult[]>;
|
||||
|
||||
dateRange?: Maybe<DateRangePickerResult>;
|
||||
|
@ -1776,6 +1822,8 @@ export interface TimelineResult {
|
|||
|
||||
favorite?: Maybe<FavoriteTimelineResult[]>;
|
||||
|
||||
filters?: Maybe<FilterTimelineResult[]>;
|
||||
|
||||
kqlMode?: Maybe<string>;
|
||||
|
||||
kqlQuery?: Maybe<SerializedFilterQueryResult>;
|
||||
|
@ -1788,13 +1836,13 @@ export interface TimelineResult {
|
|||
|
||||
pinnedEventsSaveObject?: Maybe<PinnedEvent[]>;
|
||||
|
||||
title?: Maybe<string>;
|
||||
savedQueryId?: Maybe<string>;
|
||||
|
||||
savedObjectId: string;
|
||||
|
||||
sort?: Maybe<SortTimelineResult>;
|
||||
|
||||
created?: Maybe<number>;
|
||||
|
||||
createdBy?: Maybe<string>;
|
||||
title?: Maybe<string>;
|
||||
|
||||
updated?: Maybe<number>;
|
||||
|
||||
|
@ -1869,6 +1917,46 @@ export interface FavoriteTimelineResult {
|
|||
favoriteDate?: Maybe<number>;
|
||||
}
|
||||
|
||||
export interface FilterTimelineResult {
|
||||
exists?: Maybe<string>;
|
||||
|
||||
meta?: Maybe<FilterMetaTimelineResult>;
|
||||
|
||||
match_all?: Maybe<string>;
|
||||
|
||||
missing?: Maybe<string>;
|
||||
|
||||
query?: Maybe<string>;
|
||||
|
||||
range?: Maybe<string>;
|
||||
|
||||
script?: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface FilterMetaTimelineResult {
|
||||
alias?: Maybe<string>;
|
||||
|
||||
controlledBy?: Maybe<string>;
|
||||
|
||||
disabled?: Maybe<boolean>;
|
||||
|
||||
field?: Maybe<string>;
|
||||
|
||||
formattedValue?: Maybe<string>;
|
||||
|
||||
index?: Maybe<string>;
|
||||
|
||||
key?: Maybe<string>;
|
||||
|
||||
negate?: Maybe<boolean>;
|
||||
|
||||
params?: Maybe<string>;
|
||||
|
||||
type?: Maybe<string>;
|
||||
|
||||
value?: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface SerializedFilterQueryResult {
|
||||
filterQuery?: Maybe<SerializedKueryQueryResult>;
|
||||
}
|
||||
|
@ -7469,10 +7557,12 @@ export namespace SayMyNameResolvers {
|
|||
|
||||
export namespace TimelineResultResolvers {
|
||||
export interface Resolvers<TContext = SiemContext, TypeParent = TimelineResult> {
|
||||
savedObjectId?: SavedObjectIdResolver<string, TypeParent, TContext>;
|
||||
|
||||
columns?: ColumnsResolver<Maybe<ColumnHeaderResult[]>, TypeParent, TContext>;
|
||||
|
||||
created?: CreatedResolver<Maybe<number>, TypeParent, TContext>;
|
||||
|
||||
createdBy?: CreatedByResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
dataProviders?: DataProvidersResolver<Maybe<DataProviderResult[]>, TypeParent, TContext>;
|
||||
|
||||
dateRange?: DateRangeResolver<Maybe<DateRangePickerResult>, TypeParent, TContext>;
|
||||
|
@ -7483,6 +7573,8 @@ export namespace TimelineResultResolvers {
|
|||
|
||||
favorite?: FavoriteResolver<Maybe<FavoriteTimelineResult[]>, TypeParent, TContext>;
|
||||
|
||||
filters?: FiltersResolver<Maybe<FilterTimelineResult[]>, TypeParent, TContext>;
|
||||
|
||||
kqlMode?: KqlModeResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
kqlQuery?: KqlQueryResolver<Maybe<SerializedFilterQueryResult>, TypeParent, TContext>;
|
||||
|
@ -7499,13 +7591,13 @@ export namespace TimelineResultResolvers {
|
|||
TContext
|
||||
>;
|
||||
|
||||
title?: TitleResolver<Maybe<string>, TypeParent, TContext>;
|
||||
savedQueryId?: SavedQueryIdResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
savedObjectId?: SavedObjectIdResolver<string, TypeParent, TContext>;
|
||||
|
||||
sort?: SortResolver<Maybe<SortTimelineResult>, TypeParent, TContext>;
|
||||
|
||||
created?: CreatedResolver<Maybe<number>, TypeParent, TContext>;
|
||||
|
||||
createdBy?: CreatedByResolver<Maybe<string>, TypeParent, TContext>;
|
||||
title?: TitleResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
updated?: UpdatedResolver<Maybe<number>, TypeParent, TContext>;
|
||||
|
||||
|
@ -7514,13 +7606,18 @@ export namespace TimelineResultResolvers {
|
|||
version?: VersionResolver<string, TypeParent, TContext>;
|
||||
}
|
||||
|
||||
export type SavedObjectIdResolver<
|
||||
R = string,
|
||||
export type ColumnsResolver<
|
||||
R = Maybe<ColumnHeaderResult[]>,
|
||||
Parent = TimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type ColumnsResolver<
|
||||
R = Maybe<ColumnHeaderResult[]>,
|
||||
export type CreatedResolver<
|
||||
R = Maybe<number>,
|
||||
Parent = TimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type CreatedByResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = TimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
|
@ -7549,6 +7646,11 @@ export namespace TimelineResultResolvers {
|
|||
Parent = TimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type FiltersResolver<
|
||||
R = Maybe<FilterTimelineResult[]>,
|
||||
Parent = TimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type KqlModeResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = TimelineResult,
|
||||
|
@ -7579,22 +7681,22 @@ export namespace TimelineResultResolvers {
|
|||
Parent = TimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type TitleResolver<
|
||||
export type SavedQueryIdResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = TimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type SavedObjectIdResolver<
|
||||
R = string,
|
||||
Parent = TimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type SortResolver<
|
||||
R = Maybe<SortTimelineResult>,
|
||||
Parent = TimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type CreatedResolver<
|
||||
R = Maybe<number>,
|
||||
Parent = TimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type CreatedByResolver<
|
||||
export type TitleResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = TimelineResult,
|
||||
TContext = SiemContext
|
||||
|
@ -7837,6 +7939,142 @@ export namespace FavoriteTimelineResultResolvers {
|
|||
> = Resolver<R, Parent, TContext>;
|
||||
}
|
||||
|
||||
export namespace FilterTimelineResultResolvers {
|
||||
export interface Resolvers<TContext = SiemContext, TypeParent = FilterTimelineResult> {
|
||||
exists?: ExistsResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
meta?: MetaResolver<Maybe<FilterMetaTimelineResult>, TypeParent, TContext>;
|
||||
|
||||
match_all?: MatchAllResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
missing?: MissingResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
query?: QueryResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
range?: RangeResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
script?: ScriptResolver<Maybe<string>, TypeParent, TContext>;
|
||||
}
|
||||
|
||||
export type ExistsResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type MetaResolver<
|
||||
R = Maybe<FilterMetaTimelineResult>,
|
||||
Parent = FilterTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type MatchAllResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type MissingResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type QueryResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type RangeResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type ScriptResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
}
|
||||
|
||||
export namespace FilterMetaTimelineResultResolvers {
|
||||
export interface Resolvers<TContext = SiemContext, TypeParent = FilterMetaTimelineResult> {
|
||||
alias?: AliasResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
controlledBy?: ControlledByResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
disabled?: DisabledResolver<Maybe<boolean>, TypeParent, TContext>;
|
||||
|
||||
field?: FieldResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
formattedValue?: FormattedValueResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
index?: IndexResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
key?: KeyResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
negate?: NegateResolver<Maybe<boolean>, TypeParent, TContext>;
|
||||
|
||||
params?: ParamsResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
type?: TypeResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
value?: ValueResolver<Maybe<string>, TypeParent, TContext>;
|
||||
}
|
||||
|
||||
export type AliasResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterMetaTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type ControlledByResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterMetaTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type DisabledResolver<
|
||||
R = Maybe<boolean>,
|
||||
Parent = FilterMetaTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type FieldResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterMetaTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type FormattedValueResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterMetaTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type IndexResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterMetaTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type KeyResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterMetaTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type NegateResolver<
|
||||
R = Maybe<boolean>,
|
||||
Parent = FilterMetaTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type ParamsResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterMetaTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type TypeResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterMetaTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type ValueResolver<
|
||||
R = Maybe<string>,
|
||||
Parent = FilterMetaTimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
}
|
||||
|
||||
export namespace SerializedFilterQueryResultResolvers {
|
||||
export interface Resolvers<TContext = SiemContext, TypeParent = SerializedFilterQueryResult> {
|
||||
filterQuery?: FilterQueryResolver<Maybe<SerializedKueryQueryResult>, TypeParent, TContext>;
|
||||
|
@ -8484,6 +8722,8 @@ export type IResolvers<TContext = SiemContext> = {
|
|||
QueryMatchResult?: QueryMatchResultResolvers.Resolvers<TContext>;
|
||||
DateRangePickerResult?: DateRangePickerResultResolvers.Resolvers<TContext>;
|
||||
FavoriteTimelineResult?: FavoriteTimelineResultResolvers.Resolvers<TContext>;
|
||||
FilterTimelineResult?: FilterTimelineResultResolvers.Resolvers<TContext>;
|
||||
FilterMetaTimelineResult?: FilterMetaTimelineResultResolvers.Resolvers<TContext>;
|
||||
SerializedFilterQueryResult?: SerializedFilterQueryResultResolvers.Resolvers<TContext>;
|
||||
SerializedKueryQueryResult?: SerializedKueryQueryResultResolvers.Resolvers<TContext>;
|
||||
KueryFilterQueryResult?: KueryFilterQueryResultResolvers.Resolvers<TContext>;
|
||||
|
|
|
@ -146,6 +146,65 @@ export const timelineSavedObjectMappings: {
|
|||
},
|
||||
},
|
||||
},
|
||||
filters: {
|
||||
properties: {
|
||||
meta: {
|
||||
properties: {
|
||||
alias: {
|
||||
type: 'text',
|
||||
},
|
||||
controlledBy: {
|
||||
type: 'text',
|
||||
},
|
||||
disabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
field: {
|
||||
type: 'text',
|
||||
},
|
||||
formattedValue: {
|
||||
type: 'text',
|
||||
},
|
||||
index: {
|
||||
type: 'keyword',
|
||||
},
|
||||
key: {
|
||||
type: 'keyword',
|
||||
},
|
||||
negate: {
|
||||
type: 'boolean',
|
||||
},
|
||||
params: {
|
||||
type: 'text',
|
||||
},
|
||||
type: {
|
||||
type: 'keyword',
|
||||
},
|
||||
value: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
exists: {
|
||||
type: 'text',
|
||||
},
|
||||
match_all: {
|
||||
type: 'text',
|
||||
},
|
||||
missing: {
|
||||
type: 'text',
|
||||
},
|
||||
query: {
|
||||
type: 'text',
|
||||
},
|
||||
range: {
|
||||
type: 'text',
|
||||
},
|
||||
script: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
kqlMode: {
|
||||
type: 'keyword',
|
||||
},
|
||||
|
@ -183,6 +242,9 @@ export const timelineSavedObjectMappings: {
|
|||
},
|
||||
},
|
||||
},
|
||||
savedQueryId: {
|
||||
type: 'keyword',
|
||||
},
|
||||
sort: {
|
||||
properties: {
|
||||
columnId: {
|
||||
|
|
|
@ -59,6 +59,33 @@ const SavedDataProviderRuntimeType = runtimeTypes.partial({
|
|||
and: unionWithNullType(runtimeTypes.array(SavedDataProviderQueryMatchRuntimeType)),
|
||||
});
|
||||
|
||||
/*
|
||||
* Filters Types
|
||||
*/
|
||||
const SavedFilterMetaRuntimeType = runtimeTypes.partial({
|
||||
alias: unionWithNullType(runtimeTypes.string),
|
||||
controlledBy: unionWithNullType(runtimeTypes.string),
|
||||
disabled: unionWithNullType(runtimeTypes.boolean),
|
||||
field: unionWithNullType(runtimeTypes.string),
|
||||
formattedValue: unionWithNullType(runtimeTypes.string),
|
||||
index: unionWithNullType(runtimeTypes.string),
|
||||
key: unionWithNullType(runtimeTypes.string),
|
||||
negate: unionWithNullType(runtimeTypes.boolean),
|
||||
params: unionWithNullType(runtimeTypes.string),
|
||||
type: unionWithNullType(runtimeTypes.string),
|
||||
value: unionWithNullType(runtimeTypes.string),
|
||||
});
|
||||
|
||||
const SavedFilterRuntimeType = runtimeTypes.partial({
|
||||
exists: unionWithNullType(runtimeTypes.string),
|
||||
meta: unionWithNullType(SavedFilterMetaRuntimeType),
|
||||
match_all: unionWithNullType(runtimeTypes.string),
|
||||
missing: unionWithNullType(runtimeTypes.string),
|
||||
query: unionWithNullType(runtimeTypes.string),
|
||||
range: unionWithNullType(runtimeTypes.string),
|
||||
script: unionWithNullType(runtimeTypes.string),
|
||||
});
|
||||
|
||||
/*
|
||||
* kqlQuery -> filterQuery Types
|
||||
*/
|
||||
|
@ -110,10 +137,12 @@ export const SavedTimelineRuntimeType = runtimeTypes.partial({
|
|||
dataProviders: unionWithNullType(runtimeTypes.array(SavedDataProviderRuntimeType)),
|
||||
description: unionWithNullType(runtimeTypes.string),
|
||||
favorite: unionWithNullType(runtimeTypes.array(SavedFavoriteRuntimeType)),
|
||||
filters: unionWithNullType(runtimeTypes.array(SavedFilterRuntimeType)),
|
||||
kqlMode: unionWithNullType(runtimeTypes.string),
|
||||
kqlQuery: unionWithNullType(SavedFilterQueryQueryRuntimeType),
|
||||
title: unionWithNullType(runtimeTypes.string),
|
||||
dateRange: unionWithNullType(SavedDateRangePickerRuntimeType),
|
||||
savedQueryId: unionWithNullType(runtimeTypes.string),
|
||||
sort: unionWithNullType(SavedSortRuntimeType),
|
||||
created: unionWithNullType(runtimeTypes.number),
|
||||
createdBy: unionWithNullType(runtimeTypes.string),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue