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;
|
prev: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AlertState {
|
||||||
|
state: {
|
||||||
|
host_metadata: HostMetadata;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Union of alert data and metadata.
|
* Union of alert data and metadata.
|
||||||
*/
|
*/
|
||||||
export type AlertData = AlertEvent & AlertMetadata;
|
export type AlertData = AlertEvent & AlertMetadata;
|
||||||
|
|
||||||
|
export type AlertDetails = AlertData & AlertState;
|
||||||
|
|
||||||
export type HostMetadata = Immutable<{
|
export type HostMetadata = Immutable<{
|
||||||
'@timestamp': number;
|
'@timestamp': number;
|
||||||
event: {
|
event: {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IIndexPattern } from 'src/plugins/data/public';
|
import { IIndexPattern } from 'src/plugins/data/public';
|
||||||
import { Immutable, AlertData } from '../../../../../common/types';
|
import { Immutable, AlertDetails } from '../../../../../common/types';
|
||||||
import { AlertListData } from '../../types';
|
import { AlertListData } from '../../types';
|
||||||
|
|
||||||
interface ServerReturnedAlertsData {
|
interface ServerReturnedAlertsData {
|
||||||
|
@ -15,7 +15,7 @@ interface ServerReturnedAlertsData {
|
||||||
|
|
||||||
interface ServerReturnedAlertDetailsData {
|
interface ServerReturnedAlertDetailsData {
|
||||||
readonly type: 'serverReturnedAlertDetailsData';
|
readonly type: 'serverReturnedAlertDetailsData';
|
||||||
readonly payload: Immutable<AlertData>;
|
readonly payload: Immutable<AlertDetails>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ServerReturnedSearchBarIndexPatterns {
|
interface ServerReturnedSearchBarIndexPatterns {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IIndexPattern } from 'src/plugins/data/public';
|
import { IIndexPattern } from 'src/plugins/data/public';
|
||||||
import { AlertResultList, AlertData } from '../../../../../common/types';
|
import { AlertResultList, AlertDetails } from '../../../../../common/types';
|
||||||
import { AppAction } from '../action';
|
import { AppAction } from '../action';
|
||||||
import { MiddlewareFactory, AlertListState } from '../../types';
|
import { MiddlewareFactory, AlertListState } from '../../types';
|
||||||
import { isOnAlertPage, apiQueryParams, hasSelectedAlert, uiQueryParams } from './selectors';
|
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)) {
|
if (action.type === 'userChangedUrl' && isOnAlertPage(state) && hasSelectedAlert(state)) {
|
||||||
const uiParams = uiQueryParams(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/endpoint/alerts/${uiParams.selected_alert}`
|
||||||
);
|
);
|
||||||
api.dispatch({ type: 'serverReturnedAlertDetailsData', payload: response });
|
api.dispatch({ type: 'serverReturnedAlertDetailsData', payload: response });
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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';
|
import { EndpointDocGenerator } from '../../../../../common/generate_data';
|
||||||
|
|
||||||
export const mockAlertResultList: (options?: {
|
export const mockAlertResultList: (options?: {
|
||||||
|
@ -47,3 +47,18 @@ export const mockAlertResultList: (options?: {
|
||||||
};
|
};
|
||||||
return mock;
|
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,
|
AlertResultList,
|
||||||
Immutable,
|
Immutable,
|
||||||
ImmutableArray,
|
ImmutableArray,
|
||||||
|
AlertDetails,
|
||||||
} from '../../../common/types';
|
} from '../../../common/types';
|
||||||
import { EndpointPluginStartDependencies } from '../../plugin';
|
import { EndpointPluginStartDependencies } from '../../plugin';
|
||||||
import { AppAction } from './store/action';
|
import { AppAction } from './store/action';
|
||||||
|
@ -196,7 +197,7 @@ export interface AlertListState {
|
||||||
readonly location?: Immutable<EndpointAppLocation>;
|
readonly location?: Immutable<EndpointAppLocation>;
|
||||||
|
|
||||||
/** Specific Alert data to be shown in the details view */
|
/** Specific Alert data to be shown in the details view */
|
||||||
readonly alertDetails?: Immutable<AlertData>;
|
readonly alertDetails?: Immutable<AlertDetails>;
|
||||||
|
|
||||||
/** Search bar state including indexPatterns */
|
/** Search bar state including indexPatterns */
|
||||||
readonly searchBar: AlertsSearchBarState;
|
readonly searchBar: AlertsSearchBarState;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { appStoreFactory } from '../../store';
|
||||||
import { fireEvent } from '@testing-library/react';
|
import { fireEvent } from '@testing-library/react';
|
||||||
import { MemoryHistory } from 'history';
|
import { MemoryHistory } from 'history';
|
||||||
import { AppAction } from '../../types';
|
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';
|
import { alertPageTestRender } from './test_helpers/render_alert_page';
|
||||||
|
|
||||||
describe('when the alert details flyout is open', () => {
|
describe('when the alert details flyout is open', () => {
|
||||||
|
@ -34,7 +34,7 @@ describe('when the alert details flyout is open', () => {
|
||||||
reactTestingLibrary.act(() => {
|
reactTestingLibrary.act(() => {
|
||||||
const action: AppAction = {
|
const action: AppAction = {
|
||||||
type: 'serverReturnedAlertDetailsData',
|
type: 'serverReturnedAlertDetailsData',
|
||||||
payload: mockAlertResultList().alerts[0],
|
payload: mockAlertDetailsResult(),
|
||||||
};
|
};
|
||||||
store.dispatch(action);
|
store.dispatch(action);
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { AlertEvent, EndpointAppConstants } from '../../../../common/types';
|
||||||
import { EndpointAppContext } from '../../../types';
|
import { EndpointAppContext } from '../../../types';
|
||||||
import { AlertDetailsRequestParams } from '../types';
|
import { AlertDetailsRequestParams } from '../types';
|
||||||
import { AlertDetailsPagination } from './lib';
|
import { AlertDetailsPagination } from './lib';
|
||||||
|
import { getHostData } from '../../../routes/metadata';
|
||||||
|
|
||||||
export const alertDetailsHandlerWrapper = function(
|
export const alertDetailsHandlerWrapper = function(
|
||||||
endpointAppContext: EndpointAppContext
|
endpointAppContext: EndpointAppContext
|
||||||
|
@ -33,10 +34,15 @@ export const alertDetailsHandlerWrapper = function(
|
||||||
response
|
response
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const currentHostInfo = await getHostData(ctx, response._source.host.id);
|
||||||
|
|
||||||
return res.ok({
|
return res.ok({
|
||||||
body: {
|
body: {
|
||||||
id: response._id,
|
id: response._id,
|
||||||
...response._source,
|
...response._source,
|
||||||
|
state: {
|
||||||
|
host_metadata: currentHostInfo,
|
||||||
|
},
|
||||||
next: await pagination.getNextUrl(),
|
next: await pagination.getNextUrl(),
|
||||||
prev: await pagination.getPrevUrl(),
|
prev: await pagination.getPrevUrl(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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 { SearchResponse } from 'elasticsearch';
|
||||||
import { schema } from '@kbn/config-schema';
|
import { schema } from '@kbn/config-schema';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
kibanaRequestToMetadataListESQuery,
|
kibanaRequestToMetadataListESQuery,
|
||||||
kibanaRequestToMetadataGetESQuery,
|
getESQueryHostMetadataByID,
|
||||||
} from '../services/endpoint/metadata_query_builders';
|
} from '../services/endpoint/metadata_query_builders';
|
||||||
import { HostMetadata, HostResultList } from '../../common/types';
|
import { HostMetadata, HostResultList } from '../../common/types';
|
||||||
import { EndpointAppContext } from '../types';
|
import { EndpointAppContext } from '../types';
|
||||||
|
@ -75,17 +75,11 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp
|
||||||
},
|
},
|
||||||
async (context, req, res) => {
|
async (context, req, res) => {
|
||||||
try {
|
try {
|
||||||
const query = kibanaRequestToMetadataGetESQuery(req, endpointAppContext);
|
const doc = await getHostData(context, req.params.id);
|
||||||
const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser(
|
if (doc) {
|
||||||
'search',
|
return res.ok({ body: doc });
|
||||||
query
|
|
||||||
)) as SearchResponse<HostMetadata>;
|
|
||||||
|
|
||||||
if (response.hits.hits.length === 0) {
|
|
||||||
return res.notFound({ body: 'Endpoint Not Found' });
|
|
||||||
}
|
}
|
||||||
|
return res.notFound({ body: 'Endpoint Not Found' });
|
||||||
return res.ok({ body: response.hits.hits[0]._source });
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return res.internalError({ body: 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(
|
function mapToHostResultList(
|
||||||
queryParams: Record<string, any>,
|
queryParams: Record<string, any>,
|
||||||
searchResponse: SearchResponse<HostMetadata>
|
searchResponse: SearchResponse<HostMetadata>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { httpServerMock, loggingServiceMock } from '../../../../../../src/core/s
|
||||||
import { EndpointConfigSchema } from '../../config';
|
import { EndpointConfigSchema } from '../../config';
|
||||||
import {
|
import {
|
||||||
kibanaRequestToMetadataListESQuery,
|
kibanaRequestToMetadataListESQuery,
|
||||||
kibanaRequestToMetadataGetESQuery,
|
getESQueryHostMetadataByID,
|
||||||
} from './metadata_query_builders';
|
} from './metadata_query_builders';
|
||||||
import { EndpointAppConstants } from '../../../common/types';
|
import { EndpointAppConstants } from '../../../common/types';
|
||||||
|
|
||||||
|
@ -118,15 +118,7 @@ describe('query builder', () => {
|
||||||
describe('MetadataGetQuery', () => {
|
describe('MetadataGetQuery', () => {
|
||||||
it('searches for the correct ID', () => {
|
it('searches for the correct ID', () => {
|
||||||
const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899';
|
const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899';
|
||||||
const mockRequest = httpServerMock.createKibanaRequest({
|
const query = getESQueryHostMetadataByID(mockID);
|
||||||
params: {
|
|
||||||
id: mockID,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const query = kibanaRequestToMetadataGetESQuery(mockRequest, {
|
|
||||||
logFactory: loggingServiceMock.create(),
|
|
||||||
config: () => Promise.resolve(EndpointConfigSchema.validate({})),
|
|
||||||
});
|
|
||||||
expect(query).toEqual({
|
expect(query).toEqual({
|
||||||
body: {
|
body: {
|
||||||
query: { match: { 'host.id.keyword': mockID } },
|
query: { match: { 'host.id.keyword': mockID } },
|
||||||
|
|
|
@ -74,15 +74,12 @@ function buildQueryBody(request: KibanaRequest<any, any, any>): Record<string, a
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const kibanaRequestToMetadataGetESQuery = (
|
export function getESQueryHostMetadataByID(hostID: string) {
|
||||||
request: KibanaRequest<any, any, any>,
|
|
||||||
endpointAppContext: EndpointAppContext
|
|
||||||
) => {
|
|
||||||
return {
|
return {
|
||||||
body: {
|
body: {
|
||||||
query: {
|
query: {
|
||||||
match: {
|
match: {
|
||||||
'host.id.keyword': request.params.id,
|
'host.id.keyword': hostID,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sort: [
|
sort: [
|
||||||
|
@ -96,4 +93,4 @@ export const kibanaRequestToMetadataGetESQuery = (
|
||||||
},
|
},
|
||||||
index: EndpointAppConstants.ENDPOINT_INDEX_NAME,
|
index: EndpointAppConstants.ENDPOINT_INDEX_NAME,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
|
@ -72,13 +72,18 @@ export default function({ getService }: FtrProviderContext) {
|
||||||
describe('when data is in elasticsearch', () => {
|
describe('when data is in elasticsearch', () => {
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await esArchiver.load('endpoint/alerts/api_feature');
|
await esArchiver.load('endpoint/alerts/api_feature');
|
||||||
|
await esArchiver.load('endpoint/metadata/api_feature');
|
||||||
const res = await es.search({
|
const res = await es.search({
|
||||||
index: 'events-endpoint-1',
|
index: 'events-endpoint-1',
|
||||||
body: ES_QUERY_MISSING,
|
body: ES_QUERY_MISSING,
|
||||||
});
|
});
|
||||||
nullableEventId = res.hits.hits[0]._source.event.id;
|
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 () => {
|
it('should not support POST requests', async () => {
|
||||||
await supertest
|
await supertest
|
||||||
|
@ -381,6 +386,7 @@ export default function({ getService }: FtrProviderContext) {
|
||||||
expect(body.id).to.eql(documentID);
|
expect(body.id).to.eql(documentID);
|
||||||
expect(body.prev).to.eql(`/api/endpoint/alerts/${prevDocumentID}`);
|
expect(body.prev).to.eql(`/api/endpoint/alerts/${prevDocumentID}`);
|
||||||
expect(body.next).to.eql(null); // last alert, no more beyond this
|
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 () => {
|
it('should return alert details by id, getting first alert', async () => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue