[TIP] Add inspect capability for barchart and grid requests (#140810)

This commit is contained in:
Luke Gmys 2022-09-21 13:40:58 +02:00 committed by GitHub
parent 5c11b65bb6
commit 21d01719eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 403 additions and 184 deletions

View file

@ -28,6 +28,8 @@ import {
FIELD_SELECTOR_TOGGLE_BUTTON,
FIELD_SELECTOR_INPUT,
FIELD_SELECTOR_LIST,
INSPECTOR_BUTTON,
INSPECTOR_PANEL,
} from '../screens/indicators';
import { login } from '../tasks/login';
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
@ -148,11 +150,11 @@ describe('Indicators', () => {
const threatFeedName = 'threat.feed.name';
cy.get(`${FIELD_SELECTOR_INPUT}`).eq(0).should('have.text', threatFeedName);
const threatIndicatorIp: string = 'threat.indicator.ip';
const timestamp: string = '@timestamp';
cy.get(`${FIELD_SELECTOR_TOGGLE_BUTTON}`).should('exist').click();
cy.get(`${FIELD_SELECTOR_LIST}`).should('exist').contains(threatIndicatorIp);
cy.get(`${FIELD_SELECTOR_LIST}`).should('exist').contains(timestamp);
});
});
@ -171,4 +173,22 @@ describe('Indicators', () => {
});
});
});
describe('Request inspector', () => {
before(() => {
cy.visit(THREAT_INTELLIGENCE);
selectRange();
});
describe('when inspector button is clicked', () => {
it('should render the inspector flyout', () => {
cy.get(INSPECTOR_BUTTON).last().click({ force: true });
cy.get(INSPECTOR_PANEL).should('be.visible');
cy.get(INSPECTOR_PANEL).contains('Index patterns');
});
});
});
});

View file

@ -23,7 +23,7 @@ export const FLYOUT_TITLE = `[data-test-subj="tiIndicatorFlyoutTitle"]`;
export const FLYOUT_TABS = `[data-test-subj="tiIndicatorFlyoutTabs"]`;
export const FLYOUT_TABLE = `[data-test-subj="tiFlyoutTableMemoryTable"]`;
export const FLYOUT_TABLE = `[data-test-subj="tiFlyoutTableTabRow"]`;
export const FLYOUT_JSON = `[data-test-subj="tiFlyoutJsonCodeBlock"]`;
@ -45,7 +45,8 @@ export const FIELD_SELECTOR_INPUT = '[data-test-subj="comboBoxInput"]';
export const FIELD_SELECTOR_TOGGLE_BUTTON = '[data-test-subj="comboBoxToggleListButton"]';
export const FIELD_SELECTOR_LIST = '[data-test-subj="comboBoxOptionsList"]';
export const FIELD_SELECTOR_LIST =
'[data-test-subj="comboBoxOptionsList tiIndicatorFieldSelectorDropdown-optionsList"]';
export const FIELD_BROWSER = `[data-test-subj="show-field-browser"]`;
@ -113,3 +114,6 @@ export const INDICATORS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_ICON =
export const INDICATOR_FLYOUT_INVESTIGATE_IN_TIMELINE_BUTTON =
'[data-test-subj="tiIndicatorFlyoutInvestigateInTimelineButton"]';
export const INSPECTOR_BUTTON = '[data-test-subj="tiIndicatorsGridInspect"]';
export const INSPECTOR_PANEL = '[data-test-subj="inspectorPanel"]';

View file

@ -16,7 +16,8 @@
"kibanaUtils",
"navigation",
"kibanaReact",
"triggersActionsUi"
"triggersActionsUi",
"inspector"
],
"requiredBundles": [
"data",

View file

@ -16,6 +16,7 @@ import { Storage } from '@kbn/kibana-utils-plugin/public';
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
import { createTGridMocks } from '@kbn/timelines-plugin/public/mock';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { RequestAdapter } from '@kbn/inspector-plugin/common';
import { KibanaContext } from '../../hooks/use_kibana';
import { SecuritySolutionPluginContext } from '../../types';
import { getSecuritySolutionContextMock } from './mock_security_context';
@ -25,6 +26,7 @@ import { IndicatorsFiltersContext } from '../../modules/indicators/context';
import { mockIndicatorsFiltersContext } from './mock_indicators_filters_context';
import { FieldTypesContext } from '../../containers/field_types_provider';
import { generateFieldTypeMap } from './mock_field_type_map';
import { InspectorContext } from '../../containers/inspector';
export const localStorageMock = (): IStorage => {
let store: Record<string, unknown> = {};
@ -125,19 +127,21 @@ export const mockedServices = {
};
export const TestProvidersComponent: FC = ({ children }) => (
<FieldTypesContext.Provider value={generateFieldTypeMap()}>
<EuiThemeProvider>
<SecuritySolutionContext.Provider value={mockSecurityContext}>
<KibanaContext.Provider value={{ services: mockedServices } as any}>
<I18nProvider>
<IndicatorsFiltersContext.Provider value={mockIndicatorsFiltersContext}>
{children}
</IndicatorsFiltersContext.Provider>
</I18nProvider>
</KibanaContext.Provider>
</SecuritySolutionContext.Provider>
</EuiThemeProvider>
</FieldTypesContext.Provider>
<InspectorContext.Provider value={{ requests: new RequestAdapter() }}>
<FieldTypesContext.Provider value={generateFieldTypeMap()}>
<EuiThemeProvider>
<SecuritySolutionContext.Provider value={mockSecurityContext}>
<KibanaContext.Provider value={{ services: mockedServices } as any}>
<I18nProvider>
<IndicatorsFiltersContext.Provider value={mockIndicatorsFiltersContext}>
{children}
</IndicatorsFiltersContext.Provider>
</I18nProvider>
</KibanaContext.Provider>
</SecuritySolutionContext.Provider>
</EuiThemeProvider>
</FieldTypesContext.Provider>
</InspectorContext.Provider>
);
export type MockedSearch = jest.Mocked<typeof mockedServices.data.search>;

View file

@ -0,0 +1,8 @@
/*
* 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 './inspector';

View file

@ -0,0 +1,23 @@
/*
* 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 { RequestAdapter } from '@kbn/inspector-plugin/common';
import React, { createContext, FC, useMemo } from 'react';
export interface InspectorContextValue {
requests: RequestAdapter;
}
export const InspectorContext = createContext<InspectorContextValue | undefined>(undefined);
export const InspectorProvider: FC = ({ children }) => {
const inspectorAdapters = useMemo(() => ({ requests: new RequestAdapter() }), []);
return (
<InspectorContext.Provider value={inspectorAdapters}>{children}</InspectorContext.Provider>
);
};

View file

@ -0,0 +1,51 @@
/*
* 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 { useCallback, useContext, useEffect, useState } from 'react';
import { InspectorSession } from '@kbn/inspector-plugin/public';
import { i18n } from '@kbn/i18n';
import { useKibana } from './use_kibana';
import { InspectorContext } from '../containers/inspector';
const INSPECTOR_FLYOUT_TITLE = i18n.translate('xpack.threatIntelligence.inspectorFlyoutTitle', {
defaultMessage: 'Indicators search requests',
});
/**
*
* @returns Exposes the adapters used to analyze requests and a method to open the inspector
*/
export const useInspector = () => {
const {
services: { inspector },
} = useKibana();
const inspectorAdapters = useContext(InspectorContext);
if (!inspectorAdapters) {
throw new Error('Inspector Context is not available');
}
const [inspectorSession, setInspectorSession] = useState<InspectorSession | undefined>(undefined);
const onOpenInspector = useCallback(() => {
const session = inspector.open(inspectorAdapters, {
title: INSPECTOR_FLYOUT_TITLE,
});
setInspectorSession(session);
}, [inspectorAdapters, inspector]);
useEffect(() => {
return () => {
if (inspectorSession) {
inspectorSession.close();
}
};
}, [inspectorSession]);
return { onOpenInspector, inspectorAdapters };
};

View file

@ -5,21 +5,24 @@
* 2.0.
*/
import { TestProvidersComponent } from '../../../../../common/mocks/test_providers';
import { renderHook } from '@testing-library/react-hooks';
import { useToolbarOptions } from './use_toolbar_options';
describe('useToolbarOptions()', () => {
it('should return correct value for 0 indicators total', () => {
const result = renderHook(() =>
useToolbarOptions({
browserFields: {},
columns: [],
end: 0,
start: 0,
indicatorCount: 0,
onResetColumns: () => {},
onToggleColumn: () => {},
})
const result = renderHook(
() =>
useToolbarOptions({
browserFields: {},
columns: [],
end: 0,
start: 0,
indicatorCount: 0,
onResetColumns: () => {},
onToggleColumn: () => {},
}),
{ wrapper: TestProvidersComponent }
);
expect(result.result.current).toMatchInlineSnapshot(`
@ -45,6 +48,12 @@ describe('useToolbarOptions()', () => {
</React.Fragment>
</EuiText>,
},
"right": <EuiButtonIcon
data-test-subj="tiIndicatorsGridInspect"
iconType="inspect"
onClick={[Function]}
title="Inspect"
/>,
},
"showDisplaySelector": false,
"showFullScreenSelector": false,
@ -53,16 +62,18 @@ describe('useToolbarOptions()', () => {
});
it('should return correct value for 25 indicators total', () => {
const result = renderHook(() =>
useToolbarOptions({
browserFields: {},
columns: [],
end: 25,
start: 0,
indicatorCount: 25,
onResetColumns: () => {},
onToggleColumn: () => {},
})
const result = renderHook(
() =>
useToolbarOptions({
browserFields: {},
columns: [],
end: 25,
start: 0,
indicatorCount: 25,
onResetColumns: () => {},
onToggleColumn: () => {},
}),
{ wrapper: TestProvidersComponent }
);
expect(result.result.current).toMatchInlineSnapshot(`
@ -95,6 +106,12 @@ describe('useToolbarOptions()', () => {
</React.Fragment>
</EuiText>,
},
"right": <EuiButtonIcon
data-test-subj="tiIndicatorsGridInspect"
iconType="inspect"
onClick={[Function]}
title="Inspect"
/>,
},
"showDisplaySelector": false,
"showFullScreenSelector": false,
@ -103,16 +120,18 @@ describe('useToolbarOptions()', () => {
});
it('should return correct value for 50 indicators total', () => {
const result = renderHook(() =>
useToolbarOptions({
browserFields: {},
columns: [],
end: 50,
start: 25,
indicatorCount: 50,
onResetColumns: () => {},
onToggleColumn: () => {},
})
const result = renderHook(
() =>
useToolbarOptions({
browserFields: {},
columns: [],
end: 50,
start: 25,
indicatorCount: 50,
onResetColumns: () => {},
onToggleColumn: () => {},
}),
{ wrapper: TestProvidersComponent }
);
expect(result.result.current).toMatchInlineSnapshot(`
@ -145,6 +164,12 @@ describe('useToolbarOptions()', () => {
</React.Fragment>
</EuiText>,
},
"right": <EuiButtonIcon
data-test-subj="tiIndicatorsGridInspect"
iconType="inspect"
onClick={[Function]}
title="Inspect"
/>,
},
"showDisplaySelector": false,
"showFullScreenSelector": false,

View file

@ -7,10 +7,18 @@
import React from 'react';
import { useMemo } from 'react';
import { EuiDataGridColumn, EuiText } from '@elastic/eui';
import { EuiButtonIcon, EuiDataGridColumn, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { BrowserField } from '@kbn/rule-registry-plugin/common';
import { useInspector } from '../../../../../hooks/use_inspector';
import { IndicatorsFieldBrowser } from '../../indicators_field_browser';
const INSPECT_BUTTON_TEST_ID = 'tiIndicatorsGridInspect';
const INSPECT_BUTTON_TITLE = i18n.translate('xpack.threatIntelligence.inspectTitle', {
defaultMessage: 'Inspect',
});
export const useToolbarOptions = ({
browserFields,
start,
@ -27,8 +35,10 @@ export const useToolbarOptions = ({
columns: EuiDataGridColumn[];
onResetColumns: () => void;
onToggleColumn: (columnId: string) => void;
}) =>
useMemo(
}) => {
const { onOpenInspector: handleOpenInspector } = useInspector();
return useMemo(
() => ({
showDisplaySelector: false,
showFullScreenSelector: false,
@ -55,7 +65,25 @@ export const useToolbarOptions = ({
/>
),
},
right: (
<EuiButtonIcon
iconType="inspect"
title={INSPECT_BUTTON_TITLE}
data-test-subj={INSPECT_BUTTON_TEST_ID}
onClick={handleOpenInspector}
/>
),
},
}),
[start, end, indicatorCount, browserFields, columns, onResetColumns, onToggleColumn]
[
indicatorCount,
end,
start,
browserFields,
columns,
onResetColumns,
onToggleColumn,
handleOpenInspector,
]
);
};

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { buildEsQuery, TimeRange } from '@kbn/es-query';
import { TimeRange } from '@kbn/es-query';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Subscription } from 'rxjs';
import {
@ -15,15 +15,16 @@ import {
isErrorResponse,
TimeRangeBounds,
} from '@kbn/data-plugin/common';
import { useInspector } from '../../../hooks/use_inspector';
import { useFilters } from '../../query_bar/hooks/use_filters';
import { convertAggregationToChartSeries } from '../../../common/utils/barchart';
import { RawIndicatorFieldId } from '../../../../common/types/indicator';
import { THREAT_QUERY_BASE } from '../../../../common/constants';
import { calculateBarchartColumnTimeInterval } from '../../../common/utils/dates';
import { useKibana } from '../../../hooks/use_kibana';
import { DEFAULT_TIME_RANGE } from '../../query_bar/hooks/use_filters/utils';
import { useSourcererDataView } from './use_sourcerer_data_view';
import { threatIndicatorNamesOriginScript, threatIndicatorNamesScript } from '../lib/display_name';
import { getRuntimeMappings } from '../lib/get_runtime_mappings';
import { getIndicatorsQuery } from '../lib/get_indicators_query';
export interface UseAggregatedIndicatorsParam {
/**
@ -97,98 +98,73 @@ export const useAggregatedIndicators = ({
const { selectedPatterns } = useSourcererDataView();
const { inspectorAdapters } = useInspector();
const searchSubscription$ = useRef(new Subscription());
const abortController = useRef(new AbortController());
const [indicators, setIndicators] = useState<ChartSeries[]>([]);
const [field, setField] = useState<string>(DEFAULT_FIELD);
const { filters, filterQuery } = useFilters();
const dateRange: TimeRangeBounds = useMemo(
() => queryService.timefilter.timefilter.calculateBounds(timeRange),
[queryService, timeRange]
);
const { filters, filterQuery } = useFilters();
const queryToExecute = useMemo(() => {
return getIndicatorsQuery({ timeRange, filters, filterQuery });
}, [filterQuery, filters, timeRange]);
const loadData = useCallback(async () => {
const dateFrom: number = (dateRange.min as moment.Moment).toDate().getTime();
const dateTo: number = (dateRange.max as moment.Moment).toDate().getTime();
const interval = calculateBarchartColumnTimeInterval(dateFrom, dateTo);
const request = inspectorAdapters.requests.start('Indicator barchart', {});
request.stats({
indexPattern: {
label: 'Index patterns',
value: selectedPatterns,
},
});
abortController.current = new AbortController();
const queryToExecute = buildEsQuery(
undefined,
[
{
query: THREAT_QUERY_BASE,
language: 'kuery',
},
{
query: filterQuery.query as string,
language: 'kuery',
},
],
[
...filters,
{
query: {
range: {
[TIMESTAMP_FIELD]: {
gte: timeRange.from,
lte: timeRange.to,
const requestBody = {
aggregations: {
[AGGREGATION_NAME]: {
terms: {
field,
},
aggs: {
events: {
date_histogram: {
field: TIMESTAMP_FIELD,
fixed_interval: interval,
min_doc_count: 0,
extended_bounds: {
min: dateFrom,
max: dateTo,
},
},
},
},
meta: {},
},
]
);
},
fields: [TIMESTAMP_FIELD, field],
size: 0,
query: queryToExecute,
runtime_mappings: getRuntimeMappings(),
};
searchSubscription$.current = searchService
.search<IEsSearchRequest, IKibanaSearchResponse<RawAggregatedIndicatorsResponse>>(
{
params: {
index: selectedPatterns,
body: {
aggregations: {
[AGGREGATION_NAME]: {
terms: {
field,
},
aggs: {
events: {
date_histogram: {
field: TIMESTAMP_FIELD,
fixed_interval: interval,
min_doc_count: 0,
extended_bounds: {
min: dateFrom,
max: dateTo,
},
},
},
},
},
},
fields: [TIMESTAMP_FIELD, field], // limit the response to only the fields we need
size: 0, // we don't need hits, just aggregations
query: queryToExecute,
runtime_mappings: {
'threat.indicator.name': {
type: 'keyword',
script: {
source: threatIndicatorNamesScript(),
},
},
'threat.indicator.name_origin': {
type: 'keyword',
script: {
source: threatIndicatorNamesOriginScript(),
},
},
},
},
body: requestBody,
},
},
{
@ -202,27 +178,34 @@ export const useAggregatedIndicators = ({
response.rawResponse.aggregations[AGGREGATION_NAME]?.buckets;
const chartSeries: ChartSeries[] = convertAggregationToChartSeries(aggregations);
setIndicators(chartSeries);
searchSubscription$.current.unsubscribe();
request.stats({}).ok({ json: response });
request.json(requestBody);
} else if (isErrorResponse(response)) {
request.error({ json: response });
searchSubscription$.current.unsubscribe();
}
},
error: (msg) => {
searchService.showError(msg);
error: (requestError) => {
searchService.showError(requestError);
searchSubscription$.current.unsubscribe();
if (requestError instanceof Error && requestError.name.includes('Abort')) {
inspectorAdapters.requests.reset();
} else {
request.error({ json: requestError });
}
},
});
}, [
dateRange.max,
dateRange.min,
field,
filterQuery,
filters,
inspectorAdapters.requests,
queryToExecute,
searchService,
selectedPatterns,
timeRange.from,
timeRange.to,
]);
const onFieldChange = useCallback(

View file

@ -13,12 +13,13 @@ import {
} from '@kbn/data-plugin/common';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { Subscription } from 'rxjs';
import { buildEsQuery, Filter, Query, TimeRange } from '@kbn/es-query';
import { Filter, Query, TimeRange } from '@kbn/es-query';
import { useInspector } from '../../../hooks/use_inspector';
import { Indicator } from '../../../../common/types/indicator';
import { useKibana } from '../../../hooks/use_kibana';
import { THREAT_QUERY_BASE } from '../../../../common/constants';
import { useSourcererDataView } from './use_sourcerer_data_view';
import { threatIndicatorNamesOriginScript, threatIndicatorNamesScript } from '../lib/display_name';
import { getRuntimeMappings } from '../lib/get_runtime_mappings';
import { getIndicatorsQuery } from '../lib/get_indicators_query';
const PAGE_SIZES = [10, 25, 50];
@ -67,6 +68,8 @@ export const useIndicators = ({
} = useKibana();
const { selectedPatterns } = useSourcererDataView();
const { inspectorAdapters } = useInspector();
const searchSubscription$ = useRef<Subscription>();
const abortController = useRef(new AbortController());
@ -80,36 +83,9 @@ export const useIndicators = ({
pageSizeOptions: PAGE_SIZES,
});
const queryToExecute = useMemo(
() =>
buildEsQuery(
undefined,
[
{
query: THREAT_QUERY_BASE,
language: 'kuery',
},
{
query: filterQuery.query as string,
language: 'kuery',
},
],
[
...filters,
{
query: {
range: {
['@timestamp']: {
gte: timeRange?.from,
lte: timeRange?.to,
},
},
},
meta: {},
},
]
),
[filterQuery, filters, timeRange?.from, timeRange?.to]
const query = useMemo(
() => getIndicatorsQuery({ filters, timeRange, filterQuery }),
[filterQuery, filters, timeRange]
);
const loadData = useCallback(
@ -118,32 +94,30 @@ export const useIndicators = ({
setLoading(true);
const request = inspectorAdapters.requests.start('Indicator search', {});
request.stats({
indexPattern: {
label: 'Index patterns',
value: selectedPatterns,
},
});
const requestBody = {
query,
runtime_mappings: getRuntimeMappings(),
fields: [{ field: '*', include_unmapped: true }],
size,
from,
sort: sorting.map(({ id, direction }) => ({ [id]: direction })),
};
searchSubscription$.current = searchService
.search<IEsSearchRequest, IKibanaSearchResponse<RawIndicatorsResponse>>(
{
params: {
index: selectedPatterns,
body: {
size,
from,
fields: [{ field: '*', include_unmapped: true }],
query: queryToExecute,
sort: sorting.map(({ id, direction }) => ({ [id]: direction })),
runtime_mappings: {
'threat.indicator.name': {
type: 'keyword',
script: {
source: threatIndicatorNamesScript(),
},
},
'threat.indicator.name_origin': {
type: 'keyword',
script: {
source: threatIndicatorNamesOriginScript(),
},
},
},
},
body: requestBody,
},
},
{
@ -158,20 +132,29 @@ export const useIndicators = ({
if (isCompleteResponse(response)) {
setLoading(false);
searchSubscription$.current?.unsubscribe();
request.stats({}).ok({ json: response });
request.json(requestBody);
} else if (isErrorResponse(response)) {
setLoading(false);
request.error({ json: response });
searchSubscription$.current?.unsubscribe();
}
},
error: (msg) => {
searchService.showError(msg);
error: (requestError) => {
searchService.showError(requestError);
searchSubscription$.current?.unsubscribe();
if (requestError instanceof Error && requestError.name.includes('Abort')) {
inspectorAdapters.requests.reset();
} else {
request.error({ json: requestError });
}
setLoading(false);
},
});
},
[queryToExecute, searchService, selectedPatterns, sorting]
[inspectorAdapters.requests, query, searchService, selectedPatterns, sorting]
);
const onChangeItemsPerPage = useCallback(

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { VFC } from 'react';
import React, { FC, VFC } from 'react';
import { IndicatorsFilters } from './containers/indicators_filters/indicators_filters';
import { IndicatorsBarChartWrapper } from './components/indicators_barchart_wrapper/indicators_barchart_wrapper';
import { IndicatorsTable } from './components/indicators_table/indicators_table';
@ -16,9 +16,16 @@ 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';
export const IndicatorsPage: VFC = () => {
const IndicatorsPageProviders: FC = ({ children }) => (
<FieldTypesProvider>
<InspectorProvider>{children}</InspectorProvider>
</FieldTypesProvider>
);
const IndicatorsPageContent: VFC = () => {
const { browserFields, indexPattern } = useSourcererDataView();
const columnSettings = useColumnSettings();
@ -75,6 +82,12 @@ export const IndicatorsPage: VFC = () => {
);
};
export const IndicatorsPage: VFC = () => (
<IndicatorsPageProviders>
<IndicatorsPageContent />
</IndicatorsPageProviders>
);
// Note: This is for lazy loading
// eslint-disable-next-line import/no-default-export
export default IndicatorsPage;

View file

@ -0,0 +1,50 @@
/*
* 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 { buildEsQuery, Filter, Query, TimeRange } from '@kbn/es-query';
import { THREAT_QUERY_BASE } from '../../../../common/constants';
import { RawIndicatorFieldId } from '../../../../common/types/indicator';
const TIMESTAMP_FIELD = RawIndicatorFieldId.TimeStamp;
export const getIndicatorsQuery = ({
filters,
filterQuery,
timeRange,
}: {
filters: Filter[];
filterQuery: Query;
timeRange?: TimeRange;
}) => {
return buildEsQuery(
undefined,
[
{
query: THREAT_QUERY_BASE,
language: 'kuery',
},
{
query: filterQuery.query as string,
language: 'kuery',
},
],
[
...filters,
{
query: {
range: {
[TIMESTAMP_FIELD]: {
gte: timeRange?.from,
lte: timeRange?.to,
},
},
},
meta: {},
},
]
);
};

View file

@ -0,0 +1,24 @@
/*
* 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 { threatIndicatorNamesOriginScript, threatIndicatorNamesScript } from './display_name';
export const getRuntimeMappings = () =>
({
'threat.indicator.name': {
type: 'keyword',
script: {
source: threatIndicatorNamesScript(),
},
},
'threat.indicator.name_origin': {
type: 'keyword',
script: {
source: threatIndicatorNamesOriginScript(),
},
},
} as const);

View file

@ -20,6 +20,7 @@ import { DataViewBase } from '@kbn/es-query';
import { BrowserField } from '@kbn/rule-registry-plugin/common';
import { Store } from 'redux';
import { DataProvider } from '@kbn/timelines-plugin/common';
import { Start as InspectorPluginStart } from '@kbn/inspector-plugin/public';
export interface SecuritySolutionDataViewBase extends DataViewBase {
fields: Array<FieldSpec & DataViewField>;
@ -45,6 +46,7 @@ export type Services = {
triggersActionsUi: TriggersActionsStart;
timelines: TimelinesUIStart;
securityLayout: any;
inspector: InspectorPluginStart;
} & CoreStart;
export interface LicenseAware {