Geo containment alert sparsity handling: preserve active status for non-updated alerts (#85364)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Aaron Caldwell 2020-12-10 07:27:01 -07:00 committed by GitHub
parent 3177f47451
commit ad922d0f59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 165 additions and 19 deletions

View file

@ -11,7 +11,7 @@ import { executeEsQueryFactory, getShapesFilters, OTHER_CATEGORY } from './es_qu
import { AlertServices, AlertTypeState } from '../../../../alerts/server';
import { ActionGroupId, GEO_CONTAINMENT_ID, GeoContainmentParams } from './alert_type';
interface LatestEntityLocation {
export interface LatestEntityLocation {
location: number[];
shapeLocationId: string;
dateInShape: string | null;
@ -94,6 +94,40 @@ function getOffsetTime(delayOffsetWithUnits: string, oldTime: Date): Date {
return adjustedDate;
}
export function getActiveEntriesAndGenerateAlerts(
prevLocationMap: Record<string, LatestEntityLocation>,
currLocationMap: Map<string, LatestEntityLocation>,
alertInstanceFactory: (
x: string
) => { scheduleActions: (x: string, y: Record<string, unknown>) => void },
shapesIdsNamesMap: Record<string, unknown>,
currIntervalEndTime: Date
) {
const allActiveEntriesMap: Map<string, LatestEntityLocation> = new Map([
...Object.entries(prevLocationMap || {}),
...currLocationMap,
]);
allActiveEntriesMap.forEach(({ location, shapeLocationId, dateInShape, docId }, entityName) => {
const containingBoundaryName = shapesIdsNamesMap[shapeLocationId] || shapeLocationId;
const context = {
entityId: entityName,
entityDateTime: dateInShape ? new Date(dateInShape).toISOString() : null,
entityDocumentId: docId,
detectionDateTime: new Date(currIntervalEndTime).toISOString(),
entityLocation: `POINT (${location[0]} ${location[1]})`,
containingBoundaryId: shapeLocationId,
containingBoundaryName,
};
const alertInstanceId = `${entityName}-${containingBoundaryName}`;
if (shapeLocationId === OTHER_CATEGORY) {
allActiveEntriesMap.delete(entityName);
} else {
alertInstanceFactory(alertInstanceId).scheduleActions(ActionGroupId, context);
}
});
return allActiveEntriesMap;
}
export const getGeoContainmentExecutor = (log: Logger) =>
async function ({
previousStartedAt,
@ -153,26 +187,17 @@ export const getGeoContainmentExecutor = (log: Logger) =>
params.geoField
);
// Cycle through new alert statuses and set active
currLocationMap.forEach(({ location, shapeLocationId, dateInShape, docId }, entityName) => {
const containingBoundaryName = shapesIdsNamesMap[shapeLocationId] || shapeLocationId;
const context = {
entityId: entityName,
entityDateTime: new Date(currIntervalEndTime).toISOString(),
entityDocumentId: docId,
detectionDateTime: new Date(currIntervalEndTime).toISOString(),
entityLocation: `POINT (${location[0]} ${location[1]})`,
containingBoundaryId: shapeLocationId,
containingBoundaryName,
};
const alertInstanceId = `${entityName}-${containingBoundaryName}`;
if (shapeLocationId !== OTHER_CATEGORY) {
services.alertInstanceFactory(alertInstanceId).scheduleActions(ActionGroupId, context);
}
});
const allActiveEntriesMap = getActiveEntriesAndGenerateAlerts(
state.prevLocationMap as Record<string, LatestEntityLocation>,
currLocationMap,
services.alertInstanceFactory,
shapesIdsNamesMap,
currIntervalEndTime
);
return {
shapesFilters,
shapesIdsNamesMap,
prevLocationMap: Object.fromEntries(allActiveEntriesMap),
};
};

View file

@ -6,8 +6,9 @@
import sampleJsonResponse from './es_sample_response.json';
import sampleJsonResponseWithNesting from './es_sample_response_with_nesting.json';
import { transformResults } from '../geo_containment';
import { getActiveEntriesAndGenerateAlerts, transformResults } from '../geo_containment';
import { SearchResponse } from 'elasticsearch';
import { OTHER_CATEGORY } from '../es_query_builder';
describe('geo_containment', () => {
describe('transformResults', () => {
@ -116,4 +117,124 @@ describe('geo_containment', () => {
expect(transformedResults).toEqual(new Map());
});
});
describe('getActiveEntriesAndGenerateAlerts', () => {
const testAlertActionArr: unknown[] = [];
afterEach(() => {
jest.clearAllMocks();
testAlertActionArr.length = 0;
});
const currLocationMap = new Map([
[
'a',
{
location: [0, 0],
shapeLocationId: '123',
dateInShape: 'Wed Dec 09 2020 14:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId1',
},
],
[
'b',
{
location: [0, 0],
shapeLocationId: '456',
dateInShape: 'Wed Dec 09 2020 15:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId2',
},
],
[
'c',
{
location: [0, 0],
shapeLocationId: '789',
dateInShape: 'Wed Dec 09 2020 16:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId3',
},
],
]);
const emptyShapesIdsNamesMap = {};
const scheduleActions = jest.fn((alertInstance: string, context: Record<string, unknown>) => {
testAlertActionArr.push(context.entityId);
});
const alertInstanceFactory = (x: string) => ({ scheduleActions });
const currentDateTime = new Date();
it('should use currently active entities if no older entity entries', () => {
const emptyPrevLocationMap = {};
const allActiveEntriesMap = getActiveEntriesAndGenerateAlerts(
emptyPrevLocationMap,
currLocationMap,
alertInstanceFactory,
emptyShapesIdsNamesMap,
currentDateTime
);
expect(allActiveEntriesMap).toEqual(currLocationMap);
expect(scheduleActions.mock.calls.length).toEqual(allActiveEntriesMap.size);
expect(testAlertActionArr).toEqual([...allActiveEntriesMap.keys()]);
});
it('should overwrite older identical entity entries', () => {
const prevLocationMapWithIdenticalEntityEntry = {
a: {
location: [0, 0],
shapeLocationId: '999',
dateInShape: 'Wed Dec 09 2020 12:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId7',
},
};
const allActiveEntriesMap = getActiveEntriesAndGenerateAlerts(
prevLocationMapWithIdenticalEntityEntry,
currLocationMap,
alertInstanceFactory,
emptyShapesIdsNamesMap,
currentDateTime
);
expect(allActiveEntriesMap).toEqual(currLocationMap);
expect(scheduleActions.mock.calls.length).toEqual(allActiveEntriesMap.size);
expect(testAlertActionArr).toEqual([...allActiveEntriesMap.keys()]);
});
it('should preserve older non-identical entity entries', () => {
const prevLocationMapWithNonIdenticalEntityEntry = {
d: {
location: [0, 0],
shapeLocationId: '999',
dateInShape: 'Wed Dec 09 2020 12:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId7',
},
};
const allActiveEntriesMap = getActiveEntriesAndGenerateAlerts(
prevLocationMapWithNonIdenticalEntityEntry,
currLocationMap,
alertInstanceFactory,
emptyShapesIdsNamesMap,
currentDateTime
);
expect(allActiveEntriesMap).not.toEqual(currLocationMap);
expect(allActiveEntriesMap.has('d')).toBeTruthy();
expect(scheduleActions.mock.calls.length).toEqual(allActiveEntriesMap.size);
expect(testAlertActionArr).toEqual([...allActiveEntriesMap.keys()]);
});
it('should remove "other" entries and schedule the expected number of actions', () => {
const emptyPrevLocationMap = {};
const currLocationMapWithOther = new Map(currLocationMap).set('d', {
location: [0, 0],
shapeLocationId: OTHER_CATEGORY,
dateInShape: 'Wed Dec 09 2020 14:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId1',
});
expect(currLocationMapWithOther).not.toEqual(currLocationMap);
const allActiveEntriesMap = getActiveEntriesAndGenerateAlerts(
emptyPrevLocationMap,
currLocationMapWithOther,
alertInstanceFactory,
emptyShapesIdsNamesMap,
currentDateTime
);
expect(allActiveEntriesMap).toEqual(currLocationMap);
expect(scheduleActions.mock.calls.length).toEqual(allActiveEntriesMap.size);
expect(testAlertActionArr).toEqual([...allActiveEntriesMap.keys()]);
});
});
});