mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[TIP] Add inspect capability for barchart and grid requests (#140810)
This commit is contained in:
parent
5c11b65bb6
commit
21d01719eb
15 changed files with 403 additions and 184 deletions
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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"]';
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
"kibanaUtils",
|
||||
"navigation",
|
||||
"kibanaReact",
|
||||
"triggersActionsUi"
|
||||
"triggersActionsUi",
|
||||
"inspector"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"data",
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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';
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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 };
|
||||
};
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: {},
|
||||
},
|
||||
]
|
||||
);
|
||||
};
|
|
@ -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);
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue