mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Endpoint] Get current host info when retrieving alert details (#60906)
* create new alert details type * update integration test * add await to esarchiver call * remove unused host stats type * does the ui types good * change host.host to host_metadata.host * fix mock result type Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Davis Plumlee <davis.plumlee@elastic.co>
This commit is contained in:
parent
77a5b9e8ac
commit
b1fa159e17
11 changed files with 73 additions and 37 deletions
|
@ -239,11 +239,19 @@ interface AlertMetadata {
|
|||
prev: string | null;
|
||||
}
|
||||
|
||||
interface AlertState {
|
||||
state: {
|
||||
host_metadata: HostMetadata;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Union of alert data and metadata.
|
||||
*/
|
||||
export type AlertData = AlertEvent & AlertMetadata;
|
||||
|
||||
export type AlertDetails = AlertData & AlertState;
|
||||
|
||||
export type HostMetadata = Immutable<{
|
||||
'@timestamp': number;
|
||||
event: {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { IIndexPattern } from 'src/plugins/data/public';
|
||||
import { Immutable, AlertData } from '../../../../../common/types';
|
||||
import { Immutable, AlertDetails } from '../../../../../common/types';
|
||||
import { AlertListData } from '../../types';
|
||||
|
||||
interface ServerReturnedAlertsData {
|
||||
|
@ -15,7 +15,7 @@ interface ServerReturnedAlertsData {
|
|||
|
||||
interface ServerReturnedAlertDetailsData {
|
||||
readonly type: 'serverReturnedAlertDetailsData';
|
||||
readonly payload: Immutable<AlertData>;
|
||||
readonly payload: Immutable<AlertDetails>;
|
||||
}
|
||||
|
||||
interface ServerReturnedSearchBarIndexPatterns {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { IIndexPattern } from 'src/plugins/data/public';
|
||||
import { AlertResultList, AlertData } from '../../../../../common/types';
|
||||
import { AlertResultList, AlertDetails } from '../../../../../common/types';
|
||||
import { AppAction } from '../action';
|
||||
import { MiddlewareFactory, AlertListState } from '../../types';
|
||||
import { isOnAlertPage, apiQueryParams, hasSelectedAlert, uiQueryParams } from './selectors';
|
||||
|
@ -40,7 +40,7 @@ export const alertMiddlewareFactory: MiddlewareFactory<AlertListState> = (coreSt
|
|||
|
||||
if (action.type === 'userChangedUrl' && isOnAlertPage(state) && hasSelectedAlert(state)) {
|
||||
const uiParams = uiQueryParams(state);
|
||||
const response: AlertData = await coreStart.http.get(
|
||||
const response: AlertDetails = await coreStart.http.get(
|
||||
`/api/endpoint/alerts/${uiParams.selected_alert}`
|
||||
);
|
||||
api.dispatch({ type: 'serverReturnedAlertDetailsData', payload: response });
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AlertResultList } from '../../../../../common/types';
|
||||
import { AlertResultList, AlertDetails } from '../../../../../common/types';
|
||||
import { EndpointDocGenerator } from '../../../../../common/generate_data';
|
||||
|
||||
export const mockAlertResultList: (options?: {
|
||||
|
@ -47,3 +47,18 @@ export const mockAlertResultList: (options?: {
|
|||
};
|
||||
return mock;
|
||||
};
|
||||
|
||||
export const mockAlertDetailsResult = (): AlertDetails => {
|
||||
const generator = new EndpointDocGenerator();
|
||||
return {
|
||||
...generator.generateAlert(new Date().getTime()),
|
||||
...{
|
||||
id: 'xDUYMHABAKk0XnHd8rrd',
|
||||
prev: null,
|
||||
next: null,
|
||||
state: {
|
||||
host_metadata: generator.generateHostMetadata(),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
AlertResultList,
|
||||
Immutable,
|
||||
ImmutableArray,
|
||||
AlertDetails,
|
||||
} from '../../../common/types';
|
||||
import { EndpointPluginStartDependencies } from '../../plugin';
|
||||
import { AppAction } from './store/action';
|
||||
|
@ -196,7 +197,7 @@ export interface AlertListState {
|
|||
readonly location?: Immutable<EndpointAppLocation>;
|
||||
|
||||
/** Specific Alert data to be shown in the details view */
|
||||
readonly alertDetails?: Immutable<AlertData>;
|
||||
readonly alertDetails?: Immutable<AlertDetails>;
|
||||
|
||||
/** Search bar state including indexPatterns */
|
||||
readonly searchBar: AlertsSearchBarState;
|
||||
|
|
|
@ -9,7 +9,7 @@ import { appStoreFactory } from '../../store';
|
|||
import { fireEvent } from '@testing-library/react';
|
||||
import { MemoryHistory } from 'history';
|
||||
import { AppAction } from '../../types';
|
||||
import { mockAlertResultList } from '../../store/alerts/mock_alert_result_list';
|
||||
import { mockAlertDetailsResult } from '../../store/alerts/mock_alert_result_list';
|
||||
import { alertPageTestRender } from './test_helpers/render_alert_page';
|
||||
|
||||
describe('when the alert details flyout is open', () => {
|
||||
|
@ -34,7 +34,7 @@ describe('when the alert details flyout is open', () => {
|
|||
reactTestingLibrary.act(() => {
|
||||
const action: AppAction = {
|
||||
type: 'serverReturnedAlertDetailsData',
|
||||
payload: mockAlertResultList().alerts[0],
|
||||
payload: mockAlertDetailsResult(),
|
||||
};
|
||||
store.dispatch(action);
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ import { AlertEvent, EndpointAppConstants } from '../../../../common/types';
|
|||
import { EndpointAppContext } from '../../../types';
|
||||
import { AlertDetailsRequestParams } from '../types';
|
||||
import { AlertDetailsPagination } from './lib';
|
||||
import { getHostData } from '../../../routes/metadata';
|
||||
|
||||
export const alertDetailsHandlerWrapper = function(
|
||||
endpointAppContext: EndpointAppContext
|
||||
|
@ -33,10 +34,15 @@ export const alertDetailsHandlerWrapper = function(
|
|||
response
|
||||
);
|
||||
|
||||
const currentHostInfo = await getHostData(ctx, response._source.host.id);
|
||||
|
||||
return res.ok({
|
||||
body: {
|
||||
id: response._id,
|
||||
...response._source,
|
||||
state: {
|
||||
host_metadata: currentHostInfo,
|
||||
},
|
||||
next: await pagination.getNextUrl(),
|
||||
prev: await pagination.getPrevUrl(),
|
||||
},
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { IRouter } from 'kibana/server';
|
||||
import { IRouter, RequestHandlerContext } from 'kibana/server';
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import {
|
||||
kibanaRequestToMetadataListESQuery,
|
||||
kibanaRequestToMetadataGetESQuery,
|
||||
getESQueryHostMetadataByID,
|
||||
} from '../services/endpoint/metadata_query_builders';
|
||||
import { HostMetadata, HostResultList } from '../../common/types';
|
||||
import { EndpointAppContext } from '../types';
|
||||
|
@ -75,17 +75,11 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp
|
|||
},
|
||||
async (context, req, res) => {
|
||||
try {
|
||||
const query = kibanaRequestToMetadataGetESQuery(req, endpointAppContext);
|
||||
const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser(
|
||||
'search',
|
||||
query
|
||||
)) as SearchResponse<HostMetadata>;
|
||||
|
||||
if (response.hits.hits.length === 0) {
|
||||
return res.notFound({ body: 'Endpoint Not Found' });
|
||||
const doc = await getHostData(context, req.params.id);
|
||||
if (doc) {
|
||||
return res.ok({ body: doc });
|
||||
}
|
||||
|
||||
return res.ok({ body: response.hits.hits[0]._source });
|
||||
return res.notFound({ body: 'Endpoint Not Found' });
|
||||
} catch (err) {
|
||||
return res.internalError({ body: err });
|
||||
}
|
||||
|
@ -93,6 +87,23 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp
|
|||
);
|
||||
}
|
||||
|
||||
export async function getHostData(
|
||||
context: RequestHandlerContext,
|
||||
id: string
|
||||
): Promise<HostMetadata | undefined> {
|
||||
const query = getESQueryHostMetadataByID(id);
|
||||
const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser(
|
||||
'search',
|
||||
query
|
||||
)) as SearchResponse<HostMetadata>;
|
||||
|
||||
if (response.hits.hits.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return response.hits.hits[0]._source;
|
||||
}
|
||||
|
||||
function mapToHostResultList(
|
||||
queryParams: Record<string, any>,
|
||||
searchResponse: SearchResponse<HostMetadata>
|
||||
|
|
|
@ -7,7 +7,7 @@ import { httpServerMock, loggingServiceMock } from '../../../../../../src/core/s
|
|||
import { EndpointConfigSchema } from '../../config';
|
||||
import {
|
||||
kibanaRequestToMetadataListESQuery,
|
||||
kibanaRequestToMetadataGetESQuery,
|
||||
getESQueryHostMetadataByID,
|
||||
} from './metadata_query_builders';
|
||||
import { EndpointAppConstants } from '../../../common/types';
|
||||
|
||||
|
@ -118,15 +118,7 @@ describe('query builder', () => {
|
|||
describe('MetadataGetQuery', () => {
|
||||
it('searches for the correct ID', () => {
|
||||
const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899';
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
params: {
|
||||
id: mockID,
|
||||
},
|
||||
});
|
||||
const query = kibanaRequestToMetadataGetESQuery(mockRequest, {
|
||||
logFactory: loggingServiceMock.create(),
|
||||
config: () => Promise.resolve(EndpointConfigSchema.validate({})),
|
||||
});
|
||||
const query = getESQueryHostMetadataByID(mockID);
|
||||
expect(query).toEqual({
|
||||
body: {
|
||||
query: { match: { 'host.id.keyword': mockID } },
|
||||
|
|
|
@ -74,15 +74,12 @@ function buildQueryBody(request: KibanaRequest<any, any, any>): Record<string, a
|
|||
};
|
||||
}
|
||||
|
||||
export const kibanaRequestToMetadataGetESQuery = (
|
||||
request: KibanaRequest<any, any, any>,
|
||||
endpointAppContext: EndpointAppContext
|
||||
) => {
|
||||
export function getESQueryHostMetadataByID(hostID: string) {
|
||||
return {
|
||||
body: {
|
||||
query: {
|
||||
match: {
|
||||
'host.id.keyword': request.params.id,
|
||||
'host.id.keyword': hostID,
|
||||
},
|
||||
},
|
||||
sort: [
|
||||
|
@ -96,4 +93,4 @@ export const kibanaRequestToMetadataGetESQuery = (
|
|||
},
|
||||
index: EndpointAppConstants.ENDPOINT_INDEX_NAME,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -72,13 +72,18 @@ export default function({ getService }: FtrProviderContext) {
|
|||
describe('when data is in elasticsearch', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('endpoint/alerts/api_feature');
|
||||
await esArchiver.load('endpoint/metadata/api_feature');
|
||||
const res = await es.search({
|
||||
index: 'events-endpoint-1',
|
||||
body: ES_QUERY_MISSING,
|
||||
});
|
||||
nullableEventId = res.hits.hits[0]._source.event.id;
|
||||
});
|
||||
after(() => esArchiver.unload('endpoint/alerts/api_feature'));
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('endpoint/alerts/api_feature');
|
||||
await esArchiver.unload('endpoint/metadata/api_feature');
|
||||
});
|
||||
|
||||
it('should not support POST requests', async () => {
|
||||
await supertest
|
||||
|
@ -381,6 +386,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
expect(body.id).to.eql(documentID);
|
||||
expect(body.prev).to.eql(`/api/endpoint/alerts/${prevDocumentID}`);
|
||||
expect(body.next).to.eql(null); // last alert, no more beyond this
|
||||
expect(body.state.host_metadata.host.id).to.eql(body.host.id);
|
||||
});
|
||||
|
||||
it('should return alert details by id, getting first alert', async () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue