[EventLog] Added event log API to get events for multiple saved objects. (#87596)
* Added alerting API to get all active instances * modofied event log findEventsBySavedObject to support bulk ids, renamed to findEventsBySavedObjectIds * fixed faling typechecks * fixed crash on zpd/api/event_log/alert/84c00970-5130-11eb-9fa7/_find for non existing id * fixed faling typechecks * fixed faling typechecks * fixed due to comments * fixed due to comments * fixed failing test * fixed due to comments
This commit is contained in:
parent
5e4402c374
commit
fb67443e6d
|
@ -19,6 +19,7 @@ import {
|
|||
ElasticsearchServiceStart,
|
||||
ILegacyClusterClient,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsBulkGetObject,
|
||||
} from '../../../../src/core/server';
|
||||
|
||||
import {
|
||||
|
@ -333,7 +334,12 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
|
|||
|
||||
this.eventLogService!.registerSavedObjectProvider('action', (request) => {
|
||||
const client = secureGetActionsClientWithRequest(request);
|
||||
return async (type: string, id: string) => (await client).get({ id });
|
||||
return (objects?: SavedObjectsBulkGetObject[]) =>
|
||||
objects
|
||||
? Promise.all(
|
||||
objects.map(async (objectItem) => await (await client).get({ id: objectItem.id }))
|
||||
)
|
||||
: Promise.resolve([]);
|
||||
});
|
||||
|
||||
const getScopedSavedObjectsClientWithoutAccessToActions = (request: KibanaRequest) =>
|
||||
|
|
|
@ -412,7 +412,7 @@ export class AlertsClient {
|
|||
this.logger.debug(`getAlertInstanceSummary(): search the event log for alert ${id}`);
|
||||
let events: IEvent[];
|
||||
try {
|
||||
const queryResults = await eventLogClient.findEventsBySavedObject('alert', id, {
|
||||
const queryResults = await eventLogClient.findEventsBySavedObjectIds('alert', [id], {
|
||||
page: 1,
|
||||
per_page: 10000,
|
||||
start: parsedDateStart.toISOString(),
|
||||
|
|
|
@ -131,7 +131,7 @@ describe('getAlertInstanceSummary()', () => {
|
|||
total: events.length,
|
||||
data: events,
|
||||
};
|
||||
eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(eventsResult);
|
||||
eventLogClient.findEventsBySavedObjectIds.mockResolvedValueOnce(eventsResult);
|
||||
|
||||
const dateStart = new Date(Date.now() - 60 * 1000).toISOString();
|
||||
|
||||
|
@ -188,18 +188,20 @@ describe('getAlertInstanceSummary()', () => {
|
|||
|
||||
test('calls saved objects and event log client with default params', async () => {
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertInstanceSummarySavedObject());
|
||||
eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(
|
||||
eventLogClient.findEventsBySavedObjectIds.mockResolvedValueOnce(
|
||||
AlertInstanceSummaryFindEventsResult
|
||||
);
|
||||
|
||||
await alertsClient.getAlertInstanceSummary({ id: '1' });
|
||||
|
||||
expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1);
|
||||
expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1);
|
||||
expect(eventLogClient.findEventsBySavedObject.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
expect(eventLogClient.findEventsBySavedObjectIds).toHaveBeenCalledTimes(1);
|
||||
expect(eventLogClient.findEventsBySavedObjectIds.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"alert",
|
||||
"1",
|
||||
Array [
|
||||
"1",
|
||||
],
|
||||
Object {
|
||||
"end": "2019-02-12T21:01:22.479Z",
|
||||
"page": 1,
|
||||
|
@ -210,7 +212,7 @@ describe('getAlertInstanceSummary()', () => {
|
|||
]
|
||||
`);
|
||||
// calculate the expected start/end date for one test
|
||||
const { start, end } = eventLogClient.findEventsBySavedObject.mock.calls[0][2]!;
|
||||
const { start, end } = eventLogClient.findEventsBySavedObjectIds.mock.calls[0][2]!;
|
||||
expect(end).toBe(mockedDateString);
|
||||
|
||||
const startMillis = Date.parse(start!);
|
||||
|
@ -222,7 +224,7 @@ describe('getAlertInstanceSummary()', () => {
|
|||
|
||||
test('calls event log client with start date', async () => {
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertInstanceSummarySavedObject());
|
||||
eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(
|
||||
eventLogClient.findEventsBySavedObjectIds.mockResolvedValueOnce(
|
||||
AlertInstanceSummaryFindEventsResult
|
||||
);
|
||||
|
||||
|
@ -232,8 +234,8 @@ describe('getAlertInstanceSummary()', () => {
|
|||
await alertsClient.getAlertInstanceSummary({ id: '1', dateStart });
|
||||
|
||||
expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1);
|
||||
expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1);
|
||||
const { start, end } = eventLogClient.findEventsBySavedObject.mock.calls[0][2]!;
|
||||
expect(eventLogClient.findEventsBySavedObjectIds).toHaveBeenCalledTimes(1);
|
||||
const { start, end } = eventLogClient.findEventsBySavedObjectIds.mock.calls[0][2]!;
|
||||
|
||||
expect({ start, end }).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -245,7 +247,7 @@ describe('getAlertInstanceSummary()', () => {
|
|||
|
||||
test('calls event log client with relative start date', async () => {
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertInstanceSummarySavedObject());
|
||||
eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(
|
||||
eventLogClient.findEventsBySavedObjectIds.mockResolvedValueOnce(
|
||||
AlertInstanceSummaryFindEventsResult
|
||||
);
|
||||
|
||||
|
@ -253,8 +255,8 @@ describe('getAlertInstanceSummary()', () => {
|
|||
await alertsClient.getAlertInstanceSummary({ id: '1', dateStart });
|
||||
|
||||
expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1);
|
||||
expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1);
|
||||
const { start, end } = eventLogClient.findEventsBySavedObject.mock.calls[0][2]!;
|
||||
expect(eventLogClient.findEventsBySavedObjectIds).toHaveBeenCalledTimes(1);
|
||||
const { start, end } = eventLogClient.findEventsBySavedObjectIds.mock.calls[0][2]!;
|
||||
|
||||
expect({ start, end }).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -266,7 +268,7 @@ describe('getAlertInstanceSummary()', () => {
|
|||
|
||||
test('invalid start date throws an error', async () => {
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertInstanceSummarySavedObject());
|
||||
eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(
|
||||
eventLogClient.findEventsBySavedObjectIds.mockResolvedValueOnce(
|
||||
AlertInstanceSummaryFindEventsResult
|
||||
);
|
||||
|
||||
|
@ -280,7 +282,7 @@ describe('getAlertInstanceSummary()', () => {
|
|||
|
||||
test('saved object get throws an error', async () => {
|
||||
unsecuredSavedObjectsClient.get.mockRejectedValueOnce(new Error('OMG!'));
|
||||
eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(
|
||||
eventLogClient.findEventsBySavedObjectIds.mockResolvedValueOnce(
|
||||
AlertInstanceSummaryFindEventsResult
|
||||
);
|
||||
|
||||
|
@ -291,7 +293,7 @@ describe('getAlertInstanceSummary()', () => {
|
|||
|
||||
test('findEvents throws an error', async () => {
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertInstanceSummarySavedObject());
|
||||
eventLogClient.findEventsBySavedObject.mockRejectedValueOnce(new Error('OMG 2!'));
|
||||
eventLogClient.findEventsBySavedObjectIds.mockRejectedValueOnce(new Error('OMG 2!'));
|
||||
|
||||
// error eaten but logged
|
||||
await alertsClient.getAlertInstanceSummary({ id: '1' });
|
||||
|
|
|
@ -33,6 +33,7 @@ import {
|
|||
ILegacyClusterClient,
|
||||
StatusServiceSetup,
|
||||
ServiceStatus,
|
||||
SavedObjectsBulkGetObject,
|
||||
} from '../../../../src/core/server';
|
||||
|
||||
import {
|
||||
|
@ -370,7 +371,10 @@ export class AlertingPlugin {
|
|||
|
||||
this.eventLogService!.registerSavedObjectProvider('alert', (request) => {
|
||||
const client = getAlertsClientWithRequest(request);
|
||||
return (type: string, id: string) => client.get({ id });
|
||||
return (objects?: SavedObjectsBulkGetObject[]) =>
|
||||
objects
|
||||
? Promise.all(objects.map(async (objectItem) => await client.get({ id: objectItem.id })))
|
||||
: Promise.resolve([]);
|
||||
});
|
||||
|
||||
scheduleAlertingTelemetry(this.telemetryLogger, plugins.taskManager);
|
||||
|
|
|
@ -16,7 +16,7 @@ const createClusterClientMock = () => {
|
|||
createIndexTemplate: jest.fn(),
|
||||
doesAliasExist: jest.fn(),
|
||||
createIndex: jest.fn(),
|
||||
queryEventsBySavedObject: jest.fn(),
|
||||
queryEventsBySavedObjects: jest.fn(),
|
||||
shutdown: jest.fn(),
|
||||
};
|
||||
return mock;
|
||||
|
|
|
@ -327,11 +327,11 @@ describe('queryEventsBySavedObject', () => {
|
|||
total: { value: 0 },
|
||||
},
|
||||
});
|
||||
await clusterClientAdapter.queryEventsBySavedObject(
|
||||
await clusterClientAdapter.queryEventsBySavedObjects(
|
||||
'index-name',
|
||||
'namespace',
|
||||
'saved-object-type',
|
||||
'saved-object-id',
|
||||
['saved-object-id'],
|
||||
DEFAULT_OPTIONS
|
||||
);
|
||||
|
||||
|
@ -365,10 +365,10 @@ describe('queryEventsBySavedObject', () => {
|
|||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"kibana.saved_objects.id": Object {
|
||||
"value": "saved-object-id",
|
||||
},
|
||||
"terms": Object {
|
||||
"kibana.saved_objects.id": Array [
|
||||
"saved-object-id",
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
|
@ -406,11 +406,11 @@ describe('queryEventsBySavedObject', () => {
|
|||
total: { value: 0 },
|
||||
},
|
||||
});
|
||||
await clusterClientAdapter.queryEventsBySavedObject(
|
||||
await clusterClientAdapter.queryEventsBySavedObjects(
|
||||
'index-name',
|
||||
undefined,
|
||||
'saved-object-type',
|
||||
'saved-object-id',
|
||||
['saved-object-id'],
|
||||
DEFAULT_OPTIONS
|
||||
);
|
||||
|
||||
|
@ -444,10 +444,10 @@ describe('queryEventsBySavedObject', () => {
|
|||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"kibana.saved_objects.id": Object {
|
||||
"value": "saved-object-id",
|
||||
},
|
||||
"terms": Object {
|
||||
"kibana.saved_objects.id": Array [
|
||||
"saved-object-id",
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
|
@ -487,11 +487,11 @@ describe('queryEventsBySavedObject', () => {
|
|||
total: { value: 0 },
|
||||
},
|
||||
});
|
||||
await clusterClientAdapter.queryEventsBySavedObject(
|
||||
await clusterClientAdapter.queryEventsBySavedObjects(
|
||||
'index-name',
|
||||
'namespace',
|
||||
'saved-object-type',
|
||||
'saved-object-id',
|
||||
['saved-object-id'],
|
||||
{ ...DEFAULT_OPTIONS, sort_field: 'event.end', sort_order: 'desc' }
|
||||
);
|
||||
|
||||
|
@ -515,11 +515,11 @@ describe('queryEventsBySavedObject', () => {
|
|||
|
||||
const start = '2020-07-08T00:52:28.350Z';
|
||||
|
||||
await clusterClientAdapter.queryEventsBySavedObject(
|
||||
await clusterClientAdapter.queryEventsBySavedObjects(
|
||||
'index-name',
|
||||
'namespace',
|
||||
'saved-object-type',
|
||||
'saved-object-id',
|
||||
['saved-object-id'],
|
||||
{ ...DEFAULT_OPTIONS, start }
|
||||
);
|
||||
|
||||
|
@ -553,10 +553,10 @@ describe('queryEventsBySavedObject', () => {
|
|||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"kibana.saved_objects.id": Object {
|
||||
"value": "saved-object-id",
|
||||
},
|
||||
"terms": Object {
|
||||
"kibana.saved_objects.id": Array [
|
||||
"saved-object-id",
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
|
@ -605,11 +605,11 @@ describe('queryEventsBySavedObject', () => {
|
|||
const start = '2020-07-08T00:52:28.350Z';
|
||||
const end = '2020-07-08T00:00:00.000Z';
|
||||
|
||||
await clusterClientAdapter.queryEventsBySavedObject(
|
||||
await clusterClientAdapter.queryEventsBySavedObjects(
|
||||
'index-name',
|
||||
'namespace',
|
||||
'saved-object-type',
|
||||
'saved-object-id',
|
||||
['saved-object-id'],
|
||||
{ ...DEFAULT_OPTIONS, start, end }
|
||||
);
|
||||
|
||||
|
@ -643,10 +643,10 @@ describe('queryEventsBySavedObject', () => {
|
|||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"kibana.saved_objects.id": Object {
|
||||
"value": "saved-object-id",
|
||||
},
|
||||
"terms": Object {
|
||||
"kibana.saved_objects.id": Array [
|
||||
"saved-object-id",
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
|
|
|
@ -194,11 +194,11 @@ export class ClusterClientAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
public async queryEventsBySavedObject(
|
||||
public async queryEventsBySavedObjects(
|
||||
index: string,
|
||||
namespace: string | undefined,
|
||||
type: string,
|
||||
id: string,
|
||||
ids: string[],
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
{ page, per_page: perPage, start, end, sort_field, sort_order }: FindOptionsType
|
||||
): Promise<QueryEventsBySavedObjectResult> {
|
||||
|
@ -249,10 +249,9 @@ export class ClusterClientAdapter {
|
|||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'kibana.saved_objects.id': {
|
||||
value: id,
|
||||
},
|
||||
terms: {
|
||||
// default maximum of 65,536 terms, configurable by index.max_terms_count
|
||||
'kibana.saved_objects.id': ids,
|
||||
},
|
||||
},
|
||||
namespaceQuery,
|
||||
|
@ -298,7 +297,7 @@ export class ClusterClientAdapter {
|
|||
};
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`querying for Event Log by for type "${type}" and id "${id}" failed with: ${err.message}`
|
||||
`querying for Event Log by for type "${type}" and ids "${ids}" failed with: ${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { IEventLogClient } from './types';
|
|||
|
||||
const createEventLogClientMock = () => {
|
||||
const mock: jest.Mocked<IEventLogClient> = {
|
||||
findEventsBySavedObject: jest.fn(),
|
||||
findEventsBySavedObjectIds: jest.fn(),
|
||||
};
|
||||
return mock;
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ import { merge } from 'lodash';
|
|||
import moment from 'moment';
|
||||
|
||||
describe('EventLogStart', () => {
|
||||
describe('findEventsBySavedObject', () => {
|
||||
describe('findEventsBySavedObjectIds', () => {
|
||||
test('verifies that the user can access the specified saved object', async () => {
|
||||
const esContext = contextMock.create();
|
||||
const savedObjectGetter = jest.fn();
|
||||
|
@ -29,9 +29,9 @@ describe('EventLogStart', () => {
|
|||
references: [],
|
||||
});
|
||||
|
||||
await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id');
|
||||
await eventLogClient.findEventsBySavedObjectIds('saved-object-type', ['saved-object-id']);
|
||||
|
||||
expect(savedObjectGetter).toHaveBeenCalledWith('saved-object-type', 'saved-object-id');
|
||||
expect(savedObjectGetter).toHaveBeenCalledWith('saved-object-type', ['saved-object-id']);
|
||||
});
|
||||
|
||||
test('throws when the user doesnt have permission to access the specified saved object', async () => {
|
||||
|
@ -48,7 +48,7 @@ describe('EventLogStart', () => {
|
|||
savedObjectGetter.mockRejectedValue(new Error('Fail'));
|
||||
|
||||
expect(
|
||||
eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id')
|
||||
eventLogClient.findEventsBySavedObjectIds('saved-object-type', ['saved-object-id'])
|
||||
).rejects.toMatchInlineSnapshot(`[Error: Fail]`);
|
||||
});
|
||||
|
||||
|
@ -107,17 +107,17 @@ describe('EventLogStart', () => {
|
|||
total: expectedEvents.length,
|
||||
data: expectedEvents,
|
||||
};
|
||||
esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue(result);
|
||||
esContext.esAdapter.queryEventsBySavedObjects.mockResolvedValue(result);
|
||||
|
||||
expect(
|
||||
await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id')
|
||||
await eventLogClient.findEventsBySavedObjectIds('saved-object-type', ['saved-object-id'])
|
||||
).toEqual(result);
|
||||
|
||||
expect(esContext.esAdapter.queryEventsBySavedObject).toHaveBeenCalledWith(
|
||||
expect(esContext.esAdapter.queryEventsBySavedObjects).toHaveBeenCalledWith(
|
||||
esContext.esNames.indexPattern,
|
||||
undefined,
|
||||
'saved-object-type',
|
||||
'saved-object-id',
|
||||
['saved-object-id'],
|
||||
{
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
|
@ -182,23 +182,23 @@ describe('EventLogStart', () => {
|
|||
total: expectedEvents.length,
|
||||
data: expectedEvents,
|
||||
};
|
||||
esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue(result);
|
||||
esContext.esAdapter.queryEventsBySavedObjects.mockResolvedValue(result);
|
||||
|
||||
const start = moment().subtract(1, 'days').toISOString();
|
||||
const end = moment().add(1, 'days').toISOString();
|
||||
|
||||
expect(
|
||||
await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id', {
|
||||
await eventLogClient.findEventsBySavedObjectIds('saved-object-type', ['saved-object-id'], {
|
||||
start,
|
||||
end,
|
||||
})
|
||||
).toEqual(result);
|
||||
|
||||
expect(esContext.esAdapter.queryEventsBySavedObject).toHaveBeenCalledWith(
|
||||
expect(esContext.esAdapter.queryEventsBySavedObjects).toHaveBeenCalledWith(
|
||||
esContext.esNames.indexPattern,
|
||||
undefined,
|
||||
'saved-object-type',
|
||||
'saved-object-id',
|
||||
['saved-object-id'],
|
||||
{
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
|
@ -228,7 +228,7 @@ describe('EventLogStart', () => {
|
|||
references: [],
|
||||
});
|
||||
|
||||
esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue({
|
||||
esContext.esAdapter.queryEventsBySavedObjects.mockResolvedValue({
|
||||
page: 0,
|
||||
per_page: 0,
|
||||
total: 0,
|
||||
|
@ -236,7 +236,7 @@ describe('EventLogStart', () => {
|
|||
});
|
||||
|
||||
expect(
|
||||
eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id', {
|
||||
eventLogClient.findEventsBySavedObjectIds('saved-object-type', ['saved-object-id'], {
|
||||
start: 'not a date string',
|
||||
})
|
||||
).rejects.toMatchInlineSnapshot(`[Error: [start]: Invalid Date]`);
|
||||
|
@ -260,7 +260,7 @@ describe('EventLogStart', () => {
|
|||
references: [],
|
||||
});
|
||||
|
||||
esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue({
|
||||
esContext.esAdapter.queryEventsBySavedObjects.mockResolvedValue({
|
||||
page: 0,
|
||||
per_page: 0,
|
||||
total: 0,
|
||||
|
@ -268,7 +268,7 @@ describe('EventLogStart', () => {
|
|||
});
|
||||
|
||||
expect(
|
||||
eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id', {
|
||||
eventLogClient.findEventsBySavedObjectIds('saved-object-type', ['saved-object-id'], {
|
||||
end: 'not a date string',
|
||||
})
|
||||
).rejects.toMatchInlineSnapshot(`[Error: [end]: Invalid Date]`);
|
||||
|
|
|
@ -12,7 +12,7 @@ import { SpacesServiceStart } from '../../spaces/server';
|
|||
import { EsContext } from './es';
|
||||
import { IEventLogClient } from './types';
|
||||
import { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter';
|
||||
import { SavedObjectGetter } from './saved_object_provider_registry';
|
||||
import { SavedObjectBulkGetterResult } from './saved_object_provider_registry';
|
||||
export type PluginClusterClient = Pick<LegacyClusterClient, 'callAsInternalUser' | 'asScoped'>;
|
||||
export type AdminClusterClient$ = Observable<PluginClusterClient>;
|
||||
|
||||
|
@ -59,7 +59,7 @@ export type FindOptionsType = Pick<
|
|||
|
||||
interface EventLogServiceCtorParams {
|
||||
esContext: EsContext;
|
||||
savedObjectGetter: SavedObjectGetter;
|
||||
savedObjectGetter: SavedObjectBulkGetterResult;
|
||||
spacesService?: SpacesServiceStart;
|
||||
request: KibanaRequest;
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ interface EventLogServiceCtorParams {
|
|||
// note that clusterClient may be null, indicating we can't write to ES
|
||||
export class EventLogClient implements IEventLogClient {
|
||||
private esContext: EsContext;
|
||||
private savedObjectGetter: SavedObjectGetter;
|
||||
private savedObjectGetter: SavedObjectBulkGetterResult;
|
||||
private spacesService?: SpacesServiceStart;
|
||||
private request: KibanaRequest;
|
||||
|
||||
|
@ -78,9 +78,9 @@ export class EventLogClient implements IEventLogClient {
|
|||
this.request = request;
|
||||
}
|
||||
|
||||
async findEventsBySavedObject(
|
||||
async findEventsBySavedObjectIds(
|
||||
type: string,
|
||||
id: string,
|
||||
ids: string[],
|
||||
options?: Partial<FindOptionsType>
|
||||
): Promise<QueryEventsBySavedObjectResult> {
|
||||
const findOptions = findOptionsSchema.validate(options ?? {});
|
||||
|
@ -88,14 +88,14 @@ export class EventLogClient implements IEventLogClient {
|
|||
const space = await this.spacesService?.getActiveSpace(this.request);
|
||||
const namespace = space && this.spacesService?.spaceIdToNamespace(space.id);
|
||||
|
||||
// verify the user has the required permissions to view this saved object
|
||||
await this.savedObjectGetter(type, id);
|
||||
// verify the user has the required permissions to view this saved objects
|
||||
await this.savedObjectGetter(type, ids);
|
||||
|
||||
return await this.esContext.esAdapter.queryEventsBySavedObject(
|
||||
return await this.esContext.esAdapter.queryEventsBySavedObjects(
|
||||
this.esContext.esNames.indexPattern,
|
||||
namespace,
|
||||
type,
|
||||
id,
|
||||
ids,
|
||||
findOptions
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import { EventLogService } from './event_log_service';
|
|||
import { createEsContext, EsContext } from './es';
|
||||
import { EventLogClientService } from './event_log_start_service';
|
||||
import { SavedObjectProviderRegistry } from './saved_object_provider_registry';
|
||||
import { findByIdsRoute } from './routes/find_by_ids';
|
||||
|
||||
export type PluginClusterClient = Pick<LegacyClusterClient, 'callAsInternalUser' | 'asScoped'>;
|
||||
|
||||
|
@ -99,6 +100,7 @@ export class Plugin implements CorePlugin<IEventLogService, IEventLogClientServi
|
|||
const router = core.http.createRouter();
|
||||
// Register routes
|
||||
findRoute(router, this.systemLogger);
|
||||
findByIdsRoute(router, this.systemLogger);
|
||||
|
||||
return this.eventLogService;
|
||||
}
|
||||
|
@ -135,7 +137,7 @@ export class Plugin implements CorePlugin<IEventLogService, IEventLogClientServi
|
|||
|
||||
this.savedObjectProviderRegistry.registerDefaultProvider((request) => {
|
||||
const client = core.savedObjects.getScopedClient(request);
|
||||
return client.get.bind(client);
|
||||
return client.bulkGet.bind(client);
|
||||
});
|
||||
|
||||
this.eventLogClientService = new EventLogClientService({
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('find', () => {
|
|||
total: events.length,
|
||||
data: events,
|
||||
};
|
||||
eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(result);
|
||||
eventLogClient.findEventsBySavedObjectIds.mockResolvedValueOnce(result);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
eventLogClient,
|
||||
|
@ -46,11 +46,11 @@ describe('find', () => {
|
|||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1);
|
||||
expect(eventLogClient.findEventsBySavedObjectIds).toHaveBeenCalledTimes(1);
|
||||
|
||||
const [type, id] = eventLogClient.findEventsBySavedObject.mock.calls[0];
|
||||
const [type, ids] = eventLogClient.findEventsBySavedObjectIds.mock.calls[0];
|
||||
expect(type).toEqual(`action`);
|
||||
expect(id).toEqual(`1`);
|
||||
expect(ids).toEqual(['1']);
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: result,
|
||||
|
@ -63,7 +63,7 @@ describe('find', () => {
|
|||
findRoute(router, systemLogger);
|
||||
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
eventLogClient.findEventsBySavedObject.mockResolvedValueOnce({
|
||||
eventLogClient.findEventsBySavedObjectIds.mockResolvedValueOnce({
|
||||
page: 0,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
|
@ -81,11 +81,11 @@ describe('find', () => {
|
|||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1);
|
||||
expect(eventLogClient.findEventsBySavedObjectIds).toHaveBeenCalledTimes(1);
|
||||
|
||||
const [type, id, options] = eventLogClient.findEventsBySavedObject.mock.calls[0];
|
||||
const [type, ids, options] = eventLogClient.findEventsBySavedObjectIds.mock.calls[0];
|
||||
expect(type).toEqual(`action`);
|
||||
expect(id).toEqual(`1`);
|
||||
expect(ids).toEqual(['1']);
|
||||
expect(options).toMatchObject({});
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
|
@ -104,7 +104,7 @@ describe('find', () => {
|
|||
findRoute(router, systemLogger);
|
||||
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
eventLogClient.findEventsBySavedObject.mockRejectedValueOnce(new Error('oof!'));
|
||||
eventLogClient.findEventsBySavedObjectIds.mockRejectedValueOnce(new Error('oof!'));
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
eventLogClient,
|
||||
|
@ -119,7 +119,7 @@ describe('find', () => {
|
|||
|
||||
expect(systemLogger.debug).toHaveBeenCalledTimes(1);
|
||||
expect(systemLogger.debug).toHaveBeenCalledWith(
|
||||
'error calling eventLog findEventsBySavedObject(action, 1, {"page":3,"per_page":10}): oof!'
|
||||
'error calling eventLog findEventsBySavedObjectIds(action, [1], {"page":3,"per_page":10}): oof!'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -47,10 +47,10 @@ export const findRoute = (router: IRouter, systemLogger: Logger) => {
|
|||
|
||||
try {
|
||||
return res.ok({
|
||||
body: await eventLogClient.findEventsBySavedObject(type, id, query),
|
||||
body: await eventLogClient.findEventsBySavedObjectIds(type, [id], query),
|
||||
});
|
||||
} catch (err) {
|
||||
const call = `findEventsBySavedObject(${type}, ${id}, ${JSON.stringify(query)})`;
|
||||
const call = `findEventsBySavedObjectIds(${type}, [${id}], ${JSON.stringify(query)})`;
|
||||
systemLogger.debug(`error calling eventLog ${call}: ${err.message}`);
|
||||
return res.notFound();
|
||||
}
|
||||
|
|
128
x-pack/plugins/event_log/server/routes/find_by_ids.test.ts
Normal file
128
x-pack/plugins/event_log/server/routes/find_by_ids.test.ts
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { httpServiceMock } from 'src/core/server/mocks';
|
||||
import { mockHandlerArguments, fakeEvent } from './_mock_handler_arguments';
|
||||
import { eventLogClientMock } from '../event_log_client.mock';
|
||||
import { loggingSystemMock } from 'src/core/server/mocks';
|
||||
import { findByIdsRoute } from './find_by_ids';
|
||||
|
||||
const eventLogClient = eventLogClientMock.create();
|
||||
const systemLogger = loggingSystemMock.createLogger();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('find_by_ids', () => {
|
||||
it('finds events with proper parameters', async () => {
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
findByIdsRoute(router, systemLogger);
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/event_log/{type}/_find"`);
|
||||
|
||||
const events = [fakeEvent(), fakeEvent()];
|
||||
const result = {
|
||||
page: 0,
|
||||
per_page: 10,
|
||||
total: events.length,
|
||||
data: events,
|
||||
};
|
||||
eventLogClient.findEventsBySavedObjectIds.mockResolvedValueOnce(result);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
eventLogClient,
|
||||
{
|
||||
params: { type: 'action' },
|
||||
body: { ids: ['1'] },
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(eventLogClient.findEventsBySavedObjectIds).toHaveBeenCalledTimes(1);
|
||||
|
||||
const [type, ids] = eventLogClient.findEventsBySavedObjectIds.mock.calls[0];
|
||||
expect(type).toEqual(`action`);
|
||||
expect(ids).toEqual(['1']);
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: result,
|
||||
});
|
||||
});
|
||||
|
||||
it('supports optional pagination parameters', async () => {
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
findByIdsRoute(router, systemLogger);
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
eventLogClient.findEventsBySavedObjectIds.mockResolvedValueOnce({
|
||||
page: 0,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
data: [],
|
||||
});
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
eventLogClient,
|
||||
{
|
||||
params: { type: 'action' },
|
||||
body: { ids: ['1'] },
|
||||
query: { page: 3, per_page: 10 },
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(eventLogClient.findEventsBySavedObjectIds).toHaveBeenCalledTimes(1);
|
||||
|
||||
const [type, id, options] = eventLogClient.findEventsBySavedObjectIds.mock.calls[0];
|
||||
expect(type).toEqual(`action`);
|
||||
expect(id).toEqual(['1']);
|
||||
expect(options).toMatchObject({});
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: {
|
||||
page: 0,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
data: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('logs a warning when the query throws an error', async () => {
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
findByIdsRoute(router, systemLogger);
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
eventLogClient.findEventsBySavedObjectIds.mockRejectedValueOnce(new Error('oof!'));
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
eventLogClient,
|
||||
{
|
||||
params: { type: 'action' },
|
||||
body: { ids: ['1'] },
|
||||
query: { page: 3, per_page: 10 },
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(systemLogger.debug).toHaveBeenCalledTimes(1);
|
||||
expect(systemLogger.debug).toHaveBeenCalledWith(
|
||||
'error calling eventLog findEventsBySavedObjectIds(action, [1], {"page":3,"per_page":10}): oof!'
|
||||
);
|
||||
});
|
||||
});
|
64
x-pack/plugins/event_log/server/routes/find_by_ids.ts
Normal file
64
x-pack/plugins/event_log/server/routes/find_by_ids.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
import {
|
||||
IRouter,
|
||||
RequestHandlerContext,
|
||||
KibanaRequest,
|
||||
IKibanaResponse,
|
||||
KibanaResponseFactory,
|
||||
Logger,
|
||||
} from 'src/core/server';
|
||||
|
||||
import { BASE_EVENT_LOG_API_PATH } from '../../common';
|
||||
import { findOptionsSchema, FindOptionsType } from '../event_log_client';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
type: schema.string(),
|
||||
});
|
||||
|
||||
const bodySchema = schema.object({
|
||||
ids: schema.arrayOf(schema.string(), { defaultValue: [] }),
|
||||
});
|
||||
|
||||
export const findByIdsRoute = (router: IRouter, systemLogger: Logger) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${BASE_EVENT_LOG_API_PATH}/{type}/_find`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
query: findOptionsSchema,
|
||||
body: bodySchema,
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(async function (
|
||||
context: RequestHandlerContext,
|
||||
req: KibanaRequest<TypeOf<typeof paramSchema>, FindOptionsType, TypeOf<typeof bodySchema>>,
|
||||
res: KibanaResponseFactory
|
||||
): Promise<IKibanaResponse> {
|
||||
if (!context.eventLog) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for eventLog' });
|
||||
}
|
||||
const eventLogClient = context.eventLog.getEventLogClient();
|
||||
const {
|
||||
params: { type },
|
||||
body: { ids },
|
||||
query,
|
||||
} = req;
|
||||
|
||||
try {
|
||||
return res.ok({
|
||||
body: await eventLogClient.findEventsBySavedObjectIds(type, ids, query),
|
||||
});
|
||||
} catch (err) {
|
||||
const call = `findEventsBySavedObjectIds(${type}, [${ids}], ${JSON.stringify(query)})`;
|
||||
systemLogger.debug(`error calling eventLog ${call}: ${err.message}`);
|
||||
return res.notFound();
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
|
@ -45,10 +45,10 @@ describe('SavedObjectProviderRegistry', () => {
|
|||
|
||||
getter.mockResolvedValue(alert);
|
||||
|
||||
expect(await registry.getProvidersClient(request)('alert', alert.id)).toMatchObject(alert);
|
||||
expect(await registry.getProvidersClient(request)('alert', [alert.id])).toMatchObject(alert);
|
||||
|
||||
expect(provider).toHaveBeenCalledWith(request);
|
||||
expect(getter).toHaveBeenCalledWith('alert', alert.id);
|
||||
expect(getter).toHaveBeenCalledWith([{ id: alert.id, type: 'alert' }]);
|
||||
});
|
||||
|
||||
test('should get SavedObject using the default provider for unregistered types', async () => {
|
||||
|
@ -70,9 +70,11 @@ describe('SavedObjectProviderRegistry', () => {
|
|||
defaultProvider.mockReturnValue(getter);
|
||||
getter.mockResolvedValue(action);
|
||||
|
||||
expect(await registry.getProvidersClient(request)('action', action.id)).toMatchObject(action);
|
||||
expect(await registry.getProvidersClient(request)('action', [action.id])).toMatchObject(
|
||||
action
|
||||
);
|
||||
|
||||
expect(getter).toHaveBeenCalledWith('action', action.id);
|
||||
expect(getter).toHaveBeenCalledWith([{ id: action.id, type: 'action' }]);
|
||||
expect(defaultProvider).toHaveBeenCalledWith(request);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,7 +13,14 @@ import { pipe } from 'fp-ts/lib/pipeable';
|
|||
export type SavedObjectGetter = (
|
||||
...params: Parameters<SavedObjectsClientContract['get']>
|
||||
) => Promise<unknown>;
|
||||
export type SavedObjectProvider = (request: KibanaRequest) => SavedObjectGetter;
|
||||
|
||||
export type SavedObjectBulkGetter = (
|
||||
...params: Parameters<SavedObjectsClientContract['bulkGet']>
|
||||
) => Promise<unknown>;
|
||||
|
||||
export type SavedObjectBulkGetterResult = (type: string, ids: string[]) => Promise<unknown>;
|
||||
|
||||
export type SavedObjectProvider = (request: KibanaRequest) => SavedObjectBulkGetter;
|
||||
|
||||
export class SavedObjectProviderRegistry {
|
||||
private providers = new Map<string, SavedObjectProvider>();
|
||||
|
@ -34,7 +41,7 @@ export class SavedObjectProviderRegistry {
|
|||
this.providers.set(type, provider);
|
||||
}
|
||||
|
||||
public getProvidersClient(request: KibanaRequest): SavedObjectGetter {
|
||||
public getProvidersClient(request: KibanaRequest): SavedObjectBulkGetterResult {
|
||||
if (!this.defaultProvider) {
|
||||
throw new Error(
|
||||
i18n.translate(
|
||||
|
@ -49,9 +56,13 @@ export class SavedObjectProviderRegistry {
|
|||
// `scopedProviders` is a cache of providers which are scoped t othe current request.
|
||||
// The client will only instantiate a provider on-demand and it will cache each
|
||||
// one to enable the request to reuse each provider.
|
||||
const scopedProviders = new Map<string, SavedObjectGetter>();
|
||||
|
||||
// would be nice to have a simple version support in API:
|
||||
// curl -X GET "localhost:9200/my-index-000001/_mget?pretty" -H 'Content-Type: application/json' -d' { "ids" : ["1", "2"] } '
|
||||
const scopedProviders = new Map<string, SavedObjectBulkGetter>();
|
||||
const defaultGetter = this.defaultProvider(request);
|
||||
return (type: string, id: string) => {
|
||||
return (type: string, ids: string[]) => {
|
||||
const objects = ids.map((id: string) => ({ type, id }));
|
||||
const getter = pipe(
|
||||
fromNullable(scopedProviders.get(type)),
|
||||
getOrElse(() => {
|
||||
|
@ -62,7 +73,7 @@ export class SavedObjectProviderRegistry {
|
|||
return client;
|
||||
})
|
||||
);
|
||||
return getter(type, id);
|
||||
return getter(objects);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,9 +51,9 @@ export interface IEventLogClientService {
|
|||
}
|
||||
|
||||
export interface IEventLogClient {
|
||||
findEventsBySavedObject(
|
||||
findEventsBySavedObjectIds(
|
||||
type: string,
|
||||
id: string,
|
||||
ids: string[],
|
||||
options?: Partial<FindOptionsType>
|
||||
): Promise<QueryEventsBySavedObjectResult>;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue