[Maps] Add query bar inputs to geo threshold alerts tracked points & boundaries (#80871)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
a2d288d134
commit
0546f98070
|
@ -3,7 +3,7 @@
|
|||
"server": true,
|
||||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"requiredPlugins": ["alerts", "features", "triggersActionsUi", "kibanaReact"],
|
||||
"requiredPlugins": ["alerts", "features", "triggersActionsUi", "kibanaReact", "savedObjects", "data"],
|
||||
"configPath": ["xpack", "stack_alerts"],
|
||||
"ui": true
|
||||
}
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should render BoundaryIndexExpression 1`] = `
|
||||
<ExpressionWithPopover
|
||||
defaultValue="Select an index pattern and geo shape field"
|
||||
expressionDescription="index"
|
||||
popoverContent={
|
||||
<React.Fragment>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={true}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
id="geoIndexPatternSelect"
|
||||
labelType="label"
|
||||
>
|
||||
<GeoIndexPatternSelect
|
||||
IndexPatternSelectComponent={null}
|
||||
http={null}
|
||||
includedGeoTypes={
|
||||
Array [
|
||||
"geo_shape",
|
||||
]
|
||||
}
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={true}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
id="geoField"
|
||||
label="Geospatial field"
|
||||
labelType="label"
|
||||
>
|
||||
<SingleFieldSelect
|
||||
fields={Array []}
|
||||
onChange={[Function]}
|
||||
placeholder="Select geo field"
|
||||
value=""
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={true}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
id="boundaryNameFieldSelect"
|
||||
label="Human-readable boundary name (optional)"
|
||||
labelType="label"
|
||||
>
|
||||
<SingleFieldSelect
|
||||
fields={Array []}
|
||||
onChange={[Function]}
|
||||
placeholder="Select boundary name"
|
||||
value="testNameField"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`should render EntityIndexExpression 1`] = `
|
||||
<ExpressionWithPopover
|
||||
defaultValue="Select an index pattern and geo shape/point field"
|
||||
expressionDescription="index"
|
||||
isInvalid={false}
|
||||
popoverContent={
|
||||
<React.Fragment>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={true}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
id="geoIndexPatternSelect"
|
||||
labelType="label"
|
||||
>
|
||||
<GeoIndexPatternSelect
|
||||
IndexPatternSelectComponent={null}
|
||||
http={null}
|
||||
includedGeoTypes={
|
||||
Array [
|
||||
"geo_point",
|
||||
]
|
||||
}
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={true}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
id="thresholdTimeField"
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Time field"
|
||||
id="xpack.stackAlerts.geoThreshold.timeFieldLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
labelType="label"
|
||||
>
|
||||
<SingleFieldSelect
|
||||
fields={Array []}
|
||||
onChange={[Function]}
|
||||
placeholder="Select time field"
|
||||
value="testDateField"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={true}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
id="geoField"
|
||||
label="Geospatial field"
|
||||
labelType="label"
|
||||
>
|
||||
<SingleFieldSelect
|
||||
fields={Array []}
|
||||
onChange={[Function]}
|
||||
placeholder="Select geo field"
|
||||
value="testGeoField"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`should render EntityIndexExpression w/ invalid flag if invalid 1`] = `
|
||||
<ExpressionWithPopover
|
||||
defaultValue="Select an index pattern and geo shape/point field"
|
||||
expressionDescription="index"
|
||||
isInvalid={true}
|
||||
popoverContent={
|
||||
<React.Fragment>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={true}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
id="geoIndexPatternSelect"
|
||||
labelType="label"
|
||||
>
|
||||
<GeoIndexPatternSelect
|
||||
IndexPatternSelectComponent={null}
|
||||
http={null}
|
||||
includedGeoTypes={
|
||||
Array [
|
||||
"geo_point",
|
||||
]
|
||||
}
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={true}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
id="thresholdTimeField"
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Time field"
|
||||
id="xpack.stackAlerts.geoThreshold.timeFieldLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
labelType="label"
|
||||
>
|
||||
<SingleFieldSelect
|
||||
fields={Array []}
|
||||
onChange={[Function]}
|
||||
placeholder="Select time field"
|
||||
value="testDateField"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={true}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
id="geoField"
|
||||
label="Geospatial field"
|
||||
labelType="label"
|
||||
>
|
||||
<SingleFieldSelect
|
||||
fields={Array []}
|
||||
onChange={[Function]}
|
||||
placeholder="Select geo field"
|
||||
value="testGeoField"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
`;
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { EntityIndexExpression } from './expressions/entity_index_expression';
|
||||
import { BoundaryIndexExpression } from './expressions/boundary_index_expression';
|
||||
import { ApplicationStart, DocLinksStart, HttpSetup, ToastsStart } from 'kibana/public';
|
||||
import {
|
||||
ActionTypeRegistryContract,
|
||||
AlertTypeRegistryContract,
|
||||
IErrorObject,
|
||||
} from '../../../../../triggers_actions_ui/public';
|
||||
import { IIndexPattern } from '../../../../../../../src/plugins/data/common';
|
||||
|
||||
const alertsContext = {
|
||||
http: (null as unknown) as HttpSetup,
|
||||
alertTypeRegistry: (null as unknown) as AlertTypeRegistryContract,
|
||||
actionTypeRegistry: (null as unknown) as ActionTypeRegistryContract,
|
||||
toastNotifications: (null as unknown) as ToastsStart,
|
||||
docLinks: (null as unknown) as DocLinksStart,
|
||||
capabilities: (null as unknown) as ApplicationStart['capabilities'],
|
||||
};
|
||||
|
||||
const alertParams = {
|
||||
index: '',
|
||||
indexId: '',
|
||||
geoField: '',
|
||||
entity: '',
|
||||
dateField: '',
|
||||
trackingEvent: '',
|
||||
boundaryType: '',
|
||||
boundaryIndexTitle: '',
|
||||
boundaryIndexId: '',
|
||||
boundaryGeoField: '',
|
||||
};
|
||||
|
||||
test('should render EntityIndexExpression', async () => {
|
||||
const component = shallow(
|
||||
<EntityIndexExpression
|
||||
dateField={'testDateField'}
|
||||
geoField={'testGeoField'}
|
||||
alertsContext={alertsContext}
|
||||
errors={{} as IErrorObject}
|
||||
setAlertParamsDate={() => {}}
|
||||
setAlertParamsGeoField={() => {}}
|
||||
setAlertProperty={() => {}}
|
||||
setIndexPattern={() => {}}
|
||||
indexPattern={('' as unknown) as IIndexPattern}
|
||||
isInvalid={false}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render EntityIndexExpression w/ invalid flag if invalid', async () => {
|
||||
const component = shallow(
|
||||
<EntityIndexExpression
|
||||
dateField={'testDateField'}
|
||||
geoField={'testGeoField'}
|
||||
alertsContext={alertsContext}
|
||||
errors={{} as IErrorObject}
|
||||
setAlertParamsDate={() => {}}
|
||||
setAlertParamsGeoField={() => {}}
|
||||
setAlertProperty={() => {}}
|
||||
setIndexPattern={() => {}}
|
||||
indexPattern={('' as unknown) as IIndexPattern}
|
||||
isInvalid={true}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render BoundaryIndexExpression', async () => {
|
||||
const component = shallow(
|
||||
<BoundaryIndexExpression
|
||||
alertParams={alertParams}
|
||||
alertsContext={alertsContext}
|
||||
errors={{} as IErrorObject}
|
||||
boundaryIndexPattern={('' as unknown) as IIndexPattern}
|
||||
setBoundaryIndexPattern={() => {}}
|
||||
setBoundaryGeoField={() => {}}
|
||||
setBoundaryNameField={() => {}}
|
||||
boundaryNameField={'testNameField'}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
|
@ -30,6 +30,12 @@ import { EntityIndexExpression } from './expressions/entity_index_expression';
|
|||
import { EntityByExpression } from './expressions/entity_by_expression';
|
||||
import { BoundaryIndexExpression } from './expressions/boundary_index_expression';
|
||||
import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns';
|
||||
import {
|
||||
esQuery,
|
||||
esKuery,
|
||||
Query,
|
||||
QueryStringInput,
|
||||
} from '../../../../../../../src/plugins/data/public';
|
||||
|
||||
const DEFAULT_VALUES = {
|
||||
TRACKING_EVENT: '',
|
||||
|
@ -67,6 +73,18 @@ const labelForDelayOffset = (
|
|||
</>
|
||||
);
|
||||
|
||||
function validateQuery(query: Query) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
query.language === 'kuery'
|
||||
? esKuery.fromKueryExpression(query.query)
|
||||
: esQuery.luceneStringToDsl(query.query);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export const GeoThresholdAlertTypeExpression: React.FunctionComponent<AlertTypeParamsExpressionProps<
|
||||
GeoThresholdAlertParams,
|
||||
AlertsContextValue
|
||||
|
@ -74,6 +92,7 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent<AlertTypeP
|
|||
const {
|
||||
index,
|
||||
indexId,
|
||||
indexQuery,
|
||||
geoField,
|
||||
entity,
|
||||
dateField,
|
||||
|
@ -81,6 +100,7 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent<AlertTypeP
|
|||
boundaryType,
|
||||
boundaryIndexTitle,
|
||||
boundaryIndexId,
|
||||
boundaryIndexQuery,
|
||||
boundaryGeoField,
|
||||
boundaryNameField,
|
||||
delayOffsetWithUnits,
|
||||
|
@ -102,6 +122,12 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent<AlertTypeP
|
|||
}
|
||||
}
|
||||
};
|
||||
const [indexQueryInput, setIndexQueryInput] = useState<Query>(
|
||||
indexQuery || {
|
||||
query: '',
|
||||
language: 'kuery',
|
||||
}
|
||||
);
|
||||
const [boundaryIndexPattern, _setBoundaryIndexPattern] = useState<IIndexPattern>({
|
||||
id: '',
|
||||
fields: [],
|
||||
|
@ -118,6 +144,12 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent<AlertTypeP
|
|||
}
|
||||
}
|
||||
};
|
||||
const [boundaryIndexQueryInput, setBoundaryIndexQueryInput] = useState<Query>(
|
||||
boundaryIndexQuery || {
|
||||
query: '',
|
||||
language: 'kuery',
|
||||
}
|
||||
);
|
||||
const [delayOffset, _setDelayOffset] = useState<number>(0);
|
||||
function setDelayOffset(_delayOffset: number) {
|
||||
setAlertParams('delayOffsetWithUnits', `${_delayOffset}${delayOffsetUnit}`);
|
||||
|
@ -248,6 +280,23 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent<AlertTypeP
|
|||
indexFields={indexPattern.fields}
|
||||
isInvalid={indexId && dateField && geoField ? !entity : false}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexItem>
|
||||
<QueryStringInput
|
||||
disableAutoFocus
|
||||
bubbleSubmitEvent
|
||||
indexPatterns={indexPattern ? [indexPattern] : []}
|
||||
query={indexQueryInput}
|
||||
onChange={(query) => {
|
||||
if (query.language) {
|
||||
if (validateQuery(query)) {
|
||||
setAlertParams('indexQuery', query);
|
||||
}
|
||||
setIndexQueryInput(query);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
<EuiTitle size="xs">
|
||||
|
@ -313,6 +362,24 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent<AlertTypeP
|
|||
}
|
||||
boundaryNameField={boundaryNameField}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexItem>
|
||||
<QueryStringInput
|
||||
disableAutoFocus
|
||||
bubbleSubmitEvent
|
||||
indexPatterns={boundaryIndexPattern ? [boundaryIndexPattern] : []}
|
||||
query={boundaryIndexQueryInput}
|
||||
onChange={(query) => {
|
||||
if (query.language) {
|
||||
if (validateQuery(query)) {
|
||||
setAlertParams('boundaryIndexQuery', query);
|
||||
}
|
||||
setBoundaryIndexQueryInput(query);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer size="l" />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Query } from '../../../../../../src/plugins/data/common';
|
||||
|
||||
export enum TrackingEvent {
|
||||
entered = 'entered',
|
||||
exited = 'exited',
|
||||
|
@ -22,6 +24,8 @@ export interface GeoThresholdAlertParams {
|
|||
boundaryGeoField: string;
|
||||
boundaryNameField?: string;
|
||||
delayOffsetWithUnits?: string;
|
||||
indexQuery?: Query;
|
||||
boundaryIndexQuery?: Query;
|
||||
}
|
||||
|
||||
// Will eventually include 'geo_shape'
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
ActionVariable,
|
||||
AlertTypeState,
|
||||
} from '../../../../alerts/server';
|
||||
import { Query } from '../../../../../../src/plugins/data/common/query';
|
||||
|
||||
export const GEO_THRESHOLD_ID = '.geo-threshold';
|
||||
export type TrackingEvent = 'entered' | 'exited';
|
||||
|
@ -155,6 +156,8 @@ export const ParamsSchema = schema.object({
|
|||
boundaryGeoField: schema.string({ minLength: 1 }),
|
||||
boundaryNameField: schema.maybe(schema.string({ minLength: 1 })),
|
||||
delayOffsetWithUnits: schema.maybe(schema.string({ minLength: 1 })),
|
||||
indexQuery: schema.maybe(schema.any({})),
|
||||
boundaryIndexQuery: schema.maybe(schema.any({})),
|
||||
});
|
||||
|
||||
export interface GeoThresholdParams {
|
||||
|
@ -170,6 +173,8 @@ export interface GeoThresholdParams {
|
|||
boundaryGeoField: string;
|
||||
boundaryNameField?: string;
|
||||
delayOffsetWithUnits?: string;
|
||||
indexQuery?: Query;
|
||||
boundaryIndexQuery?: Query;
|
||||
}
|
||||
|
||||
export function getAlertType(
|
||||
|
|
|
@ -7,6 +7,13 @@
|
|||
import { ILegacyScopedClusterClient } from 'kibana/server';
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { Logger } from 'src/core/server';
|
||||
import {
|
||||
Query,
|
||||
IIndexPattern,
|
||||
fromKueryExpression,
|
||||
toElasticsearchQuery,
|
||||
luceneStringToDsl,
|
||||
} from '../../../../../../src/plugins/data/common';
|
||||
|
||||
export const OTHER_CATEGORY = 'other';
|
||||
// Consider dynamically obtaining from config?
|
||||
|
@ -14,6 +21,19 @@ const MAX_TOP_LEVEL_QUERY_SIZE = 0;
|
|||
const MAX_SHAPES_QUERY_SIZE = 10000;
|
||||
const MAX_BUCKETS_LIMIT = 65535;
|
||||
|
||||
export const getEsFormattedQuery = (query: Query, indexPattern?: IIndexPattern) => {
|
||||
let esFormattedQuery;
|
||||
|
||||
const queryLanguage = query.language;
|
||||
if (queryLanguage === 'kuery') {
|
||||
const ast = fromKueryExpression(query.query);
|
||||
esFormattedQuery = toElasticsearchQuery(ast, indexPattern);
|
||||
} else {
|
||||
esFormattedQuery = luceneStringToDsl(query.query);
|
||||
}
|
||||
return esFormattedQuery;
|
||||
};
|
||||
|
||||
export async function getShapesFilters(
|
||||
boundaryIndexTitle: string,
|
||||
boundaryGeoField: string,
|
||||
|
@ -21,7 +41,8 @@ export async function getShapesFilters(
|
|||
callCluster: ILegacyScopedClusterClient['callAsCurrentUser'],
|
||||
log: Logger,
|
||||
alertId: string,
|
||||
boundaryNameField?: string
|
||||
boundaryNameField?: string,
|
||||
boundaryIndexQuery?: Query
|
||||
) {
|
||||
const filters: Record<string, unknown> = {};
|
||||
const shapesIdsNamesMap: Record<string, unknown> = {};
|
||||
|
@ -30,8 +51,10 @@ export async function getShapesFilters(
|
|||
index: boundaryIndexTitle,
|
||||
body: {
|
||||
size: MAX_SHAPES_QUERY_SIZE,
|
||||
...(boundaryIndexQuery ? { query: getEsFormattedQuery(boundaryIndexQuery) } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
boundaryData.hits.hits.forEach(({ _index, _id }) => {
|
||||
filters[_id] = {
|
||||
geo_shape: {
|
||||
|
@ -66,6 +89,7 @@ export async function executeEsQueryFactory(
|
|||
boundaryGeoField,
|
||||
geoField,
|
||||
boundaryIndexTitle,
|
||||
indexQuery,
|
||||
}: {
|
||||
entity: string;
|
||||
index: string;
|
||||
|
@ -74,6 +98,7 @@ export async function executeEsQueryFactory(
|
|||
geoField: string;
|
||||
boundaryIndexTitle: string;
|
||||
boundaryNameField?: string;
|
||||
indexQuery?: Query;
|
||||
},
|
||||
{ callCluster }: { callCluster: ILegacyScopedClusterClient['callAsCurrentUser'] },
|
||||
log: Logger,
|
||||
|
@ -83,6 +108,19 @@ export async function executeEsQueryFactory(
|
|||
gteDateTime: Date | null,
|
||||
ltDateTime: Date | null
|
||||
): Promise<SearchResponse<unknown> | undefined> => {
|
||||
let esFormattedQuery;
|
||||
if (indexQuery) {
|
||||
const gteEpochDateTime = gteDateTime ? new Date(gteDateTime).getTime() : null;
|
||||
const ltEpochDateTime = ltDateTime ? new Date(ltDateTime).getTime() : null;
|
||||
const dateRangeUpdatedQuery =
|
||||
indexQuery.language === 'kuery'
|
||||
? `(${dateField} >= "${gteEpochDateTime}" and ${dateField} < "${ltEpochDateTime}") and (${indexQuery.query})`
|
||||
: `(${dateField}:[${gteDateTime} TO ${ltDateTime}]) AND (${indexQuery.query})`;
|
||||
esFormattedQuery = getEsFormattedQuery({
|
||||
query: dateRangeUpdatedQuery,
|
||||
language: indexQuery.language,
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const esQuery: Record<string, any> = {
|
||||
index,
|
||||
|
@ -120,27 +158,29 @@ export async function executeEsQueryFactory(
|
|||
},
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
must: [],
|
||||
filter: [
|
||||
{
|
||||
match_all: {},
|
||||
},
|
||||
{
|
||||
range: {
|
||||
[dateField]: {
|
||||
...(gteDateTime ? { gte: gteDateTime } : {}),
|
||||
lt: ltDateTime, // 'less than' to prevent overlap between intervals
|
||||
format: 'strict_date_optional_time',
|
||||
query: esFormattedQuery
|
||||
? esFormattedQuery
|
||||
: {
|
||||
bool: {
|
||||
must: [],
|
||||
filter: [
|
||||
{
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
range: {
|
||||
[dateField]: {
|
||||
...(gteDateTime ? { gte: gteDateTime } : {}),
|
||||
lt: ltDateTime, // 'less than' to prevent overlap between intervals
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
should: [],
|
||||
must_not: [],
|
||||
},
|
||||
],
|
||||
should: [],
|
||||
must_not: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
stored_fields: ['*'],
|
||||
docvalue_fields: [
|
||||
{
|
||||
|
|
|
@ -194,7 +194,8 @@ export const getGeoThresholdExecutor = (log: Logger) =>
|
|||
services.callCluster,
|
||||
log,
|
||||
alertId,
|
||||
params.boundaryNameField
|
||||
params.boundaryNameField,
|
||||
params.boundaryIndexQuery
|
||||
);
|
||||
|
||||
const executeEsQuery = await executeEsQueryFactory(params, services, log, shapesFilters);
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 { getEsFormattedQuery } from '../es_query_builder';
|
||||
|
||||
describe('esFormattedQuery', () => {
|
||||
it('lucene queries are converted correctly', async () => {
|
||||
const testLuceneQuery1 = {
|
||||
query: `"airport": "Denver"`,
|
||||
language: 'lucene',
|
||||
};
|
||||
const esFormattedQuery1 = getEsFormattedQuery(testLuceneQuery1);
|
||||
expect(esFormattedQuery1).toStrictEqual({ query_string: { query: '"airport": "Denver"' } });
|
||||
const testLuceneQuery2 = {
|
||||
query: `title:"Fun with turnips" AND text:Cabbage, cabbage and more cabbage!`,
|
||||
language: 'lucene',
|
||||
};
|
||||
const esFormattedQuery2 = getEsFormattedQuery(testLuceneQuery2);
|
||||
expect(esFormattedQuery2).toStrictEqual({
|
||||
query_string: {
|
||||
query: `title:"Fun with turnips" AND text:Cabbage, cabbage and more cabbage!`,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('kuery queries are converted correctly', async () => {
|
||||
const testKueryQuery1 = {
|
||||
query: `"airport": "Denver"`,
|
||||
language: 'kuery',
|
||||
};
|
||||
const esFormattedQuery1 = getEsFormattedQuery(testKueryQuery1);
|
||||
expect(esFormattedQuery1).toStrictEqual({
|
||||
bool: { minimum_should_match: 1, should: [{ match_phrase: { airport: 'Denver' } }] },
|
||||
});
|
||||
const testKueryQuery2 = {
|
||||
query: `"airport": "Denver" and ("animal": "goat" or "animal": "narwhal")`,
|
||||
language: 'kuery',
|
||||
};
|
||||
const esFormattedQuery2 = getEsFormattedQuery(testKueryQuery2);
|
||||
expect(esFormattedQuery2).toStrictEqual({
|
||||
bool: {
|
||||
filter: [
|
||||
{ bool: { should: [{ match_phrase: { airport: 'Denver' } }], minimum_should_match: 1 } },
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
bool: { should: [{ match_phrase: { animal: 'goat' } }], minimum_should_match: 1 },
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
should: [{ match_phrase: { animal: 'narwhal' } }],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,8 +4,8 @@
|
|||
"server": true,
|
||||
"ui": true,
|
||||
"optionalPlugins": ["alerts", "features", "home"],
|
||||
"requiredPlugins": ["management", "charts", "data"],
|
||||
"requiredPlugins": ["management", "charts", "data", "kibanaReact", "savedObjects"],
|
||||
"configPath": ["xpack", "trigger_actions_ui"],
|
||||
"extraPublicDirs": ["public/common", "public/common/constants"],
|
||||
"requiredBundles": ["home", "alerts", "esUiShared"]
|
||||
"requiredBundles": ["home", "alerts", "esUiShared", "kibanaReact", "kibanaUtils"]
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
ChromeBreadcrumb,
|
||||
CoreStart,
|
||||
ScopedHistory,
|
||||
SavedObjectsClientContract,
|
||||
} from 'kibana/public';
|
||||
import { KibanaFeature } from '../../../features/common';
|
||||
import { Section, routeToAlertDetails } from './constants';
|
||||
|
@ -24,6 +25,7 @@ import { ChartsPluginStart } from '../../../../../src/plugins/charts/public';
|
|||
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
|
||||
import { PluginStartContract as AlertingStart } from '../../../alerts/public';
|
||||
import { suspendedComponentWithProps } from './lib/suspended_component_with_props';
|
||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
const TriggersActionsUIHome = lazy(async () => import('./home'));
|
||||
const AlertDetailsRoute = lazy(
|
||||
|
@ -31,13 +33,14 @@ const AlertDetailsRoute = lazy(
|
|||
);
|
||||
|
||||
export interface AppDeps {
|
||||
dataPlugin: DataPublicPluginStart;
|
||||
data: DataPublicPluginStart;
|
||||
charts: ChartsPluginStart;
|
||||
chrome: ChromeStart;
|
||||
alerts?: AlertingStart;
|
||||
navigateToApp: CoreStart['application']['navigateToApp'];
|
||||
docLinks: DocLinksStart;
|
||||
toastNotifications: ToastsSetup;
|
||||
storage?: Storage;
|
||||
http: HttpSetup;
|
||||
uiSettings: IUiSettingsClient;
|
||||
setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void;
|
||||
|
@ -45,6 +48,9 @@ export interface AppDeps {
|
|||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
alertTypeRegistry: AlertTypeRegistryContract;
|
||||
history: ScopedHistory;
|
||||
savedObjects?: {
|
||||
client: SavedObjectsClientContract;
|
||||
};
|
||||
kibanaFeatures: KibanaFeature[];
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { AppDeps } from './app';
|
||||
|
||||
const AppContext = createContext<AppDeps | null>(null);
|
||||
|
@ -16,7 +17,11 @@ export const AppContextProvider = ({
|
|||
appDeps: AppDeps | null;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return appDeps ? <AppContext.Provider value={appDeps}>{children}</AppContext.Provider> : null;
|
||||
return appDeps ? (
|
||||
<KibanaContextProvider services={appDeps}>
|
||||
<AppContext.Provider value={appDeps}>{children}</AppContext.Provider>
|
||||
</KibanaContextProvider>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const useAppDependencies = (): AppDeps => {
|
||||
|
|
|
@ -6,21 +6,20 @@
|
|||
|
||||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { SavedObjectsClientContract } from 'src/core/public';
|
||||
|
||||
import { App, AppDeps } from './app';
|
||||
import { setSavedObjectsClient } from '../common/lib/data_apis';
|
||||
|
||||
interface BootDeps extends AppDeps {
|
||||
element: HTMLElement;
|
||||
savedObjects: SavedObjectsClientContract;
|
||||
I18nContext: any;
|
||||
}
|
||||
|
||||
export const boot = (bootDeps: BootDeps) => {
|
||||
const { I18nContext, element, savedObjects, ...appDeps } = bootDeps;
|
||||
const { I18nContext, element, ...appDeps } = bootDeps;
|
||||
|
||||
setSavedObjectsClient(savedObjects);
|
||||
if (appDeps.savedObjects) {
|
||||
setSavedObjectsClient(appDeps.savedObjects.client);
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
|
|
|
@ -55,7 +55,7 @@ describe('actions_connectors_list component empty', () => {
|
|||
const deps = {
|
||||
chrome,
|
||||
docLinks,
|
||||
dataPlugin: dataPluginMock.createStartContract(),
|
||||
data: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
alerting: alertingPluginMock.createStartContract(),
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
|
@ -165,7 +165,7 @@ describe('actions_connectors_list component with items', () => {
|
|||
const deps = {
|
||||
chrome,
|
||||
docLinks,
|
||||
dataPlugin: dataPluginMock.createStartContract(),
|
||||
data: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
alerting: alertingPluginMock.createStartContract(),
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
|
@ -256,7 +256,7 @@ describe('actions_connectors_list component empty with show only capability', ()
|
|||
const deps = {
|
||||
chrome,
|
||||
docLinks,
|
||||
dataPlugin: dataPluginMock.createStartContract(),
|
||||
data: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
alerting: alertingPluginMock.createStartContract(),
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
|
@ -348,7 +348,7 @@ describe('actions_connectors_list with show only capability', () => {
|
|||
const deps = {
|
||||
chrome,
|
||||
docLinks,
|
||||
dataPlugin: dataPluginMock.createStartContract(),
|
||||
data: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
alerting: alertingPluginMock.createStartContract(),
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
|
@ -452,7 +452,7 @@ describe('actions_connectors_list component with disabled items', () => {
|
|||
const deps = {
|
||||
chrome,
|
||||
docLinks,
|
||||
dataPlugin: dataPluginMock.createStartContract(),
|
||||
data: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
injectedMetadata: mockes.injectedMetadata,
|
||||
|
|
|
@ -42,7 +42,7 @@ jest.mock('../../../app_context', () => ({
|
|||
toastNotifications: mockes.notifications.toasts,
|
||||
docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' },
|
||||
uiSettings: mockes.uiSettings,
|
||||
dataPlugin: jest.fn(),
|
||||
data: jest.fn(),
|
||||
charts: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
|
|
@ -70,7 +70,7 @@ export const AlertDetails: React.FunctionComponent<AlertDetailsProps> = ({
|
|||
uiSettings,
|
||||
docLinks,
|
||||
charts,
|
||||
dataPlugin,
|
||||
data,
|
||||
setBreadcrumbs,
|
||||
chrome,
|
||||
} = useAppDependencies();
|
||||
|
@ -162,11 +162,11 @@ export const AlertDetails: React.FunctionComponent<AlertDetailsProps> = ({
|
|||
uiSettings,
|
||||
docLinks,
|
||||
charts,
|
||||
dataFieldsFormats: dataPlugin.fieldFormats,
|
||||
dataFieldsFormats: data.fieldFormats,
|
||||
reloadAlerts: setAlert,
|
||||
capabilities,
|
||||
dataUi: dataPlugin.ui,
|
||||
dataIndexPatterns: dataPlugin.indexPatterns,
|
||||
dataUi: data.ui,
|
||||
dataIndexPatterns: data.indexPatterns,
|
||||
}}
|
||||
>
|
||||
<AlertEdit
|
||||
|
|
|
@ -82,7 +82,7 @@ describe('alert_add', () => {
|
|||
toastNotifications: mocks.notifications.toasts,
|
||||
http: mocks.http,
|
||||
uiSettings: mocks.uiSettings,
|
||||
dataPlugin: dataPluginMock.createStartContract(),
|
||||
data: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
actionTypeRegistry,
|
||||
alertTypeRegistry,
|
||||
|
|
|
@ -108,7 +108,7 @@ describe('alerts_list component empty', () => {
|
|||
const deps = {
|
||||
chrome,
|
||||
docLinks,
|
||||
dataPlugin: dataPluginMock.createStartContract(),
|
||||
data: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
alerting: alertingPluginMock.createStartContract(),
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
|
@ -279,7 +279,7 @@ describe('alerts_list component with items', () => {
|
|||
const deps = {
|
||||
chrome,
|
||||
docLinks,
|
||||
dataPlugin: dataPluginMock.createStartContract(),
|
||||
data: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
alerting: alertingPluginMock.createStartContract(),
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
|
@ -362,7 +362,7 @@ describe('alerts_list component empty with show only capability', () => {
|
|||
const deps = {
|
||||
chrome,
|
||||
docLinks,
|
||||
dataPlugin: dataPluginMock.createStartContract(),
|
||||
data: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
alerting: alertingPluginMock.createStartContract(),
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
|
@ -483,7 +483,7 @@ describe('alerts_list with show only capability', () => {
|
|||
const deps = {
|
||||
chrome,
|
||||
docLinks,
|
||||
dataPlugin: dataPluginMock.createStartContract(),
|
||||
data: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
alerting: alertingPluginMock.createStartContract(),
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
|
|
|
@ -83,7 +83,7 @@ export const AlertsList: React.FunctionComponent = () => {
|
|||
uiSettings,
|
||||
docLinks,
|
||||
charts,
|
||||
dataPlugin,
|
||||
data,
|
||||
kibanaFeatures,
|
||||
} = useAppDependencies();
|
||||
const canExecuteActions = hasExecuteActionsCapability(capabilities);
|
||||
|
@ -668,10 +668,10 @@ export const AlertsList: React.FunctionComponent = () => {
|
|||
uiSettings,
|
||||
docLinks,
|
||||
charts,
|
||||
dataFieldsFormats: dataPlugin.fieldFormats,
|
||||
dataFieldsFormats: data.fieldFormats,
|
||||
capabilities,
|
||||
dataUi: dataPlugin.ui,
|
||||
dataIndexPatterns: dataPlugin.indexPatterns,
|
||||
dataUi: data.ui,
|
||||
dataIndexPatterns: data.indexPatterns,
|
||||
kibanaFeatures,
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -26,20 +26,20 @@ export async function getMockedAppDependencies() {
|
|||
const kibanaFeatures = await featuresPluginMock.createStart().getFeatures();
|
||||
|
||||
return {
|
||||
chrome,
|
||||
docLinks,
|
||||
dataPlugin: dataPluginMock.createStartContract(),
|
||||
data: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
alerting: alertingPluginMock.createStartContract(),
|
||||
chrome,
|
||||
navigateToApp,
|
||||
docLinks,
|
||||
toastNotifications: coreSetupMock.notifications.toasts,
|
||||
http: coreSetupMock.http,
|
||||
uiSettings: coreSetupMock.uiSettings,
|
||||
navigateToApp,
|
||||
capabilities,
|
||||
history: scopedHistoryMock.create(),
|
||||
setBreadcrumbs: jest.fn(),
|
||||
capabilities,
|
||||
actionTypeRegistry,
|
||||
alertTypeRegistry,
|
||||
history: scopedHistoryMock.create(),
|
||||
alerting: alertingPluginMock.createStartContract(),
|
||||
kibanaFeatures,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ export {
|
|||
AlertTypeModel,
|
||||
ActionType,
|
||||
ActionTypeRegistryContract,
|
||||
AlertTypeRegistryContract,
|
||||
AlertTypeParamsExpressionProps,
|
||||
ValidationResult,
|
||||
ActionVariable,
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
import { ChartsPluginStart } from '../../../../src/plugins/charts/public';
|
||||
import { PluginStartContract as AlertingStart } from '../../alerts/public';
|
||||
import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
|
||||
import { Storage } from '../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
export interface TriggersAndActionsUIPublicPluginSetup {
|
||||
actionTypeRegistry: TypeRegistry<ActionTypeModel>;
|
||||
|
@ -102,16 +103,17 @@ export class Plugin
|
|||
const { boot } = await import('./application/boot');
|
||||
const kibanaFeatures = await pluginsStart.features.getFeatures();
|
||||
return boot({
|
||||
dataPlugin: pluginsStart.data,
|
||||
data: pluginsStart.data,
|
||||
charts: pluginsStart.charts,
|
||||
alerts: pluginsStart.alerts,
|
||||
element: params.element,
|
||||
toastNotifications: coreStart.notifications.toasts,
|
||||
storage: new Storage(window.localStorage),
|
||||
http: coreStart.http,
|
||||
uiSettings: coreStart.uiSettings,
|
||||
docLinks: coreStart.docLinks,
|
||||
chrome: coreStart.chrome,
|
||||
savedObjects: coreStart.savedObjects.client,
|
||||
savedObjects: coreStart.savedObjects,
|
||||
I18nContext: coreStart.i18n.Context,
|
||||
capabilities: coreStart.application.capabilities,
|
||||
navigateToApp: coreStart.application.navigateToApp,
|
||||
|
|
Loading…
Reference in a new issue