Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for geo_shape fields as the entity geospatial field when creating tracking containment alerts #164100

Merged
merged 27 commits into from Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cc4a932
Add support for geo_shape fields as the entity geospatial field when …
nreese Aug 16, 2023
204bc9a
fix jest tests
nreese Aug 16, 2023
a9e9cd0
tslint
nreese Aug 16, 2023
234e630
replace lodash.get with optional chaining
nreese Aug 16, 2023
f3c016b
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Aug 16, 2023
1c08374
refactor transformResults to only iterator over results once and use …
nreese Aug 16, 2023
ae063fb
backwards comp and tslint
nreese Aug 16, 2023
c5f3b6d
add test case for backward compatibility
nreese Aug 16, 2023
86edb8d
eslint
nreese Aug 16, 2023
830b6ec
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Aug 16, 2023
7b499d4
eslint
nreese Aug 16, 2023
164193d
fix executor test
nreese Aug 17, 2023
ea10b04
Merge branch 'main' into issue_163996
kibanamachine Aug 17, 2023
a1d263a
update docs and UI
nreese Aug 17, 2023
668698f
update snapshots
nreese Aug 17, 2023
2f7bbb9
remove TODOs about other parts of code that can be cleaned up later
nreese Aug 17, 2023
98d14f2
tslint
nreese Aug 17, 2023
8d17967
remove obsolute copy from docs
nreese Aug 17, 2023
cc572ae
Merge branch 'main' into issue_163996
kibanamachine Aug 21, 2023
d37f7ef
Merge branch 'main' into issue_163996
kibanamachine Aug 21, 2023
1df60fc
Merge branch 'main' into issue_163996
kibanamachine Aug 21, 2023
e761398
Merge branch 'main' into issue_163996
kibanamachine Aug 22, 2023
4ecf124
ZDT fix
nreese Aug 22, 2023
b98c997
tslint
nreese Aug 22, 2023
ed4c25b
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Aug 22, 2023
46be395
fix jest test
nreese Aug 23, 2023
c987849
Merge branch 'main' into issue_163996
kibanamachine Aug 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 2 additions & 7 deletions docs/user/alerting/rule-types/geo-rule-types.asciidoc
Expand Up @@ -10,7 +10,7 @@ In the event that an entity is contained within a boundary, an alert may be gene
=== Requirements
To create a tracking containment rule, the following requirements must be present:

- *Tracks index or data view*: An index containing a `geo_point` field, `date` field,
- *Tracks index or data view*: An index containing a `geo_point` or `geo_shape` field, `date` field,
and some form of entity identifier. An entity identifier is a `keyword` or `number`
field that consistently identifies the entity to be tracked. The data in this index should be dynamically
updating so that there are entity movements to alert upon.
Expand All @@ -36,12 +36,7 @@ as well as two Kuery bars used to provide additional filtering context for each
image::user/alerting/images/alert-types-tracking-containment-conditions.png[Define the condition to detect,width=75%]
// NOTE: This is an autogenerated screenshot. Do not edit it directly.

Index (entity):: This clause requires an *index or data view*, a *time field* that will be used for the *time window*, and a *`geo_point` field* for tracking.
When entity:: This clause specifies which crossing option to track. The values
*Entered*, *Exited*, and *Crossed* can be selected to indicate which crossing conditions
should trigger a rule. *Entered* alerts on entry into a boundary, *Exited* alerts on exit
from a boundary, and *Crossed* alerts on all boundary crossings whether they be entrances
or exits.
Index (entity):: This clause requires an *index or data view*, a *time field* that will be used for the *time window*, and a *`geo_point` or `geo_shape` field* for tracking.
Index (Boundary):: This clause requires an *index or data view*, a *`geo_shape` field*
identifying boundaries, and an optional *Human-readable boundary name* for better alerting
messages.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Expand Up @@ -161,7 +161,7 @@ export const EntityIndexExpression: FunctionComponent<Props> = ({
isInvalid={isInvalid}
value={indexPattern.title}
defaultValue={i18n.translate('xpack.stackAlerts.geoContainment.entityIndexSelect', {
defaultMessage: 'Select a data view and geo point field',
defaultMessage: 'Select a data view and geospatial field',
})}
popoverContent={indexPopover}
expressionDescription={i18n.translate('xpack.stackAlerts.geoContainment.entityIndexLabel', {
Expand Down
Expand Up @@ -4,7 +4,7 @@ There are several steps required to set up geo containment alerts for testing in
that allows you to view triggered alerts as they happen. These instructions outline
how to load test data, but really these steps can be used to load any data for geo
containment alerts so long as you have the following data:
- An index containing a`geo_point` field and a `date` field. This data is presumed to
- An index containing a`geo_point` or `geo_shape` field and a `date` field. This data is presumed to
be dynamic (updated).
- An index containing `geo_shape` data, such as boundary data, bounding box data, etc.
This data is presumed to be static (not updated). Shape data matching the query is
Expand Down
Expand Up @@ -23,6 +23,5 @@ export interface GeoContainmentAlertParams extends RuleTypeParams {
boundaryIndexQuery?: Query;
}

// Will eventually include 'geo_shape'
export const ES_GEO_FIELD_TYPES = ['geo_point'];
export const ES_GEO_FIELD_TYPES = ['geo_point', 'geo_shape'];
export const ES_GEO_SHAPE_TYPES = ['geo_shape'];
Expand Up @@ -220,15 +220,17 @@ describe('getGeoContainmentExecutor', () => {
{
dateInShape: '2021-04-28T16:56:11.923Z',
docId: 'ZVBoGXkBsFLYN2Tj1wmV',
location: [-73.99018926545978, 40.751759740523994],
location: [0, 0],
locationWkt: 'POINT (-73.99018926545978 40.751759740523994)',
shapeLocationId: 'kFATGXkBsFLYN2Tj6AAk',
},
],
'1': [
{
dateInShape: '2021-04-28T16:56:11.923Z',
docId: 'ZlBoGXkBsFLYN2Tj1wmV',
location: [-73.99561604484916, 40.75449890457094],
location: [0, 0],
locationWkt: 'POINT (-73.99561604484916 40.75449890457094)',
shapeLocationId: 'kFATGXkBsFLYN2Tj6AAk',
},
],
Expand Down
Expand Up @@ -9,6 +9,31 @@ import { getContainedAlertContext, getRecoveredAlertContext } from './alert_cont
import { OTHER_CATEGORY } from '../constants';

test('getContainedAlertContext', () => {
expect(
getContainedAlertContext({
entityName: 'entity1',
containment: {
location: [0, 0],
locationWkt: 'POINT (100 0)',
shapeLocationId: 'boundary1Id',
dateInShape: '2022-06-21T16:56:11.923Z',
docId: 'docId',
},
shapesIdsNamesMap: { boundary1Id: 'boundary1Name' },
windowEnd: new Date('2022-06-21T17:00:00.000Z'),
})
).toEqual({
containingBoundaryId: 'boundary1Id',
containingBoundaryName: 'boundary1Name',
detectionDateTime: '2022-06-21T17:00:00.000Z',
entityDateTime: '2022-06-21T16:56:11.923Z',
entityDocumentId: 'docId',
entityId: 'entity1',
entityLocation: 'POINT (100 0)',
});
});

test('getContainedAlertContext for backwards compatible number[] location format', () => {
expect(
getContainedAlertContext({
entityName: 'entity1',
Expand Down Expand Up @@ -37,7 +62,8 @@ describe('getRecoveredAlertContext', () => {
const activeEntities = new Map();
activeEntities.set('entity1', [
{
location: [100, 0],
location: [0, 0],
locationWkt: 'POINT (100 0)',
shapeLocationId: 'boundary1Id',
dateInShape: '2022-06-21T16:56:11.923Z',
docId: 'docId',
Expand All @@ -63,7 +89,8 @@ describe('getRecoveredAlertContext', () => {
const inactiveEntities = new Map();
inactiveEntities.set('entity1', [
{
location: [100, 0],
location: [0, 0],
locationWkt: 'POINT (100 0)',
shapeLocationId: OTHER_CATEGORY,
dateInShape: '2022-06-21T16:56:11.923Z',
docId: 'docId',
Expand Down
Expand Up @@ -50,7 +50,10 @@ function getAlertContext({
entityId: entityName,
entityDateTime: containment.dateInShape || null,
entityDocumentId: containment.docId,
entityLocation: `POINT (${containment.location[0]} ${containment.location[1]})`,
entityLocation:
containment.locationWkt !== undefined
? containment.locationWkt
: `POINT (${containment.location[0]} ${containment.location[1]})`,
detectionDateTime: new Date(windowEnd).toISOString(),
};
if (!isRecovered) {
Expand Down
Expand Up @@ -63,13 +63,16 @@ export async function executeEsQuery(
},
},
],
docvalue_fields: [
fields: [
nickpeihl marked this conversation as resolved.
Show resolved Hide resolved
entity,
{
field: dateField,
format: 'strict_date_optional_time',
},
geoField,
{
field: geoField,
format: 'wkt',
},
],
_source: false,
},
Expand Down
Expand Up @@ -54,6 +54,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
[
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: '123',
dateInShape: 'Wed Dec 09 2020 14:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId1',
Expand All @@ -65,6 +66,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
[
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: '456',
dateInShape: 'Wed Dec 16 2020 15:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId2',
Expand All @@ -76,6 +78,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
[
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: '789',
dateInShape: 'Wed Dec 23 2020 16:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId3',
Expand Down Expand Up @@ -141,6 +144,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
[
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: '999',
dateInShape: 'Wed Dec 09 2020 12:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId7',
Expand All @@ -166,6 +170,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
[
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: '999',
dateInShape: 'Wed Dec 09 2020 12:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId7',
Expand Down Expand Up @@ -204,6 +209,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
const currLocationMapWithOther = new Map([...currLocationMap]).set('d', [
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: OTHER_CATEGORY,
dateInShape: 'Wed Dec 09 2020 14:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId1',
Expand All @@ -225,6 +231,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
[
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: 'other',
dateInShape: 'Wed Dec 09 2020 14:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId1',
Expand All @@ -241,18 +248,21 @@ describe('getEntitiesAndGenerateAlerts', () => {
const currLocationMapWithThreeMore = new Map([...currLocationMap]).set('d', [
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: '789',
dateInShape: 'Wed Dec 10 2020 14:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId1',
},
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: '123',
dateInShape: 'Wed Dec 08 2020 12:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId2',
},
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: '456',
dateInShape: 'Wed Dec 07 2020 10:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId3',
Expand All @@ -277,18 +287,21 @@ describe('getEntitiesAndGenerateAlerts', () => {
const currLocationMapWithOther = new Map([...currLocationMap]).set('d', [
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: OTHER_CATEGORY,
dateInShape: 'Wed Dec 10 2020 14:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId1',
},
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: '123',
dateInShape: 'Wed Dec 08 2020 12:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId1',
},
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: '456',
dateInShape: 'Wed Dec 07 2020 10:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId1',
Expand All @@ -310,18 +323,21 @@ describe('getEntitiesAndGenerateAlerts', () => {
const currLocationMapWithOther = new Map([...currLocationMap]).set('d', [
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: '123',
dateInShape: 'Wed Dec 10 2020 14:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId1',
},
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: OTHER_CATEGORY,
dateInShape: 'Wed Dec 08 2020 12:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId1',
},
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: '456',
dateInShape: 'Wed Dec 07 2020 10:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId1',
Expand All @@ -338,6 +354,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
new Map([...currLocationMap]).set('d', [
{
location: [0, 0],
locationWkt: 'POINT (0 0)',
shapeLocationId: '123',
dateInShape: 'Wed Dec 10 2020 14:31:31 GMT-0700 (Mountain Standard Time)',
docId: 'docId1',
Expand Down
Expand Up @@ -56,6 +56,11 @@ export function getEntitiesAndGenerateAlerts(
return;
}

// TODO remove otherCatIndex check
// Elasticsearch filters aggregation is used to group results into buckets matching entity locations intersecting boundary shapes
// filters.other_bucket_key returns bucket with entities that did not intersect any boundary shape.
// shapeLocationId === OTHER_CATEGORY can only occur when containments.length === 1
// test data does not follow this pattern and needs to be updated.
const otherCatIndex = containments.findIndex(
({ shapeLocationId }) => shapeLocationId === OTHER_CATEGORY
);
Expand Down