Skip to content

Commit

Permalink
[7.x] Geo containment alert sparsity handling: preserve active status…
Browse files Browse the repository at this point in the history
… for non-updated alerts (elastic#85364) (elastic#85573)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
Aaron Caldwell and kibanamachine authored Dec 10, 2020
1 parent 19fdb82 commit e3c2a89
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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()]);
});
});
});

0 comments on commit e3c2a89

Please sign in to comment.