mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[TIP] Unify filters with the rest of Security Solution (#142336)
This commit is contained in:
parent
5d82869ca0
commit
824b7f307c
22 changed files with 73 additions and 787 deletions
|
@ -11,8 +11,9 @@ import type { SecuritySolutionPluginContext } from '@kbn/threat-intelligence-plu
|
|||
import { THREAT_INTELLIGENCE_BASE_PATH } from '@kbn/threat-intelligence-plugin/public';
|
||||
import type { SourcererDataView } from '@kbn/threat-intelligence-plugin/public/types';
|
||||
import type { Store } from 'redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useInvestigateInTimeline } from './use_investigate_in_timeline';
|
||||
import { getStore } from '../common/store';
|
||||
import { getStore, inputsSelectors } from '../common/store';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import { FiltersGlobal } from '../common/components/filters_global';
|
||||
import { SpyRoute } from '../common/utils/route/spy_routes';
|
||||
|
@ -21,6 +22,8 @@ import { SecurityPageName } from '../app/types';
|
|||
import type { SecuritySubPluginRoutes } from '../app/types';
|
||||
import { useSourcererDataView } from '../common/containers/sourcerer';
|
||||
import { SecuritySolutionPageWrapper } from '../common/components/page_wrapper';
|
||||
import { SiemSearchBar } from '../common/components/search_bar';
|
||||
import { useGlobalTime } from '../common/containers/use_global_time';
|
||||
|
||||
const ThreatIntelligence = memo(() => {
|
||||
const { threatIntelligence } = useKibana().services;
|
||||
|
@ -37,6 +40,12 @@ const ThreatIntelligence = memo(() => {
|
|||
sourcererDataView: sourcererDataView as unknown as SourcererDataView,
|
||||
getSecuritySolutionStore: securitySolutionStore,
|
||||
getUseInvestigateInTimeline: useInvestigateInTimeline,
|
||||
|
||||
useQuery: () => useSelector(inputsSelectors.globalQuerySelector()),
|
||||
useFilters: () => useSelector(inputsSelectors.globalFiltersQuerySelector()),
|
||||
useGlobalTime,
|
||||
|
||||
SiemSearchBar,
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -12,17 +12,11 @@
|
|||
"requiredPlugins": [
|
||||
"data",
|
||||
"dataViews",
|
||||
"unifiedSearch",
|
||||
"kibanaUtils",
|
||||
"navigation",
|
||||
"kibanaReact",
|
||||
"triggersActionsUi",
|
||||
"inspector"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"data",
|
||||
"unifiedSearch",
|
||||
"kibanaUtils",
|
||||
"kibanaReact"
|
||||
]
|
||||
"requiredBundles": ["data", "kibanaUtils", "kibanaReact"]
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
import { FilterManager } from '@kbn/data-plugin/public';
|
||||
import { IndicatorsFiltersContextValue } from '../../modules/indicators/containers/indicators_filters/context';
|
||||
|
||||
export const mockTimeRange = { from: '2022-10-03T07:48:31.498Z', to: '2022-10-03T07:48:31.498Z' };
|
||||
|
||||
export const mockIndicatorsFiltersContext: IndicatorsFiltersContextValue = {
|
||||
filterManager: {
|
||||
getFilters: () => [],
|
||||
|
@ -18,7 +20,5 @@ export const mockIndicatorsFiltersContext: IndicatorsFiltersContextValue = {
|
|||
language: 'kuery',
|
||||
query: '',
|
||||
},
|
||||
handleSavedQuery: () => {},
|
||||
handleSubmitQuery: () => {},
|
||||
handleSubmitTimeRange: () => {},
|
||||
timeRange: mockTimeRange,
|
||||
};
|
||||
|
|
|
@ -36,4 +36,12 @@ export const getSecuritySolutionContextMock = (): SecuritySolutionPluginContext
|
|||
({ dataProviders, from, to }) =>
|
||||
() =>
|
||||
new Promise((resolve) => window.alert('investigate in timeline')),
|
||||
|
||||
SiemSearchBar: () => <div data-test-subj="SiemSearchBar">mock siem search</div>,
|
||||
|
||||
useFilters: () => [],
|
||||
|
||||
useGlobalTime: () => ({ from: '', to: '' }),
|
||||
|
||||
useQuery: () => ({ language: 'kuery', query: '' }),
|
||||
});
|
||||
|
|
|
@ -16,7 +16,6 @@ import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
|||
import { IUiSettingsClient } from '@kbn/core/public';
|
||||
import { StoryProvidersComponent } from '../../../../common/mocks/story_providers';
|
||||
import { mockKibanaTimelinesService } from '../../../../common/mocks/mock_kibana_timelines_service';
|
||||
import { DEFAULT_TIME_RANGE } from '../../../query_bar/hooks/use_filters/utils';
|
||||
import { IndicatorsBarChartWrapper } from '.';
|
||||
import { Aggregation, AGGREGATION_NAME, ChartSeries } from '../../services';
|
||||
|
||||
|
@ -25,7 +24,7 @@ export default {
|
|||
title: 'IndicatorsBarChartWrapper',
|
||||
};
|
||||
|
||||
const mockTimeRange: TimeRange = DEFAULT_TIME_RANGE;
|
||||
const mockTimeRange: TimeRange = { from: '', to: '' };
|
||||
|
||||
const mockIndexPattern: DataView = {
|
||||
fields: [
|
||||
|
@ -161,16 +160,6 @@ InitialLoad.decorators = [(story) => <MemoryRouter>{story()}</MemoryRouter>];
|
|||
|
||||
export const UpdatingData: Story<void> = () => {
|
||||
const mockIndicators: ChartSeries[] = [
|
||||
{
|
||||
x: '1 Jan 2022 00:00:00 GMT',
|
||||
y: 2,
|
||||
g: '[Filebeat] AbuseCH Malware',
|
||||
},
|
||||
{
|
||||
x: '1 Jan 2022 00:00:00 GMT',
|
||||
y: 10,
|
||||
g: '[Filebeat] AbuseCH MalwareBazaar',
|
||||
},
|
||||
{
|
||||
x: '1 Jan 2022 06:00:00 GMT',
|
||||
y: 0,
|
||||
|
|
|
@ -11,7 +11,6 @@ import { TimeRange } from '@kbn/es-query';
|
|||
import { DataView, DataViewField } from '@kbn/data-views-plugin/common';
|
||||
import { TestProvidersComponent } from '../../../../common/mocks/test_providers';
|
||||
import { CHART_UPDATE_PROGRESS_TEST_ID, IndicatorsBarChartWrapper } from '.';
|
||||
import { DEFAULT_TIME_RANGE } from '../../../query_bar/hooks/use_filters/utils';
|
||||
import moment from 'moment';
|
||||
|
||||
jest.mock('../../../query_bar/hooks/use_filters');
|
||||
|
@ -29,7 +28,7 @@ const mockIndexPattern: DataView = {
|
|||
],
|
||||
} as DataView;
|
||||
|
||||
const mockTimeRange: TimeRange = DEFAULT_TIME_RANGE;
|
||||
const mockTimeRange: TimeRange = { from: '', to: '' };
|
||||
|
||||
describe('<IndicatorsBarChartWrapper />', () => {
|
||||
describe('when not loading or refetching', () => {
|
||||
|
|
|
@ -6,18 +6,14 @@
|
|||
*/
|
||||
|
||||
import { createContext } from 'react';
|
||||
import { FilterManager, SavedQuery } from '@kbn/data-plugin/public';
|
||||
import { Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import { FilterManager } from '@kbn/data-plugin/public';
|
||||
|
||||
export interface IndicatorsFiltersContextValue {
|
||||
timeRange?: TimeRange;
|
||||
timeRange: TimeRange;
|
||||
filters: Filter[];
|
||||
filterQuery: Query;
|
||||
handleSavedQuery: (savedQuery: SavedQuery | undefined) => void;
|
||||
handleSubmitTimeRange: (timeRange?: TimeRange) => void;
|
||||
handleSubmitQuery: (filterQuery: Query) => void;
|
||||
filterManager: FilterManager;
|
||||
savedQuery?: SavedQuery;
|
||||
}
|
||||
|
||||
export const IndicatorsFiltersContext = createContext<IndicatorsFiltersContextValue | undefined>(
|
||||
|
|
|
@ -5,29 +5,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { SavedQuery } from '@kbn/data-plugin/common';
|
||||
import { Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { IndicatorsFiltersContext, IndicatorsFiltersContextValue } from './context';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { useKibana } from '../../../../hooks/use_kibana';
|
||||
|
||||
import {
|
||||
DEFAULT_QUERY,
|
||||
DEFAULT_TIME_RANGE,
|
||||
encodeState,
|
||||
FILTERS_QUERYSTRING_NAMESPACE,
|
||||
stateFromQueryParams,
|
||||
} from '../../../query_bar/hooks/use_filters/utils';
|
||||
import { useSecurityContext } from '../../../../hooks/use_security_context';
|
||||
import { IndicatorsFiltersContext, IndicatorsFiltersContextValue } from './context';
|
||||
|
||||
/**
|
||||
* Container used to wrap components and share the {@link FilterManager} through React context.
|
||||
*/
|
||||
export const IndicatorsFilters: FC = ({ children }) => {
|
||||
const { pathname: browserPathName, search } = useLocation();
|
||||
const history = useHistory();
|
||||
const [savedQuery, setSavedQuery] = useState<SavedQuery | undefined>(undefined);
|
||||
const securityContext = useSecurityContext();
|
||||
|
||||
const {
|
||||
services: {
|
||||
|
@ -37,72 +24,18 @@ export const IndicatorsFilters: FC = ({ children }) => {
|
|||
},
|
||||
} = useKibana();
|
||||
|
||||
// Filters are picked using the UI widgets
|
||||
const [filters, setFilters] = useState<Filter[]>([]);
|
||||
|
||||
// Time range is self explanatory
|
||||
const [timeRange, setTimeRange] = useState<TimeRange | undefined>(DEFAULT_TIME_RANGE);
|
||||
|
||||
// filterQuery is raw kql query that user can type in to filter results
|
||||
const [filterQuery, setFilterQuery] = useState<Query>(DEFAULT_QUERY);
|
||||
|
||||
// Serialize filters into query string
|
||||
useEffect(() => {
|
||||
const filterStateAsString = encodeState({ filters, filterQuery, timeRange });
|
||||
if (!deepEqual(filterManager.getFilters(), filters)) {
|
||||
filterManager.setFilters(filters);
|
||||
}
|
||||
|
||||
history.replace({
|
||||
pathname: browserPathName,
|
||||
search: `${FILTERS_QUERYSTRING_NAMESPACE}=${filterStateAsString}`,
|
||||
});
|
||||
}, [browserPathName, filterManager, filterQuery, filters, history, timeRange]);
|
||||
|
||||
// Sync filterManager to local state (after they are changed from the ui)
|
||||
useEffect(() => {
|
||||
const subscription = filterManager.getUpdates$().subscribe(() => {
|
||||
setFilters(filterManager.getFilters());
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
}, [filterManager]);
|
||||
|
||||
// Update local state with filter values from the url (on initial mount)
|
||||
useEffect(() => {
|
||||
const {
|
||||
filters: filtersFromQuery,
|
||||
timeRange: timeRangeFromQuery,
|
||||
filterQuery: filterQueryFromQuery,
|
||||
} = stateFromQueryParams(search);
|
||||
|
||||
setTimeRange(timeRangeFromQuery);
|
||||
setFilterQuery(filterQueryFromQuery);
|
||||
setFilters(filtersFromQuery);
|
||||
|
||||
// We only want to have it done on initial render with initial 'search' value;
|
||||
// that is why 'search' is ommited from the deps array
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [filterManager]);
|
||||
|
||||
const onSavedQuery = useCallback(
|
||||
(newSavedQuery: SavedQuery | undefined) => setSavedQuery(newSavedQuery),
|
||||
[]
|
||||
);
|
||||
const globalFilters = securityContext.useFilters();
|
||||
const globalQuery = securityContext.useQuery();
|
||||
const globalTimeRange = securityContext.useGlobalTime();
|
||||
|
||||
const contextValue: IndicatorsFiltersContextValue = useMemo(
|
||||
() => ({
|
||||
timeRange,
|
||||
filters,
|
||||
filterQuery,
|
||||
handleSavedQuery: onSavedQuery,
|
||||
handleSubmitTimeRange: setTimeRange,
|
||||
handleSubmitQuery: setFilterQuery,
|
||||
timeRange: globalTimeRange,
|
||||
filters: globalFilters,
|
||||
filterQuery: globalQuery,
|
||||
filterManager,
|
||||
savedQuery,
|
||||
}),
|
||||
[filterManager, filterQuery, filters, onSavedQuery, savedQuery, timeRange]
|
||||
[globalFilters, globalQuery, globalTimeRange, filterManager]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -7,17 +7,17 @@
|
|||
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { useAggregatedIndicators, UseAggregatedIndicatorsParam } from './use_aggregated_indicators';
|
||||
import { DEFAULT_TIME_RANGE } from '../../query_bar/hooks/use_filters/utils';
|
||||
import {
|
||||
mockedTimefilterService,
|
||||
TestProvidersComponent,
|
||||
} from '../../../common/mocks/test_providers';
|
||||
import { createFetchAggregatedIndicators } from '../services';
|
||||
import { mockTimeRange } from '../../../common/mocks/mock_indicators_filters_context';
|
||||
|
||||
jest.mock('../services/fetch_aggregated_indicators');
|
||||
|
||||
const useAggregatedIndicatorsParams: UseAggregatedIndicatorsParam = {
|
||||
timeRange: DEFAULT_TIME_RANGE,
|
||||
timeRange: mockTimeRange,
|
||||
filters: [],
|
||||
filterQuery: { language: 'kuery', query: '' },
|
||||
};
|
||||
|
@ -28,8 +28,7 @@ const renderUseAggregatedIndicators = () =>
|
|||
wrapper: TestProvidersComponent,
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/142312
|
||||
describe.skip('useAggregatedIndicators()', () => {
|
||||
describe('useAggregatedIndicators()', () => {
|
||||
beforeEach(jest.clearAllMocks);
|
||||
|
||||
type MockedCreateFetchAggregatedIndicators = jest.MockedFunction<
|
||||
|
@ -51,7 +50,7 @@ describe.skip('useAggregatedIndicators()', () => {
|
|||
it('should create and call the aggregatedIndicatorsQuery correctly', async () => {
|
||||
aggregatedIndicatorsQuery.mockResolvedValue([]);
|
||||
|
||||
const { result, rerender } = renderUseAggregatedIndicators();
|
||||
const { result, rerender, waitFor } = renderUseAggregatedIndicators();
|
||||
|
||||
// indicators service and the query should be called just once
|
||||
expect(
|
||||
|
@ -73,6 +72,7 @@ describe.skip('useAggregatedIndicators()', () => {
|
|||
rerender({
|
||||
filterQuery: { language: 'kuery', query: "threat.indicator.type: 'file'" },
|
||||
filters: [],
|
||||
timeRange: mockTimeRange,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -83,14 +83,17 @@ describe.skip('useAggregatedIndicators()', () => {
|
|||
}),
|
||||
expect.any(AbortSignal)
|
||||
);
|
||||
|
||||
await waitFor(() => !result.current.isLoading);
|
||||
|
||||
expect(result.current).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"dateRange": Object {
|
||||
"max": "2022-01-02T00:00:00.000Z",
|
||||
"min": "2022-01-01T00:00:00.000Z",
|
||||
},
|
||||
"isFetching": true,
|
||||
"isLoading": true,
|
||||
"isFetching": false,
|
||||
"isLoading": false,
|
||||
"onFieldChange": [Function],
|
||||
"selectedField": "threat.feed.name",
|
||||
"series": Array [],
|
||||
|
|
|
@ -12,7 +12,6 @@ import { TimeRangeBounds } from '@kbn/data-plugin/common';
|
|||
import { useInspector } from '../../../hooks/use_inspector';
|
||||
import { RawIndicatorFieldId } from '../../../../common/types/indicator';
|
||||
import { useKibana } from '../../../hooks/use_kibana';
|
||||
import { DEFAULT_TIME_RANGE } from '../../query_bar/hooks/use_filters/utils';
|
||||
import { useSourcererDataView } from '.';
|
||||
import {
|
||||
ChartSeries,
|
||||
|
@ -25,10 +24,7 @@ export interface UseAggregatedIndicatorsParam {
|
|||
* From and To values passed to the {@link useAggregatedIndicators} hook
|
||||
* to query indicators for the Indicators barchart.
|
||||
*/
|
||||
timeRange?: TimeRange;
|
||||
/**
|
||||
* Filters data passed to the {@link useAggregatedIndicators} hook to query indicators.
|
||||
*/
|
||||
timeRange: TimeRange;
|
||||
filters: Filter[];
|
||||
/**
|
||||
* Query data passed to the {@link useAggregatedIndicators} hook to query indicators.
|
||||
|
@ -66,7 +62,7 @@ export interface UseAggregatedIndicatorsValue {
|
|||
const DEFAULT_FIELD = RawIndicatorFieldId.Feed;
|
||||
|
||||
export const useAggregatedIndicators = ({
|
||||
timeRange = DEFAULT_TIME_RANGE,
|
||||
timeRange,
|
||||
filters,
|
||||
filterQuery,
|
||||
}: UseAggregatedIndicatorsParam): UseAggregatedIndicatorsValue => {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { renderHook, act } from '@testing-library/react-hooks';
|
|||
import { useIndicators, UseIndicatorsParams, UseIndicatorsValue } from './use_indicators';
|
||||
import { TestProvidersComponent } from '../../../common/mocks/test_providers';
|
||||
import { createFetchIndicators } from '../services/fetch_indicators';
|
||||
import { mockTimeRange } from '../../../common/mocks/mock_indicators_filters_context';
|
||||
|
||||
jest.mock('../services/fetch_indicators');
|
||||
|
||||
|
@ -16,6 +17,7 @@ const useIndicatorsParams: UseIndicatorsParams = {
|
|||
filters: [],
|
||||
filterQuery: { query: '', language: 'kuery' },
|
||||
sorting: [],
|
||||
timeRange: mockTimeRange,
|
||||
};
|
||||
|
||||
const indicatorsQueryResult = { indicators: [], total: 0 };
|
||||
|
@ -99,13 +101,15 @@ describe('useIndicators()', () => {
|
|||
expect.any(AbortSignal)
|
||||
);
|
||||
|
||||
await hookResult.waitFor(() => !hookResult.result.current.isLoading);
|
||||
|
||||
expect(hookResult.result.current).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"handleRefresh": [Function],
|
||||
"indicatorCount": 0,
|
||||
"indicators": Array [],
|
||||
"isFetching": true,
|
||||
"isLoading": true,
|
||||
"isFetching": false,
|
||||
"isLoading": false,
|
||||
"onChangeItemsPerPage": [Function],
|
||||
"onChangePage": [Function],
|
||||
"pagination": Object {
|
||||
|
|
|
@ -22,7 +22,7 @@ export const DEFAULT_PAGE_SIZE = PAGE_SIZES[1];
|
|||
export interface UseIndicatorsParams {
|
||||
filterQuery: Query;
|
||||
filters: Filter[];
|
||||
timeRange?: TimeRange;
|
||||
timeRange: TimeRange;
|
||||
sorting: EuiDataGridSorting['columns'];
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import { useFilters } from '../query_bar/hooks/use_filters';
|
|||
import moment from 'moment';
|
||||
import { TestProvidersComponent } from '../../common/mocks/test_providers';
|
||||
import { TABLE_TEST_ID } from './components/indicators_table';
|
||||
import { mockTimeRange } from '../../common/mocks/mock_indicators_filters_context';
|
||||
|
||||
jest.mock('../query_bar/hooks/use_filters');
|
||||
jest.mock('./hooks/use_indicators');
|
||||
|
@ -47,9 +48,7 @@ describe('<IndicatorsPage />', () => {
|
|||
filters: [],
|
||||
filterQuery: { language: 'kuery', query: '' },
|
||||
filterManager: {} as any,
|
||||
handleSavedQuery: stub,
|
||||
handleSubmitQuery: stub,
|
||||
handleSubmitTimeRange: stub,
|
||||
timeRange: mockTimeRange,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -59,9 +58,9 @@ describe('<IndicatorsPage />', () => {
|
|||
expect(queryByTestId(TABLE_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the query input', () => {
|
||||
it('should render SIEM Search Bar', () => {
|
||||
const { queryByTestId } = render(<IndicatorsPage />, { wrapper: TestProvidersComponent });
|
||||
expect(queryByTestId('iocListPageQueryInput')).toBeInTheDocument();
|
||||
expect(queryByTestId('SiemSearchBar')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render stack by selector', () => {
|
||||
|
|
|
@ -13,13 +13,13 @@ import { useIndicators } from './hooks/use_indicators';
|
|||
import { DefaultPageLayout } from '../../components/layout';
|
||||
import { useFilters } from '../query_bar/hooks/use_filters';
|
||||
import { FiltersGlobal } from '../../containers/filters_global';
|
||||
import QueryBar from '../query_bar/components/query_bar';
|
||||
import { useSourcererDataView } from './hooks/use_sourcerer_data_view';
|
||||
import { FieldTypesProvider } from '../../containers/field_types_provider';
|
||||
import { InspectorProvider } from '../../containers/inspector';
|
||||
import { useColumnSettings } from './components/indicators_table/hooks/use_column_settings';
|
||||
import { useAggregatedIndicators } from './hooks/use_aggregated_indicators';
|
||||
import { IndicatorsFilters } from './containers/indicators_filters';
|
||||
import { useSecurityContext } from '../../hooks/use_security_context';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
|
@ -38,19 +38,9 @@ const IndicatorsPageContent: VFC = () => {
|
|||
|
||||
const columnSettings = useColumnSettings();
|
||||
|
||||
const {
|
||||
timeRange,
|
||||
filters,
|
||||
filterManager,
|
||||
filterQuery,
|
||||
handleSubmitQuery,
|
||||
handleSubmitTimeRange,
|
||||
handleSavedQuery,
|
||||
savedQuery,
|
||||
} = useFilters();
|
||||
const { timeRange, filters, filterQuery } = useFilters();
|
||||
|
||||
const {
|
||||
handleRefresh,
|
||||
indicatorCount,
|
||||
indicators,
|
||||
onChangeItemsPerPage,
|
||||
|
@ -78,25 +68,13 @@ const IndicatorsPageContent: VFC = () => {
|
|||
filterQuery,
|
||||
});
|
||||
|
||||
const { SiemSearchBar } = useSecurityContext();
|
||||
|
||||
return (
|
||||
<FieldTypesProvider>
|
||||
<DefaultPageLayout pageTitle="Indicators">
|
||||
<FiltersGlobal>
|
||||
<QueryBar
|
||||
dateRangeFrom={timeRange?.from}
|
||||
dateRangeTo={timeRange?.to}
|
||||
indexPattern={indexPattern}
|
||||
filterQuery={filterQuery}
|
||||
filterManager={filterManager}
|
||||
filters={filters}
|
||||
dataTestSubj="iocListPageQueryInput"
|
||||
displayStyle="detached"
|
||||
savedQuery={savedQuery}
|
||||
onRefresh={handleRefresh}
|
||||
onSubmitQuery={handleSubmitQuery}
|
||||
onSavedQuery={handleSavedQuery}
|
||||
onSubmitDateRange={handleSubmitTimeRange}
|
||||
/>
|
||||
<SiemSearchBar indexPattern={indexPattern} id="global" />
|
||||
</FiltersGlobal>
|
||||
<IndicatorsBarChartWrapper
|
||||
dateRange={dateRange}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './query_bar';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { QueryBar as default } from './query_bar';
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { ComponentStory } from '@storybook/react';
|
||||
import { KibanaContextProvider } from '../../../../hooks/use_kibana';
|
||||
import { QueryBar } from './query_bar';
|
||||
|
||||
export default {
|
||||
component: QueryBar,
|
||||
title: 'QueryBar',
|
||||
argTypes: {
|
||||
onSubmitQuery: { action: 'onSubmitQuery' },
|
||||
onChangedQuery: { action: 'onChangedQuery' },
|
||||
onChangedDateRange: { action: 'onChangedDateRange' },
|
||||
onSubmitDateRange: { action: 'onSubmitDateRange' },
|
||||
onSavedQuery: { action: 'onSavedQuery' },
|
||||
onRefresh: { action: 'onRefresh' },
|
||||
},
|
||||
};
|
||||
|
||||
const services = {
|
||||
data: { query: {} },
|
||||
storage: new Storage(localStorage),
|
||||
uiSettings: { get: () => {} },
|
||||
};
|
||||
|
||||
const Template: ComponentStory<typeof QueryBar> = (args) => (
|
||||
<IntlProvider>
|
||||
<KibanaContextProvider services={services}>
|
||||
<QueryBar {...args} />{' '}
|
||||
</KibanaContextProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
export const Basic = Template.bind({});
|
||||
|
||||
Basic.args = {
|
||||
indexPattern: {} as any,
|
||||
filterManager: {} as any,
|
||||
filters: [],
|
||||
filterQuery: { language: 'kuery', query: '' },
|
||||
};
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render, screen, act, waitFor } from '@testing-library/react';
|
||||
import { QueryBar } from './query_bar';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { FilterManager } from '@kbn/data-plugin/public';
|
||||
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { TestProvidersComponent } from '../../../../common/mocks/test_providers';
|
||||
import { getByTestSubj } from '../../../../../common/test/utils';
|
||||
|
||||
const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings;
|
||||
|
||||
const filterManager = new FilterManager(mockUiSettingsForFilterManager);
|
||||
|
||||
describe('QueryBar ', () => {
|
||||
const onSubmitQuery = jest.fn();
|
||||
const onSubmitDateRange = jest.fn();
|
||||
const onSavedQuery = jest.fn();
|
||||
const onChangedQuery = jest.fn();
|
||||
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<TestProvidersComponent>
|
||||
<QueryBar
|
||||
filterQuery={{ query: '', language: 'kuery' }}
|
||||
indexPattern={{ fields: [] } as any}
|
||||
filterManager={filterManager}
|
||||
filters={[]}
|
||||
onRefresh={jest.fn()}
|
||||
onSubmitQuery={onSubmitQuery}
|
||||
onChangedQuery={onChangedQuery}
|
||||
onSubmitDateRange={onSubmitDateRange}
|
||||
onSavedQuery={onSavedQuery}
|
||||
/>
|
||||
</TestProvidersComponent>
|
||||
);
|
||||
});
|
||||
|
||||
// Some parts of this are lazy loaded, we need to wait for quert input to appear before tests can be done
|
||||
await waitFor(() => screen.queryByRole('input'));
|
||||
});
|
||||
|
||||
it('should call onSubmitDateRange when date range is changed', async () => {
|
||||
expect(getByTestSubj('superDatePickerToggleQuickMenuButton')).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestSubj('superDatePickerToggleQuickMenuButton'));
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
screen.getByText('Apply').click();
|
||||
});
|
||||
|
||||
expect(onSubmitDateRange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onSubmitQuery when query is changed', async () => {
|
||||
const queryInput = getByTestSubj('queryInput');
|
||||
|
||||
await act(async () => {
|
||||
userEvent.type(queryInput, 'one_serious_query');
|
||||
});
|
||||
|
||||
expect(onChangedQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ language: 'kuery', query: expect.any(String) })
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
screen.getByText('Refresh').click();
|
||||
});
|
||||
|
||||
expect(onSubmitQuery).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -1,179 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useMemo, useCallback } from 'react';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
import { DataView } from '@kbn/data-views-plugin/public';
|
||||
import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import {
|
||||
FilterManager,
|
||||
TimeHistory,
|
||||
SavedQuery,
|
||||
SavedQueryTimeFilter,
|
||||
} from '@kbn/data-plugin/public';
|
||||
import { SearchBar, SearchBarProps } from '@kbn/unified-search-plugin/public';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { SecuritySolutionDataViewBase } from '../../../../types';
|
||||
|
||||
interface QueryPayload {
|
||||
dateRange: TimeRange;
|
||||
query?: Query | AggregateQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* User defined type guard to verify if we are dealing with Query param
|
||||
* @param query query param to test
|
||||
* @returns
|
||||
*/
|
||||
const isQuery = (query?: Query | AggregateQuery | null): query is Query => {
|
||||
return !!query && Object.prototype.hasOwnProperty.call(query, 'query');
|
||||
};
|
||||
|
||||
export interface QueryBarComponentProps {
|
||||
dataTestSubj?: string;
|
||||
dateRangeFrom?: string;
|
||||
dateRangeTo?: string;
|
||||
hideSavedQuery?: boolean;
|
||||
indexPattern: SecuritySolutionDataViewBase;
|
||||
isLoading?: boolean;
|
||||
isRefreshPaused?: boolean;
|
||||
filterQuery: Query;
|
||||
filterManager: FilterManager;
|
||||
filters: Filter[];
|
||||
onRefresh: VoidFunction;
|
||||
onChangedQuery?: (query: Query) => void;
|
||||
onChangedDateRange?: (dateRange?: TimeRange) => void;
|
||||
onSubmitQuery: (query: Query, timefilter?: SavedQueryTimeFilter) => void;
|
||||
onSubmitDateRange: (dateRange?: TimeRange) => void;
|
||||
refreshInterval?: number;
|
||||
savedQuery?: SavedQuery;
|
||||
onSavedQuery: (savedQuery: SavedQuery | undefined) => void;
|
||||
displayStyle?: SearchBarProps['displayStyle'];
|
||||
}
|
||||
|
||||
export const INDICATOR_FILTER_DROP_AREA = 'indicator-filter-drop-area';
|
||||
|
||||
export const QueryBar = memo<QueryBarComponentProps>(
|
||||
({
|
||||
dateRangeFrom,
|
||||
dateRangeTo,
|
||||
hideSavedQuery = false,
|
||||
indexPattern,
|
||||
isLoading = false,
|
||||
isRefreshPaused,
|
||||
filterQuery,
|
||||
filterManager,
|
||||
filters,
|
||||
refreshInterval,
|
||||
savedQuery,
|
||||
dataTestSubj,
|
||||
displayStyle,
|
||||
onChangedQuery,
|
||||
onSubmitQuery,
|
||||
onChangedDateRange,
|
||||
onSubmitDateRange,
|
||||
onSavedQuery,
|
||||
onRefresh,
|
||||
}) => {
|
||||
const onQuerySubmit = useCallback(
|
||||
({ query, dateRange }: QueryPayload) => {
|
||||
if (dateRange != null) {
|
||||
onSubmitDateRange(dateRange);
|
||||
}
|
||||
|
||||
if (isQuery(query) && !deepEqual(query, filterQuery)) {
|
||||
onSubmitQuery(query);
|
||||
} else {
|
||||
onRefresh();
|
||||
}
|
||||
},
|
||||
[filterQuery, onRefresh, onSubmitDateRange, onSubmitQuery]
|
||||
);
|
||||
|
||||
const onQueryChange = useCallback(
|
||||
({ query, dateRange }: QueryPayload) => {
|
||||
if (!onChangedQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isQuery(query) && !deepEqual(query, filterQuery)) {
|
||||
onChangedQuery(query);
|
||||
}
|
||||
|
||||
if (onChangedDateRange && dateRange != null) {
|
||||
onChangedDateRange(dateRange);
|
||||
}
|
||||
},
|
||||
[filterQuery, onChangedDateRange, onChangedQuery]
|
||||
);
|
||||
|
||||
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(undefined);
|
||||
}
|
||||
}, [filterManager, onSubmitQuery, onSavedQuery, savedQuery]);
|
||||
|
||||
const onFiltersUpdated = useCallback(
|
||||
(newFilters: Filter[]) => {
|
||||
return filterManager.setFilters(newFilters);
|
||||
},
|
||||
[filterManager]
|
||||
);
|
||||
|
||||
const timeHistory = useMemo(() => new TimeHistory(new Storage(localStorage)), []);
|
||||
|
||||
const indexPatterns = useMemo(() => [indexPattern], [indexPattern]);
|
||||
|
||||
return (
|
||||
<SearchBar
|
||||
showSubmitButton={true}
|
||||
dateRangeFrom={dateRangeFrom}
|
||||
dateRangeTo={dateRangeTo}
|
||||
filters={filters}
|
||||
indexPatterns={indexPatterns as DataView[]}
|
||||
isLoading={isLoading}
|
||||
isRefreshPaused={isRefreshPaused}
|
||||
query={filterQuery}
|
||||
onClearSavedQuery={onClearSavedQuery}
|
||||
onFiltersUpdated={onFiltersUpdated}
|
||||
onQueryChange={onQueryChange}
|
||||
onQuerySubmit={onQuerySubmit}
|
||||
onSaved={onSavedQuery}
|
||||
onSavedQueryUpdated={onSavedQueryUpdated}
|
||||
refreshInterval={refreshInterval}
|
||||
showAutoRefreshOnly={false}
|
||||
showFilterBar={!hideSavedQuery}
|
||||
showDatePicker={true}
|
||||
showQueryBar={true}
|
||||
showQueryInput={true}
|
||||
showSaveQuery={true}
|
||||
timeHistory={timeHistory}
|
||||
dataTestSubj={dataTestSubj}
|
||||
savedQuery={savedQuery}
|
||||
displayStyle={displayStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QueryBar.displayName = 'QueryBar';
|
|
@ -1,165 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mockUseKibanaForFilters } from '../../../../common/mocks/mock_use_kibana_for_filters';
|
||||
import { renderHook, act, RenderHookResult, Renderer } from '@testing-library/react-hooks';
|
||||
import { useFilters, UseFiltersValue } from './use_filters';
|
||||
|
||||
import { useLocation, useHistory } from 'react-router-dom';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { TestProvidersComponent } from '../../../../common/mocks/test_providers';
|
||||
import React, { FC } from 'react';
|
||||
import { IndicatorsFilters } from '../../../indicators/containers/indicators_filters';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
MemoryRouter: ({ children }: any) => <>{children}</>,
|
||||
useLocation: jest.fn().mockReturnValue({ pathname: '', search: '' }),
|
||||
useHistory: jest.fn().mockReturnValue({ replace: jest.fn() }),
|
||||
}));
|
||||
|
||||
const FiltersWrapper: FC = ({ children }) => (
|
||||
<TestProvidersComponent>
|
||||
<IndicatorsFilters>{children}</IndicatorsFilters>{' '}
|
||||
</TestProvidersComponent>
|
||||
);
|
||||
|
||||
const renderUseFilters = () => renderHook(() => useFilters(), { wrapper: FiltersWrapper });
|
||||
|
||||
describe('useFilters()', () => {
|
||||
let hookResult: RenderHookResult<{}, UseFiltersValue, Renderer<unknown>>;
|
||||
let mockRef: ReturnType<typeof mockUseKibanaForFilters>;
|
||||
|
||||
afterAll(() => jest.unmock('react-router-dom'));
|
||||
|
||||
describe('when mounted', () => {
|
||||
beforeEach(async () => {
|
||||
mockRef = mockUseKibanaForFilters();
|
||||
|
||||
hookResult = renderUseFilters();
|
||||
});
|
||||
|
||||
it('should have valid initial filterQuery value', () => {
|
||||
expect(hookResult.result.current.filterQuery).toMatchObject({ language: 'kuery', query: '' });
|
||||
});
|
||||
|
||||
describe('when query string is populated', () => {
|
||||
it('should try to compute the initial state based on query string', async () => {
|
||||
(useLocation as jest.Mock).mockReturnValue({
|
||||
search:
|
||||
'?indicators=(filterQuery:(language:kuery,query:%27threat.indicator.type%20:%20"file"%20%27),filters:!(),timeRange:(from:now/d,to:now/d))',
|
||||
});
|
||||
|
||||
hookResult = renderUseFilters();
|
||||
|
||||
expect(hookResult.result.current.filterQuery).toMatchObject({
|
||||
language: 'kuery',
|
||||
query: 'threat.indicator.type : "file" ',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when filter values change', () => {
|
||||
const historyReplace = jest.fn();
|
||||
beforeEach(async () => {
|
||||
mockRef = mockUseKibanaForFilters();
|
||||
|
||||
hookResult = renderUseFilters();
|
||||
|
||||
(useHistory as jest.Mock).mockReturnValue({ replace: historyReplace });
|
||||
|
||||
historyReplace.mockClear();
|
||||
});
|
||||
|
||||
describe('when filters change', () => {
|
||||
it('should update history entry', async () => {
|
||||
const newFilterEntry = { query: { filter: 'new_filter' }, meta: {} };
|
||||
|
||||
// Make sure new filter value is returned from filter manager before signalling an update
|
||||
// to subscribers
|
||||
mockRef.getFilters.mockReturnValue([newFilterEntry] as Filter[]);
|
||||
|
||||
// Emit the filterManager update to see how it propagates to local component state
|
||||
await act(async () => {
|
||||
mockRef.$filterUpdates.next(void 0);
|
||||
});
|
||||
|
||||
// Internally, filters should be loaded from filterManager
|
||||
expect(mockRef.getFilters).toHaveBeenCalled();
|
||||
|
||||
// Serialized into browser query string
|
||||
expect(historyReplace).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ search: expect.stringMatching(/new_filter/) })
|
||||
);
|
||||
|
||||
// And updated in local hook state
|
||||
expect(hookResult.result.current.filters).toContain(newFilterEntry);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when time range changes', () => {
|
||||
const newTimeRange = { from: 'dawnOfTime', to: 'endOfTime' };
|
||||
|
||||
const updateTime = async () => {
|
||||
// After new time range is selected
|
||||
await act(async () => {
|
||||
hookResult.result.current.handleSubmitTimeRange(newTimeRange);
|
||||
});
|
||||
};
|
||||
|
||||
it('should update its local state', async () => {
|
||||
expect(hookResult.result.current.timeRange).toBeDefined();
|
||||
expect(hookResult.result.current.timeRange).not.toEqual(newTimeRange);
|
||||
|
||||
// After new time range is selected
|
||||
await updateTime();
|
||||
|
||||
// Local filter state should be updated
|
||||
expect(hookResult.result.current.timeRange).toEqual(newTimeRange);
|
||||
});
|
||||
|
||||
it('should update history entry', async () => {
|
||||
expect(historyReplace).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ search: expect.stringMatching(/dawnOfTime/) })
|
||||
);
|
||||
|
||||
// After new time range is selected
|
||||
await updateTime();
|
||||
|
||||
// Query string should be updated
|
||||
expect(historyReplace).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ search: expect.stringMatching(/dawnOfTime/) })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when filterQuery changes', () => {
|
||||
beforeEach(async () => {
|
||||
// After new time range is selected
|
||||
await act(async () => {
|
||||
hookResult.result.current.handleSubmitQuery({
|
||||
query: 'threat.indicator.type : *',
|
||||
language: 'kuery',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should update history entry', async () => {
|
||||
expect(historyReplace).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ search: expect.stringMatching(/threat\.indicator\.type/) })
|
||||
);
|
||||
});
|
||||
|
||||
it('should update local state', () => {
|
||||
expect(hookResult.result.current.filterQuery).toMatchObject({
|
||||
language: 'kuery',
|
||||
query: 'threat.indicator.type : *',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { stateFromQueryParams } from './utils';
|
||||
|
||||
describe('encodeState()', () => {});
|
||||
|
||||
describe('stateFromQueryParams()', () => {
|
||||
it('should return valid state object from invalid query', () => {
|
||||
expect(stateFromQueryParams('')).toMatchObject({
|
||||
filterQuery: expect.any(Object),
|
||||
timeRange: expect.any(Object),
|
||||
filters: expect.any(Array),
|
||||
});
|
||||
});
|
||||
|
||||
it('should return valid state when indicators fields is invalid', () => {
|
||||
expect(stateFromQueryParams('?indicators=')).toMatchObject({
|
||||
filterQuery: expect.any(Object),
|
||||
timeRange: expect.any(Object),
|
||||
filters: expect.any(Array),
|
||||
});
|
||||
});
|
||||
|
||||
it('should deserialize valid query state', () => {
|
||||
expect(
|
||||
stateFromQueryParams(
|
||||
'?indicators=(filterQuery:(language:kuery,query:%27threat.indicator.type%20:%20"file"%20or%20threat.indicator.type%20:%20"url"%20%27),filters:!((%27$state%27:(store:appState),meta:(alias:!n,disabled:!f,index:%27%27,key:_id,negate:!f,type:exists,value:exists),query:(exists:(field:_id)))),timeRange:(from:now-1y/d,to:now))'
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"filterQuery": Object {
|
||||
"language": "kuery",
|
||||
"query": "threat.indicator.type : \\"file\\" or threat.indicator.type : \\"url\\" ",
|
||||
},
|
||||
"filters": Array [
|
||||
Object {
|
||||
"$state": Object {
|
||||
"store": "appState",
|
||||
},
|
||||
"meta": Object {
|
||||
"alias": null,
|
||||
"disabled": false,
|
||||
"index": "",
|
||||
"key": "_id",
|
||||
"negate": false,
|
||||
"type": "exists",
|
||||
"value": "exists",
|
||||
},
|
||||
"query": Object {
|
||||
"exists": Object {
|
||||
"field": "_id",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"timeRange": Object {
|
||||
"from": "now-1y/d",
|
||||
"to": "now",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import { parse } from 'query-string';
|
||||
import { decode, encode } from 'rison-node';
|
||||
|
||||
export const FILTERS_QUERYSTRING_NAMESPACE = 'indicators';
|
||||
|
||||
export const DEFAULT_TIME_RANGE = { from: 'now/d', to: 'now/d' };
|
||||
|
||||
export const DEFAULT_QUERY: Readonly<Query> = { query: '', language: 'kuery' };
|
||||
|
||||
const INITIAL_FILTERS_STATE: Readonly<SerializableFilterState> = {
|
||||
filters: [],
|
||||
timeRange: DEFAULT_TIME_RANGE,
|
||||
filterQuery: DEFAULT_QUERY,
|
||||
};
|
||||
|
||||
interface SerializableFilterState {
|
||||
timeRange?: TimeRange;
|
||||
filterQuery: Query;
|
||||
filters: Filter[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts filter state to query string
|
||||
* @param filterState Serializable filter state to convert into query string
|
||||
* @returns
|
||||
*/
|
||||
export const encodeState = (filterState: SerializableFilterState): string =>
|
||||
encode(filterState as any);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param encodedFilterState Serialized filter state to decode
|
||||
* @returns
|
||||
*/
|
||||
const decodeState = (encodedFilterState: string): SerializableFilterState | null =>
|
||||
decode(encodedFilterState) as unknown as SerializableFilterState;
|
||||
|
||||
/**
|
||||
* Find and convert filter state stored within query string into object literal
|
||||
* @param searchString Brower query string containing encoded filter information, within single query field
|
||||
* @returns SerializableFilterState with all the relevant fields ready to use
|
||||
*/
|
||||
export const stateFromQueryParams = (searchString: string): SerializableFilterState => {
|
||||
const { [FILTERS_QUERYSTRING_NAMESPACE]: filtersSerialized } = parse(searchString);
|
||||
|
||||
if (!filtersSerialized) {
|
||||
return INITIAL_FILTERS_STATE;
|
||||
}
|
||||
|
||||
if (Array.isArray(filtersSerialized)) {
|
||||
throw new Error('serialized filters should not be an array');
|
||||
}
|
||||
|
||||
const deserializedFilters = decodeState(filtersSerialized);
|
||||
|
||||
if (!deserializedFilters) {
|
||||
return INITIAL_FILTERS_STATE;
|
||||
}
|
||||
|
||||
return {
|
||||
...INITIAL_FILTERS_STATE,
|
||||
...deserializedFilters,
|
||||
timeRange: deserializedFilters.timeRange || DEFAULT_TIME_RANGE,
|
||||
};
|
||||
};
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ComponentType, ReactElement, ReactNode } from 'react';
|
||||
import { ComponentType, ReactElement, ReactNode, VFC } from 'react';
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import {
|
||||
|
@ -16,7 +16,7 @@ import {
|
|||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { TimelinesUIStart } from '@kbn/timelines-plugin/public';
|
||||
import type { TriggersAndActionsUIPublicPluginStart as TriggersActionsStart } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { DataViewBase } from '@kbn/es-query';
|
||||
import { DataViewBase, Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import { BrowserField } from '@kbn/rule-registry-plugin/common';
|
||||
import { Store } from 'redux';
|
||||
import { DataProvider } from '@kbn/timelines-plugin/common';
|
||||
|
@ -102,4 +102,10 @@ export interface SecuritySolutionPluginContext {
|
|||
from,
|
||||
to,
|
||||
}: UseInvestigateInTimelineProps) => () => Promise<void>;
|
||||
|
||||
useQuery: () => Query;
|
||||
useFilters: () => Filter[];
|
||||
useGlobalTime: () => TimeRange;
|
||||
|
||||
SiemSearchBar: VFC<any>;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue