From 95948fd5ebdeeb6f7dd6177df6f2d104b56f10e0 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Tue, 9 Feb 2021 15:12:13 -0600 Subject: [PATCH 1/9] [Metrics UI] Add warning severity to Metric Alerts (#90070) --- .../infra/common/alerting/metrics/index.ts | 4 +- .../infra/common/alerting/metrics/types.ts | 17 +- .../common/components/alert_preview.tsx | 208 ++++++++++++---- .../inventory/components/expression.tsx | 184 ++++++++++++-- .../inventory/components/validation.tsx | 80 +++++-- .../components/expression_chart.tsx | 226 ++++++++++-------- .../components/expression_row.tsx | 203 +++++++++++++--- .../components/validation.tsx | 74 ++++-- .../server/lib/alerting/common/messages.ts | 3 + .../infra/server/lib/alerting/common/types.ts | 9 + .../evaluate_condition.ts | 25 +- .../inventory_metric_threshold_executor.ts | 38 ++- ...review_inventory_metric_threshold_alert.ts | 22 +- ...r_inventory_metric_threshold_alert_type.ts | 20 +- .../inventory_metric_threshold/types.ts | 2 + .../metric_threshold/lib/evaluate_alert.ts | 21 +- .../metric_threshold_executor.ts | 50 +++- .../preview_metric_threshold_alert.test.ts | 66 ++--- .../preview_metric_threshold_alert.ts | 25 +- .../register_metric_threshold_alert_type.ts | 14 +- .../lib/alerting/metric_threshold/types.ts | 2 + .../infra/server/routes/alerting/preview.ts | 73 +++--- .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 7 +- 24 files changed, 998 insertions(+), 379 deletions(-) diff --git a/x-pack/plugins/infra/common/alerting/metrics/index.ts b/x-pack/plugins/infra/common/alerting/metrics/index.ts index 5151a40c7e8b1..2c66638711cd0 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/index.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/index.ts @@ -10,8 +10,8 @@ export const INFRA_ALERT_PREVIEW_PATH = '/api/infra/alerting/preview'; export const TOO_MANY_BUCKETS_PREVIEW_EXCEPTION = 'TOO_MANY_BUCKETS_PREVIEW_EXCEPTION'; export interface TooManyBucketsPreviewExceptionMetadata { - TOO_MANY_BUCKETS_PREVIEW_EXCEPTION: any; - maxBuckets: number; + TOO_MANY_BUCKETS_PREVIEW_EXCEPTION: boolean; + maxBuckets: any; } export const isTooManyBucketsPreviewException = ( value: any diff --git a/x-pack/plugins/infra/common/alerting/metrics/types.ts b/x-pack/plugins/infra/common/alerting/metrics/types.ts index 47a5202cc7275..a89f82e931fd4 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/types.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/types.ts @@ -90,12 +90,17 @@ export type AlertPreviewRequestParams = rt.TypeOf = (props) => { return unthrottledNotifications > notifications; }, [previewResult, showNoDataResults]); + const hasWarningThreshold = useMemo( + () => alertParams.criteria?.some((c) => Reflect.has(c, 'warningThreshold')), + [alertParams] + ); + return ( = (props) => { - - - - ), - }} - />{' '} - {previewResult.groupByDisplayName ? ( - <> - {' '} - - - {' '} - - ) : null} - e.value === previewResult.previewLookbackInterval - )?.shortText, - }} - /> - + } > {showNoDataResults && previewResult.resultTotals.noData ? ( + ), boldedResultsNumber: ( {i18n.translate( 'xpack.infra.metrics.alertFlyout.alertPreviewNoDataResultNumber', { - defaultMessage: - '{noData, plural, one {was # result} other {were # results}}', + defaultMessage: '{noData, plural, one {# result} other {# results}}', values: { noData: previewResult.resultTotals.noData, }, @@ -361,6 +332,145 @@ export const AlertPreview: React.FC = (props) => { ); }; +const PreviewTextString = ({ + previewResult, + hasWarningThreshold, +}: { + previewResult: AlertPreviewSuccessResponsePayload & Record; + hasWarningThreshold: boolean; +}) => { + const instanceCount = hasWarningThreshold ? ( + + ), + criticalInstances: ( + + + + ), + warningInstances: ( + + + + ), + boldCritical: ( + + + + ), + boldWarning: ( + + + + ), + }} + /> + ) : ( + + ), + firedTimes: ( + + + + ), + }} + /> + ); + + const groupByText = previewResult.groupByDisplayName ? ( + <> + + + + ), + }} + />{' '} + + ) : ( + <> + ); + + const lookbackText = ( + e.value === previewResult.previewLookbackInterval) + ?.shortText, + }} + /> + ); + + return ( + + ); +}; + const previewOptions = [ { value: 'h', diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx index d403c254f2bd0..4a05521e9fc87 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { debounce, pick } from 'lodash'; +import { debounce, pick, omit } from 'lodash'; import { Unit } from '@elastic/datemath'; import React, { useCallback, useMemo, useEffect, useState, ChangeEvent } from 'react'; import { IFieldType } from 'src/plugins/data/public'; @@ -21,6 +21,7 @@ import { EuiCheckbox, EuiToolTip, EuiIcon, + EuiHealth, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -423,9 +424,24 @@ const StyledExpression = euiStyled.div` padding: 0 4px; `; +const StyledHealth = euiStyled(EuiHealth)` + margin-left: 4px; +`; + export const ExpressionRow: React.FC = (props) => { const { setAlertParams, expression, errors, expressionId, remove, canDelete, fields } = props; - const { metric, comparator = Comparator.GT, threshold = [], customMetric } = expression; + const { + metric, + comparator = Comparator.GT, + threshold = [], + customMetric, + warningThreshold = [], + warningComparator, + } = expression; + + const [displayWarningThreshold, setDisplayWarningThreshold] = useState( + Boolean(warningThreshold?.length) + ); const updateMetric = useCallback( (m?: SnapshotMetricType | string) => { @@ -452,6 +468,13 @@ export const ExpressionRow: React.FC = (props) => { [expressionId, expression, setAlertParams] ); + const updateWarningComparator = useCallback( + (c?: string) => { + setAlertParams(expressionId, { ...expression, warningComparator: c as Comparator }); + }, + [expressionId, expression, setAlertParams] + ); + const updateThreshold = useCallback( (t) => { if (t.join() !== expression.threshold.join()) { @@ -461,6 +484,58 @@ export const ExpressionRow: React.FC = (props) => { [expressionId, expression, setAlertParams] ); + const updateWarningThreshold = useCallback( + (t) => { + if (t.join() !== expression.warningThreshold?.join()) { + setAlertParams(expressionId, { ...expression, warningThreshold: t }); + } + }, + [expressionId, expression, setAlertParams] + ); + + const toggleWarningThreshold = useCallback(() => { + if (!displayWarningThreshold) { + setDisplayWarningThreshold(true); + setAlertParams(expressionId, { + ...expression, + warningComparator: comparator, + warningThreshold: [], + }); + } else { + setDisplayWarningThreshold(false); + setAlertParams(expressionId, omit(expression, 'warningComparator', 'warningThreshold')); + } + }, [ + displayWarningThreshold, + setDisplayWarningThreshold, + setAlertParams, + comparator, + expression, + expressionId, + ]); + + const criticalThresholdExpression = ( + + ); + + const warningThresholdExpression = displayWarningThreshold && ( + + ); + const ofFields = useMemo(() => { let myMetrics = hostMetricTypes; @@ -515,25 +590,62 @@ export const ExpressionRow: React.FC = (props) => { fields={fields} /> - - - - {metric && ( -
- {metricUnit[metric]?.label || ''} -
- )} + {!displayWarningThreshold && criticalThresholdExpression} + {displayWarningThreshold && ( + <> + + {criticalThresholdExpression} + + + + + + {warningThresholdExpression} + + + + + + + )} + {!displayWarningThreshold && ( + <> + {' '} + + + + + + + + )} {canDelete && ( @@ -553,6 +665,38 @@ export const ExpressionRow: React.FC = (props) => { ); }; +const ThresholdElement: React.FC<{ + updateComparator: (c?: string) => void; + updateThreshold: (t?: number[]) => void; + threshold: InventoryMetricConditions['threshold']; + comparator: InventoryMetricConditions['comparator']; + errors: IErrorObject; + metric?: SnapshotMetricType; +}> = ({ updateComparator, updateThreshold, threshold, metric, comparator, errors }) => { + return ( + <> + + + + {metric && ( +
+ {metricUnit[metric]?.label || ''} +
+ )} + + ); +}; + const getDisplayNameForType = (type: InventoryItemType) => { const inventoryModel = findInventoryModel(type); return inventoryModel.displayName; diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx index 7a87998c8ce1f..c8bab76d79a4e 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx @@ -6,8 +6,11 @@ */ import { i18n } from '@kbn/i18n'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { InventoryMetricConditions } from '../../../../server/lib/alerting/inventory_metric_threshold/types'; +import { + InventoryMetricConditions, + Comparator, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../server/lib/alerting/inventory_metric_threshold/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ValidationResult } from '../../../../../triggers_actions_ui/public/types'; @@ -21,8 +24,14 @@ export function validateMetricThreshold({ [id: string]: { timeSizeUnit: string[]; timeWindowSize: string[]; - threshold0: string[]; - threshold1: string[]; + critical: { + threshold0: string[]; + threshold1: string[]; + }; + warning: { + threshold0: string[]; + threshold1: string[]; + }; metric: string[]; }; } = {}; @@ -39,41 +48,64 @@ export function validateMetricThreshold({ errors[id] = errors[id] || { timeSizeUnit: [], timeWindowSize: [], - threshold0: [], - threshold1: [], + critical: { + threshold0: [], + threshold1: [], + }, + warning: { + threshold0: [], + threshold1: [], + }, metric: [], }; if (!c.threshold || !c.threshold.length) { - errors[id].threshold0.push( + errors[id].critical.threshold0.push( i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', { defaultMessage: 'Threshold is required.', }) ); } - // The Threshold component returns an empty array with a length ([empty]) because it's using delete newThreshold[i]. - // We need to use [...c.threshold] to convert it to an array with an undefined value ([undefined]) so we can test each element. - if (c.threshold && c.threshold.length && ![...c.threshold].every(isNumber)) { - [...c.threshold].forEach((v, i) => { - if (!isNumber(v)) { - const key = i === 0 ? 'threshold0' : 'threshold1'; - errors[id][key].push( - i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdTypeRequired', { - defaultMessage: 'Thresholds must contain a valid number.', - }) - ); - } - }); - } - - if (c.comparator === 'between' && (!c.threshold || c.threshold.length < 2)) { - errors[id].threshold1.push( + if (c.warningThreshold && !c.warningThreshold.length) { + errors[id].warning.threshold0.push( i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', { defaultMessage: 'Threshold is required.', }) ); } + for (const props of [ + { comparator: c.comparator, threshold: c.threshold, type: 'critical' }, + { comparator: c.warningComparator, threshold: c.warningThreshold, type: 'warning' }, + ]) { + // The Threshold component returns an empty array with a length ([empty]) because it's using delete newThreshold[i]. + // We need to use [...c.threshold] to convert it to an array with an undefined value ([undefined]) so we can test each element. + const { comparator, threshold, type } = props as { + comparator?: Comparator; + threshold?: number[]; + type: 'critical' | 'warning'; + }; + if (threshold && threshold.length && ![...threshold].every(isNumber)) { + [...threshold].forEach((v, i) => { + if (!isNumber(v)) { + const key = i === 0 ? 'threshold0' : 'threshold1'; + errors[id][type][key].push( + i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdTypeRequired', { + defaultMessage: 'Thresholds must contain a valid number.', + }) + ); + } + }); + } + + if (comparator === Comparator.BETWEEN && (!threshold || threshold.length < 2)) { + errors[id][type].threshold1.push( + i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', { + defaultMessage: 'Threshold is required.', + }) + ); + } + } if (!c.timeSize) { errors[id].timeWindowSize.push( diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx index 65842089863f3..c98984b5475cd 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx @@ -111,7 +111,9 @@ export const ExpressionChart: React.FC = ({ ); } - const thresholds = expression.threshold.slice().sort(); + const criticalThresholds = expression.threshold.slice().sort(); + const warningThresholds = expression.warningThreshold?.slice().sort() ?? []; + const thresholds = [...criticalThresholds, ...warningThresholds].sort(); // Creating a custom series where the ID is changed to 0 // so that we can get a proper domian @@ -145,108 +147,70 @@ export const ExpressionChart: React.FC = ({ const dataDomain = calculateDomain(series, [metric], false); const domain = { max: Math.max(dataDomain.max, last(thresholds) || dataDomain.max) * 1.1, // add 10% headroom. - min: Math.min(dataDomain.min, first(thresholds) || dataDomain.min), + min: Math.min(dataDomain.min, first(thresholds) || dataDomain.min) * 0.9, // add 10% floor, }; if (domain.min === first(expression.threshold)) { domain.min = domain.min * 0.9; } - const isAbove = [Comparator.GT, Comparator.GT_OR_EQ].includes(expression.comparator); - const isBelow = [Comparator.LT, Comparator.LT_OR_EQ].includes(expression.comparator); const opacity = 0.3; const { timeSize, timeUnit } = expression; const timeLabel = TIME_LABELS[timeUnit as keyof typeof TIME_LABELS]; - return ( - <> - - - - ({ - dataValue: threshold, - }))} - style={{ - line: { - strokeWidth: 2, - stroke: colorTransformer(Color.color1), - opacity: 1, - }, - }} - /> - {thresholds.length === 2 && expression.comparator === Comparator.BETWEEN ? ( - <> - - - ) : null} - {thresholds.length === 2 && expression.comparator === Comparator.OUTSIDE_RANGE ? ( - <> - - & { sortedThresholds: number[]; color: Color; id: string }) => { + if (!comparator || !threshold) return null; + const isAbove = [Comparator.GT, Comparator.GT_OR_EQ].includes(comparator); + const isBelow = [Comparator.LT, Comparator.LT_OR_EQ].includes(comparator); + return ( + <> + ({ + dataValue: t, + }))} + style={{ + line: { + strokeWidth: 2, + stroke: colorTransformer(color), + opacity: 1, + }, + }} + /> + {sortedThresholds.length === 2 && comparator === Comparator.BETWEEN ? ( + <> + - - ) : null} - {isBelow && first(expression.threshold) != null ? ( + }, + ]} + /> + + ) : null} + {sortedThresholds.length === 2 && comparator === Comparator.OUTSIDE_RANGE ? ( + <> = ({ x0: firstTimestamp, x1: lastTimestamp, y0: domain.min, - y1: first(expression.threshold), + y1: first(threshold), }, }, ]} /> - ) : null} - {isAbove && first(expression.threshold) != null ? ( = ({ coordinates: { x0: firstTimestamp, x1: lastTimestamp, - y0: first(expression.threshold), + y0: last(threshold), y1: domain.max, }, }, ]} /> - ) : null} + + ) : null} + {isBelow && first(threshold) != null ? ( + + ) : null} + {isAbove && first(threshold) != null ? ( + + ) : null} + + ); + }; + + return ( + <> + + + + + {expression.warningComparator && expression.warningThreshold && ( + + )} = (props) => { const [isExpanded, setRowState] = useState(true); const toggleRowState = useCallback(() => setRowState(!isExpanded), [isExpanded]); @@ -85,9 +92,14 @@ export const ExpressionRow: React.FC = (props) => { metric, comparator = Comparator.GT, threshold = [], + warningThreshold = [], + warningComparator, } = expression; + const [displayWarningThreshold, setDisplayWarningThreshold] = useState( + Boolean(warningThreshold?.length) + ); - const isMetricPct = useMemo(() => metric && metric.endsWith('.pct'), [metric]); + const isMetricPct = useMemo(() => Boolean(metric && metric.endsWith('.pct')), [metric]); const updateAggType = useCallback( (at: string) => { @@ -114,22 +126,81 @@ export const ExpressionRow: React.FC = (props) => { [expressionId, expression, setAlertParams] ); + const updateWarningComparator = useCallback( + (c?: string) => { + setAlertParams(expressionId, { ...expression, warningComparator: c as Comparator }); + }, + [expressionId, expression, setAlertParams] + ); + + const convertThreshold = useCallback( + (enteredThreshold) => + isMetricPct ? enteredThreshold.map((v: number) => pctToDecimal(v)) : enteredThreshold, + [isMetricPct] + ); + const updateThreshold = useCallback( (enteredThreshold) => { - const t = isMetricPct - ? enteredThreshold.map((v: number) => pctToDecimal(v)) - : enteredThreshold; + const t = convertThreshold(enteredThreshold); if (t.join() !== expression.threshold.join()) { setAlertParams(expressionId, { ...expression, threshold: t }); } }, - [expressionId, expression, isMetricPct, setAlertParams] + [expressionId, expression, convertThreshold, setAlertParams] ); - const displayedThreshold = useMemo(() => { - if (isMetricPct) return threshold.map((v) => decimalToPct(v)); - return threshold; - }, [threshold, isMetricPct]); + const updateWarningThreshold = useCallback( + (enteredThreshold) => { + const t = convertThreshold(enteredThreshold); + if (t.join() !== expression.warningThreshold?.join()) { + setAlertParams(expressionId, { ...expression, warningThreshold: t }); + } + }, + [expressionId, expression, convertThreshold, setAlertParams] + ); + + const toggleWarningThreshold = useCallback(() => { + if (!displayWarningThreshold) { + setDisplayWarningThreshold(true); + setAlertParams(expressionId, { + ...expression, + warningComparator: comparator, + warningThreshold: [], + }); + } else { + setDisplayWarningThreshold(false); + setAlertParams(expressionId, omit(expression, 'warningComparator', 'warningThreshold')); + } + }, [ + displayWarningThreshold, + setDisplayWarningThreshold, + setAlertParams, + comparator, + expression, + expressionId, + ]); + + const criticalThresholdExpression = ( + + ); + + const warningThresholdExpression = displayWarningThreshold && ( + + ); return ( <> @@ -187,26 +258,62 @@ export const ExpressionRow: React.FC = (props) => { /> )} - - - - {isMetricPct && ( -
- % -
- )} + {!displayWarningThreshold && criticalThresholdExpression} + {displayWarningThreshold && ( + <> + + {criticalThresholdExpression} + + + + + + {warningThresholdExpression} + + + + + + + )} + {!displayWarningThreshold && ( + <> + {' '} + + + + + + + + )}
{canDelete && ( @@ -227,6 +334,44 @@ export const ExpressionRow: React.FC = (props) => { ); }; +const ThresholdElement: React.FC<{ + updateComparator: (c?: string) => void; + updateThreshold: (t?: number[]) => void; + threshold: MetricExpression['threshold']; + isMetricPct: boolean; + comparator: MetricExpression['comparator']; + errors: IErrorObject; +}> = ({ updateComparator, updateThreshold, threshold, isMetricPct, comparator, errors }) => { + const displayedThreshold = useMemo(() => { + if (isMetricPct) return threshold.map((v) => decimalToPct(v)); + return threshold; + }, [threshold, isMetricPct]); + + return ( + <> + + + + {isMetricPct && ( +
+ % +
+ )} + + ); +}; + export const aggregationType: { [key: string]: any } = { avg: { text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.avg', { diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx index bab396df9da0d..69b2f1d1bcc8f 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx @@ -25,8 +25,14 @@ export function validateMetricThreshold({ aggField: string[]; timeSizeUnit: string[]; timeWindowSize: string[]; - threshold0: string[]; - threshold1: string[]; + critical: { + threshold0: string[]; + threshold1: string[]; + }; + warning: { + threshold0: string[]; + threshold1: string[]; + }; metric: string[]; }; } = {}; @@ -44,8 +50,14 @@ export function validateMetricThreshold({ aggField: [], timeSizeUnit: [], timeWindowSize: [], - threshold0: [], - threshold1: [], + critical: { + threshold0: [], + threshold1: [], + }, + warning: { + threshold0: [], + threshold1: [], + }, metric: [], }; if (!c.aggType) { @@ -57,36 +69,54 @@ export function validateMetricThreshold({ } if (!c.threshold || !c.threshold.length) { - errors[id].threshold0.push( + errors[id].critical.threshold0.push( i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', { defaultMessage: 'Threshold is required.', }) ); } - // The Threshold component returns an empty array with a length ([empty]) because it's using delete newThreshold[i]. - // We need to use [...c.threshold] to convert it to an array with an undefined value ([undefined]) so we can test each element. - if (c.threshold && c.threshold.length && ![...c.threshold].every(isNumber)) { - [...c.threshold].forEach((v, i) => { - if (!isNumber(v)) { - const key = i === 0 ? 'threshold0' : 'threshold1'; - errors[id][key].push( - i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdTypeRequired', { - defaultMessage: 'Thresholds must contain a valid number.', - }) - ); - } - }); - } - - if (c.comparator === Comparator.BETWEEN && (!c.threshold || c.threshold.length < 2)) { - errors[id].threshold1.push( + if (c.warningThreshold && !c.warningThreshold.length) { + errors[id].warning.threshold0.push( i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', { defaultMessage: 'Threshold is required.', }) ); } + for (const props of [ + { comparator: c.comparator, threshold: c.threshold, type: 'critical' }, + { comparator: c.warningComparator, threshold: c.warningThreshold, type: 'warning' }, + ]) { + // The Threshold component returns an empty array with a length ([empty]) because it's using delete newThreshold[i]. + // We need to use [...c.threshold] to convert it to an array with an undefined value ([undefined]) so we can test each element. + const { comparator, threshold, type } = props as { + comparator?: Comparator; + threshold?: number[]; + type: 'critical' | 'warning'; + }; + if (threshold && threshold.length && ![...threshold].every(isNumber)) { + [...threshold].forEach((v, i) => { + if (!isNumber(v)) { + const key = i === 0 ? 'threshold0' : 'threshold1'; + errors[id][type][key].push( + i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdTypeRequired', { + defaultMessage: 'Thresholds must contain a valid number.', + }) + ); + } + }); + } + + if (comparator === Comparator.BETWEEN && (!threshold || threshold.length < 2)) { + errors[id][type].threshold1.push( + i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', { + defaultMessage: 'Threshold is required.', + }) + ); + } + } + if (!c.timeSize) { errors[id].timeWindowSize.push( i18n.translate('xpack.infra.metrics.alertFlyout.error.timeRequred', { diff --git a/x-pack/plugins/infra/server/lib/alerting/common/messages.ts b/x-pack/plugins/infra/server/lib/alerting/common/messages.ts index 9f0be1679448f..b692629209849 100644 --- a/x-pack/plugins/infra/server/lib/alerting/common/messages.ts +++ b/x-pack/plugins/infra/server/lib/alerting/common/messages.ts @@ -19,6 +19,9 @@ export const stateToAlertMessage = { [AlertStates.ALERT]: i18n.translate('xpack.infra.metrics.alerting.threshold.alertState', { defaultMessage: 'ALERT', }), + [AlertStates.WARNING]: i18n.translate('xpack.infra.metrics.alerting.threshold.warningState', { + defaultMessage: 'WARNING', + }), [AlertStates.NO_DATA]: i18n.translate('xpack.infra.metrics.alerting.threshold.noDataState', { defaultMessage: 'NO DATA', }), diff --git a/x-pack/plugins/infra/server/lib/alerting/common/types.ts b/x-pack/plugins/infra/server/lib/alerting/common/types.ts index e4db2600e316d..0b809429de0d2 100644 --- a/x-pack/plugins/infra/server/lib/alerting/common/types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/common/types.ts @@ -29,6 +29,15 @@ export enum Aggregators { export enum AlertStates { OK, ALERT, + WARNING, NO_DATA, ERROR, } + +export interface PreviewResult { + fired: number; + warning: number; + noData: number; + error: number; + notifications: number; +} diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts index 16f74d579969a..ea37f7adda7c4 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts @@ -26,6 +26,7 @@ import { getNodes } from '../../../routes/snapshot/lib/get_nodes'; type ConditionResult = InventoryMetricConditions & { shouldFire: boolean[]; + shouldWarn: boolean[]; currentValue: number; isNoData: boolean[]; isError: boolean; @@ -39,8 +40,8 @@ export const evaluateCondition = async ( filterQuery?: string, lookbackSize?: number ): Promise> => { - const { comparator, metric, customMetric } = condition; - let { threshold } = condition; + const { comparator, warningComparator, metric, customMetric } = condition; + let { threshold, warningThreshold } = condition; const timerange = { to: Date.now(), @@ -62,19 +63,22 @@ export const evaluateCondition = async ( ); threshold = threshold.map((n) => convertMetricValue(metric, n)); - - const comparisonFunction = comparatorMap[comparator]; + warningThreshold = warningThreshold?.map((n) => convertMetricValue(metric, n)); + + const valueEvaluator = (value?: DataValue, t?: number[], c?: Comparator) => { + if (value === undefined || value === null || !t || !c) return [false]; + const comparisonFunction = comparatorMap[c]; + return Array.isArray(value) + ? value.map((v) => comparisonFunction(Number(v), t)) + : [comparisonFunction(value as number, t)]; + }; const result = mapValues(currentValues, (value) => { if (isTooManyBucketsPreviewException(value)) throw value; return { ...condition, - shouldFire: - value !== undefined && - value !== null && - (Array.isArray(value) - ? value.map((v) => comparisonFunction(Number(v), threshold)) - : [comparisonFunction(value as number, threshold)]), + shouldFire: valueEvaluator(value, threshold, comparator), + shouldWarn: valueEvaluator(value, warningThreshold, warningComparator), isNoData: Array.isArray(value) ? value.map((v) => v === null) : [value === null], isError: value === undefined, currentValue: getCurrentValue(value), @@ -90,6 +94,7 @@ const getCurrentValue: (value: any) => number = (value) => { return NaN; }; +type DataValue = number | null | Array; const getData = async ( callCluster: AlertServices['callCluster'], nodeType: InventoryItemType, diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 2658fa6820274..a15f1010194a5 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -81,6 +81,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = // Grab the result of the most recent bucket last(result[item].shouldFire) ); + const shouldAlertWarn = results.every((result) => last(result[item].shouldWarn)); // AND logic; because we need to evaluate all criteria, if one of them reports no data then the // whole alert is in a No Data/Error state @@ -93,12 +94,20 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = ? AlertStates.NO_DATA : shouldAlertFire ? AlertStates.ALERT + : shouldAlertWarn + ? AlertStates.WARNING : AlertStates.OK; let reason; - if (nextState === AlertStates.ALERT) { + if (nextState === AlertStates.ALERT || nextState === AlertStates.WARNING) { reason = results - .map((result) => buildReasonWithVerboseMetricName(result[item], buildFiredAlertReason)) + .map((result) => + buildReasonWithVerboseMetricName( + result[item], + buildFiredAlertReason, + nextState === AlertStates.WARNING + ) + ) .join('\n'); } else if (nextState === AlertStates.OK && prevState?.alertState === AlertStates.ALERT) { /* @@ -125,7 +134,11 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = } if (reason) { const actionGroupId = - nextState === AlertStates.OK ? RecoveredActionGroup.id : FIRED_ACTIONS_ID; + nextState === AlertStates.OK + ? RecoveredActionGroup.id + : nextState === AlertStates.WARNING + ? WARNING_ACTIONS.id + : FIRED_ACTIONS.id; alertInstance.scheduleActions( /** * TODO: We're lying to the compiler here as explicitly calling `scheduleActions` on @@ -152,7 +165,11 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = } }; -const buildReasonWithVerboseMetricName = (resultItem: any, buildReason: (r: any) => string) => { +const buildReasonWithVerboseMetricName = ( + resultItem: any, + buildReason: (r: any) => string, + useWarningThreshold?: boolean +) => { if (!resultItem) return ''; const resultWithVerboseMetricName = { ...resultItem, @@ -162,6 +179,8 @@ const buildReasonWithVerboseMetricName = (resultItem: any, buildReason: (r: any) ? getCustomMetricLabel(resultItem.customMetric) : resultItem.metric), currentValue: formatMetric(resultItem.metric, resultItem.currentValue), + threshold: useWarningThreshold ? resultItem.warningThreshold! : resultItem.threshold, + comparator: useWarningThreshold ? resultItem.warningComparator! : resultItem.comparator, }; return buildReason(resultWithVerboseMetricName); }; @@ -177,11 +196,18 @@ const mapToConditionsLookup = ( {} ); -export const FIRED_ACTIONS_ID = 'metrics.invenotry_threshold.fired'; +export const FIRED_ACTIONS_ID = 'metrics.inventory_threshold.fired'; export const FIRED_ACTIONS: ActionGroup = { id: FIRED_ACTIONS_ID, name: i18n.translate('xpack.infra.metrics.alerting.inventory.threshold.fired', { - defaultMessage: 'Fired', + defaultMessage: 'Alert', + }), +}; +export const WARNING_ACTIONS_ID = 'metrics.inventory_threshold.warning'; +export const WARNING_ACTIONS = { + id: WARNING_ACTIONS_ID, + name: i18n.translate('xpack.infra.metrics.alerting.threshold.warning', { + defaultMessage: 'Warning', }), }; diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/preview_inventory_metric_threshold_alert.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/preview_inventory_metric_threshold_alert.ts index 528c0f92d20e7..5fff76260e5c6 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/preview_inventory_metric_threshold_alert.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/preview_inventory_metric_threshold_alert.ts @@ -7,6 +7,7 @@ import { Unit } from '@elastic/datemath'; import { first } from 'lodash'; +import { PreviewResult } from '../common/types'; import { InventoryMetricConditions } from './types'; import { TOO_MANY_BUCKETS_PREVIEW_EXCEPTION, @@ -35,7 +36,9 @@ interface PreviewInventoryMetricThresholdAlertParams { alertOnNoData: boolean; } -export const previewInventoryMetricThresholdAlert = async ({ +export const previewInventoryMetricThresholdAlert: ( + params: PreviewInventoryMetricThresholdAlertParams +) => Promise = async ({ callCluster, params, source, @@ -43,7 +46,7 @@ export const previewInventoryMetricThresholdAlert = async ({ alertInterval, alertThrottle, alertOnNoData, -}: PreviewInventoryMetricThresholdAlertParams) => { +}) => { const { criteria, filterQuery, nodeType } = params as InventoryMetricThresholdParams; if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); @@ -74,6 +77,7 @@ export const previewInventoryMetricThresholdAlert = async ({ const numberOfResultBuckets = lookbackSize; const numberOfExecutionBuckets = Math.floor(numberOfResultBuckets / alertResultsPerExecution); let numberOfTimesFired = 0; + let numberOfTimesWarned = 0; let numberOfNoDataResults = 0; let numberOfErrors = 0; let numberOfNotifications = 0; @@ -88,6 +92,9 @@ export const previewInventoryMetricThresholdAlert = async ({ const shouldFire = result[item].shouldFire as boolean[]; return shouldFire[mappedBucketIndex]; }); + const allConditionsWarnInMappedBucket = + !allConditionsFiredInMappedBucket && + results.every((result) => result[item].shouldWarn[mappedBucketIndex]); const someConditionsNoDataInMappedBucket = results.some((result) => { const hasNoData = result[item].isNoData as boolean[]; return hasNoData[mappedBucketIndex]; @@ -108,6 +115,9 @@ export const previewInventoryMetricThresholdAlert = async ({ } else if (allConditionsFiredInMappedBucket) { numberOfTimesFired++; notifyWithThrottle(); + } else if (allConditionsWarnInMappedBucket) { + numberOfTimesWarned++; + notifyWithThrottle(); } else if (throttleTracker > 0) { throttleTracker++; } @@ -115,7 +125,13 @@ export const previewInventoryMetricThresholdAlert = async ({ throttleTracker = 0; } } - return [numberOfTimesFired, numberOfNoDataResults, numberOfErrors, numberOfNotifications]; + return { + fired: numberOfTimesFired, + warning: numberOfTimesWarned, + noData: numberOfNoDataResults, + error: numberOfErrors, + notifications: numberOfNotifications, + }; }); return previewResults; diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts index 4ae1a0e4d5d49..6c439225d9d00 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts @@ -7,11 +7,17 @@ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; -import { AlertType, AlertInstanceState, AlertInstanceContext } from '../../../../../alerts/server'; +import { + AlertType, + AlertInstanceState, + AlertInstanceContext, + ActionGroupIdsOf, +} from '../../../../../alerts/server'; import { createInventoryMetricThresholdExecutor, FIRED_ACTIONS, FIRED_ACTIONS_ID, + WARNING_ACTIONS, } from './inventory_metric_threshold_executor'; import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, Comparator } from './types'; import { InfraBackendLibs } from '../../infra_types'; @@ -25,7 +31,6 @@ import { metricActionVariableDescription, thresholdActionVariableDescription, } from '../common/messages'; -import { RecoveredActionGroupId } from '../../../../../alerts/common'; const condition = schema.object({ threshold: schema.arrayOf(schema.number()), @@ -33,6 +38,8 @@ const condition = schema.object({ timeUnit: schema.string(), timeSize: schema.number(), metric: schema.string(), + warningThreshold: schema.maybe(schema.arrayOf(schema.number())), + warningComparator: schema.maybe(oneOfLiterals(Object.values(Comparator))), customMetric: schema.maybe( schema.object({ type: schema.literal('custom'), @@ -44,7 +51,9 @@ const condition = schema.object({ ), }); -export type InventoryMetricThresholdAllowedActionGroups = typeof FIRED_ACTIONS_ID; +export type InventoryMetricThresholdAllowedActionGroups = ActionGroupIdsOf< + typeof FIRED_ACTIONS | typeof WARNING_ACTIONS +>; export const registerMetricInventoryThresholdAlertType = ( libs: InfraBackendLibs @@ -56,8 +65,7 @@ export const registerMetricInventoryThresholdAlertType = ( Record, AlertInstanceState, AlertInstanceContext, - InventoryMetricThresholdAllowedActionGroups, - RecoveredActionGroupId + InventoryMetricThresholdAllowedActionGroups > => ({ id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, name: i18n.translate('xpack.infra.metrics.inventory.alertName', { @@ -78,7 +86,7 @@ export const registerMetricInventoryThresholdAlertType = ( ), }, defaultActionGroupId: FIRED_ACTIONS_ID, - actionGroups: [FIRED_ACTIONS], + actionGroups: [FIRED_ACTIONS, WARNING_ACTIONS], producer: 'infrastructure', minimumLicenseRequired: 'basic', executor: createInventoryMetricThresholdExecutor(libs), diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts index 28c41de9b10d6..120fa47c079ab 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts @@ -22,4 +22,6 @@ export interface InventoryMetricConditions { threshold: number[]; comparator: Comparator; customMetric?: SnapshotCustomMetricInput; + warningThreshold?: number[]; + warningComparator?: Comparator; } diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts index f9661e2cd56bb..029445a441eea 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts @@ -60,8 +60,17 @@ export const evaluateAlert = { + if (!t || !c) return [false]; + const comparisonFunction = comparatorMap[c]; + return Array.isArray(points) + ? points.map( + (point) => t && typeof point.value === 'number' && comparisonFunction(point.value, t) + ) + : [false]; + }; + return mapValues(currentValues, (points: any[] | typeof NaN | null) => { if (isTooManyBucketsPreviewException(points)) throw points; return { @@ -69,12 +78,8 @@ export const evaluateAlert = - typeof point.value === 'number' && comparisonFunction(point.value, threshold) - ) - : [false], + shouldFire: pointsEvaluator(points, threshold, comparator), + shouldWarn: pointsEvaluator(points, warningThreshold, warningComparator), isNoData: Array.isArray(points) ? points.map((point) => point?.value === null || point === null) : [points === null], diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 17b9ab1cab907..b822d71b3f812 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -18,7 +18,7 @@ import { stateToAlertMessage, } from '../common/messages'; import { createFormatter } from '../../../../common/formatters'; -import { AlertStates } from './types'; +import { AlertStates, Comparator } from './types'; import { evaluateAlert, EvaluatedAlertParams } from './lib/evaluate_alert'; import { MetricThresholdAlertExecutorOptions, @@ -60,6 +60,7 @@ export const createMetricThresholdExecutor = ( // Grab the result of the most recent bucket last(result[group].shouldFire) ); + const shouldAlertWarn = alertResults.every((result) => last(result[group].shouldWarn)); // AND logic; because we need to evaluate all criteria, if one of them reports no data then the // whole alert is in a No Data/Error state const isNoData = alertResults.some((result) => last(result[group].isNoData)); @@ -71,12 +72,18 @@ export const createMetricThresholdExecutor = ( ? AlertStates.NO_DATA : shouldAlertFire ? AlertStates.ALERT + : shouldAlertWarn + ? AlertStates.WARNING : AlertStates.OK; let reason; - if (nextState === AlertStates.ALERT) { + if (nextState === AlertStates.ALERT || nextState === AlertStates.WARNING) { reason = alertResults - .map((result) => buildFiredAlertReason(formatAlertResult(result[group]))) + .map((result) => + buildFiredAlertReason( + formatAlertResult(result[group], nextState === AlertStates.WARNING) + ) + ) .join('\n'); } else if (nextState === AlertStates.OK && prevState?.alertState === AlertStates.ALERT) { /* @@ -105,7 +112,11 @@ export const createMetricThresholdExecutor = ( const firstResult = first(alertResults); const timestamp = (firstResult && firstResult[group].timestamp) ?? moment().toISOString(); const actionGroupId = - nextState === AlertStates.OK ? RecoveredActionGroup.id : FIRED_ACTIONS.id; + nextState === AlertStates.OK + ? RecoveredActionGroup.id + : nextState === AlertStates.WARNING + ? WARNING_ACTIONS.id + : FIRED_ACTIONS.id; alertInstance.scheduleActions(actionGroupId, { group, alertState: stateToAlertMessage[nextState], @@ -132,7 +143,14 @@ export const createMetricThresholdExecutor = ( export const FIRED_ACTIONS = { id: 'metrics.threshold.fired', name: i18n.translate('xpack.infra.metrics.alerting.threshold.fired', { - defaultMessage: 'Fired', + defaultMessage: 'Alert', + }), +}; + +export const WARNING_ACTIONS = { + id: 'metrics.threshold.warning', + name: i18n.translate('xpack.infra.metrics.alerting.threshold.warning', { + defaultMessage: 'Warning', }), }; @@ -152,9 +170,20 @@ const formatAlertResult = ( metric: string; currentValue: number; threshold: number[]; - } & AlertResult + comparator: Comparator; + warningThreshold?: number[]; + warningComparator?: Comparator; + } & AlertResult, + useWarningThreshold?: boolean ) => { - const { metric, currentValue, threshold } = alertResult; + const { + metric, + currentValue, + threshold, + comparator, + warningThreshold, + warningComparator, + } = alertResult; const noDataValue = i18n.translate( 'xpack.infra.metrics.alerting.threshold.noDataFormattedValue', { @@ -167,12 +196,17 @@ const formatAlertResult = ( currentValue: currentValue ?? noDataValue, }; const formatter = createFormatter('percent'); + const thresholdToFormat = useWarningThreshold ? warningThreshold! : threshold; + const comparatorToFormat = useWarningThreshold ? warningComparator! : comparator; return { ...alertResult, currentValue: currentValue !== null && typeof currentValue !== 'undefined' ? formatter(currentValue) : noDataValue, - threshold: Array.isArray(threshold) ? threshold.map((v: number) => formatter(v)) : threshold, + threshold: Array.isArray(thresholdToFormat) + ? thresholdToFormat.map((v: number) => formatter(v)) + : thresholdToFormat, + comparator: comparatorToFormat, }; }; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.test.ts index 8576fd7b59299..1adca25504b1f 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.test.ts @@ -20,10 +20,10 @@ describe('Previewing the metric threshold alert type', () => { alertThrottle: '1m', alertOnNoData: true, }); - const [firedResults, noDataResults, errorResults, notifications] = ungroupedResult; - expect(firedResults).toBe(30); - expect(noDataResults).toBe(0); - expect(errorResults).toBe(0); + const { fired, noData, error, notifications } = ungroupedResult; + expect(fired).toBe(30); + expect(noData).toBe(0); + expect(error).toBe(0); expect(notifications).toBe(30); }); @@ -35,10 +35,10 @@ describe('Previewing the metric threshold alert type', () => { alertThrottle: '3m', alertOnNoData: true, }); - const [firedResults, noDataResults, errorResults, notifications] = ungroupedResult; - expect(firedResults).toBe(10); - expect(noDataResults).toBe(0); - expect(errorResults).toBe(0); + const { fired, noData, error, notifications } = ungroupedResult; + expect(fired).toBe(10); + expect(noData).toBe(0); + expect(error).toBe(0); expect(notifications).toBe(10); }); test('returns the expected results using a bucket interval longer than the alert interval', async () => { @@ -49,10 +49,10 @@ describe('Previewing the metric threshold alert type', () => { alertThrottle: '30s', alertOnNoData: true, }); - const [firedResults, noDataResults, errorResults, notifications] = ungroupedResult; - expect(firedResults).toBe(60); - expect(noDataResults).toBe(0); - expect(errorResults).toBe(0); + const { fired, noData, error, notifications } = ungroupedResult; + expect(fired).toBe(60); + expect(noData).toBe(0); + expect(error).toBe(0); expect(notifications).toBe(60); }); test('returns the expected results using a throttle interval longer than the alert interval', async () => { @@ -63,10 +63,10 @@ describe('Previewing the metric threshold alert type', () => { alertThrottle: '3m', alertOnNoData: true, }); - const [firedResults, noDataResults, errorResults, notifications] = ungroupedResult; - expect(firedResults).toBe(30); - expect(noDataResults).toBe(0); - expect(errorResults).toBe(0); + const { fired, noData, error, notifications } = ungroupedResult; + expect(fired).toBe(30); + expect(noData).toBe(0); + expect(error).toBe(0); expect(notifications).toBe(15); }); }); @@ -83,15 +83,25 @@ describe('Previewing the metric threshold alert type', () => { alertThrottle: '1m', alertOnNoData: true, }); - const [firedResultsA, noDataResultsA, errorResultsA, notificationsA] = resultA; - expect(firedResultsA).toBe(30); - expect(noDataResultsA).toBe(0); - expect(errorResultsA).toBe(0); + const { + fired: firedA, + noData: noDataA, + error: errorA, + notifications: notificationsA, + } = resultA; + expect(firedA).toBe(30); + expect(noDataA).toBe(0); + expect(errorA).toBe(0); expect(notificationsA).toBe(30); - const [firedResultsB, noDataResultsB, errorResultsB, notificationsB] = resultB; - expect(firedResultsB).toBe(60); - expect(noDataResultsB).toBe(0); - expect(errorResultsB).toBe(0); + const { + fired: firedB, + noData: noDataB, + error: errorB, + notifications: notificationsB, + } = resultB; + expect(firedB).toBe(60); + expect(noDataB).toBe(0); + expect(errorB).toBe(0); expect(notificationsB).toBe(60); }); }); @@ -113,10 +123,10 @@ describe('Previewing the metric threshold alert type', () => { alertThrottle: '1m', alertOnNoData: true, }); - const [firedResults, noDataResults, errorResults, notifications] = ungroupedResult; - expect(firedResults).toBe(25); - expect(noDataResults).toBe(10); - expect(errorResults).toBe(0); + const { fired, noData, error, notifications } = ungroupedResult; + expect(fired).toBe(25); + expect(noData).toBe(10); + expect(error).toBe(0); expect(notifications).toBe(35); }); }); diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts index ac6372a94b1fe..b9fa6659d5fcd 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts @@ -14,6 +14,7 @@ import { import { ILegacyScopedClusterClient } from '../../../../../../../src/core/server'; import { InfraSource } from '../../../../common/http_api/source_api'; import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; +import { PreviewResult } from '../common/types'; import { MetricExpressionParams } from './types'; import { evaluateAlert } from './lib/evaluate_alert'; @@ -39,7 +40,7 @@ export const previewMetricThresholdAlert: ( params: PreviewMetricThresholdAlertParams, iterations?: number, precalculatedNumberOfGroups?: number -) => Promise = async ( +) => Promise = async ( { callCluster, params, @@ -98,6 +99,7 @@ export const previewMetricThresholdAlert: ( numberOfResultBuckets / alertResultsPerExecution ); let numberOfTimesFired = 0; + let numberOfTimesWarned = 0; let numberOfNoDataResults = 0; let numberOfErrors = 0; let numberOfNotifications = 0; @@ -111,6 +113,9 @@ export const previewMetricThresholdAlert: ( const allConditionsFiredInMappedBucket = alertResults.every( (alertResult) => alertResult[group].shouldFire[mappedBucketIndex] ); + const allConditionsWarnInMappedBucket = + !allConditionsFiredInMappedBucket && + alertResults.every((alertResult) => alertResult[group].shouldWarn[mappedBucketIndex]); const someConditionsNoDataInMappedBucket = alertResults.some((alertResult) => { const hasNoData = alertResult[group].isNoData as boolean[]; return hasNoData[mappedBucketIndex]; @@ -131,6 +136,9 @@ export const previewMetricThresholdAlert: ( } else if (allConditionsFiredInMappedBucket) { numberOfTimesFired++; notifyWithThrottle(); + } else if (allConditionsWarnInMappedBucket) { + numberOfTimesWarned++; + notifyWithThrottle(); } else if (throttleTracker > 0) { throttleTracker += alertIntervalInSeconds; } @@ -138,7 +146,13 @@ export const previewMetricThresholdAlert: ( throttleTracker = 0; } } - return [numberOfTimesFired, numberOfNoDataResults, numberOfErrors, numberOfNotifications]; + return { + fired: numberOfTimesFired, + warning: numberOfTimesWarned, + noData: numberOfNoDataResults, + error: numberOfErrors, + notifications: numberOfNotifications, + }; }) ); return previewResults; @@ -199,7 +213,12 @@ export const previewMetricThresholdAlert: ( .reduce((a, b) => { if (!a) return b; if (!b) return a; - return [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]]; + const res = { ...a }; + const entries = (Object.entries(b) as unknown) as Array<[keyof PreviewResult, number]>; + for (const [key, value] of entries) { + res[key] += value; + } + return res; }) ); return zippedResult; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts index 6d8790f4f430c..e5e3a7bff329e 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts @@ -15,7 +15,11 @@ import { ActionGroupIdsOf, } from '../../../../../alerts/server'; import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api/metrics_explorer'; -import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor'; +import { + createMetricThresholdExecutor, + FIRED_ACTIONS, + WARNING_ACTIONS, +} from './metric_threshold_executor'; import { METRIC_THRESHOLD_ALERT_TYPE_ID, Comparator } from './types'; import { InfraBackendLibs } from '../../infra_types'; import { oneOfLiterals, validateIsStringElasticsearchJSONFilter } from '../common/utils'; @@ -37,7 +41,7 @@ export type MetricThresholdAlertType = AlertType< Record, AlertInstanceState, AlertInstanceContext, - ActionGroupIdsOf + ActionGroupIdsOf >; export type MetricThresholdAlertExecutorOptions = AlertExecutorOptions< /** @@ -47,7 +51,7 @@ export type MetricThresholdAlertExecutorOptions = AlertExecutorOptions< Record, AlertInstanceState, AlertInstanceContext, - ActionGroupIdsOf + ActionGroupIdsOf >; export function registerMetricThresholdAlertType(libs: InfraBackendLibs): MetricThresholdAlertType { @@ -56,6 +60,8 @@ export function registerMetricThresholdAlertType(libs: InfraBackendLibs): Metric comparator: oneOfLiterals(Object.values(Comparator)), timeUnit: schema.string(), timeSize: schema.number(), + warningThreshold: schema.maybe(schema.arrayOf(schema.number())), + warningComparator: schema.maybe(oneOfLiterals(Object.values(Comparator))), }; const nonCountCriterion = schema.object({ @@ -92,7 +98,7 @@ export function registerMetricThresholdAlertType(libs: InfraBackendLibs): Metric ), }, defaultActionGroupId: FIRED_ACTIONS.id, - actionGroups: [FIRED_ACTIONS], + actionGroups: [FIRED_ACTIONS, WARNING_ACTIONS], minimumLicenseRequired: 'basic', executor: createMetricThresholdExecutor(libs), actionVariables: { diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts index f876e40d9cd1f..37f21022f183d 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts @@ -29,6 +29,8 @@ interface BaseMetricExpressionParams { sourceId?: string; threshold: number[]; comparator: Comparator; + warningComparator?: Comparator; + warningThreshold?: number[]; } interface NonCountMetricExpressionParams extends BaseMetricExpressionParams { diff --git a/x-pack/plugins/infra/server/routes/alerting/preview.ts b/x-pack/plugins/infra/server/routes/alerting/preview.ts index ba16221108958..cc2cf4092520a 100644 --- a/x-pack/plugins/infra/server/routes/alerting/preview.ts +++ b/x-pack/plugins/infra/server/routes/alerting/preview.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { PreviewResult } from '../../lib/alerting/common/types'; import { METRIC_THRESHOLD_ALERT_TYPE_ID, METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, @@ -65,29 +66,9 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs) alertOnNoData, }); - const numberOfGroups = previewResult.length; - const resultTotals = previewResult.reduce( - (totals, [firedResult, noDataResult, errorResult, notifications]) => { - return { - ...totals, - fired: totals.fired + firedResult, - noData: totals.noData + noDataResult, - error: totals.error + errorResult, - notifications: totals.notifications + notifications, - }; - }, - { - fired: 0, - noData: 0, - error: 0, - notifications: 0, - } - ); + const payload = processPreviewResults(previewResult); return response.ok({ - body: alertPreviewSuccessResponsePayloadRT.encode({ - numberOfGroups, - resultTotals, - }), + body: alertPreviewSuccessResponsePayloadRT.encode(payload), }); } case METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID: { @@ -102,30 +83,10 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs) alertOnNoData, }); - const numberOfGroups = previewResult.length; - const resultTotals = previewResult.reduce( - (totals, [firedResult, noDataResult, errorResult, notifications]) => { - return { - ...totals, - fired: totals.fired + firedResult, - noData: totals.noData + noDataResult, - error: totals.error + errorResult, - notifications: totals.notifications + notifications, - }; - }, - { - fired: 0, - noData: 0, - error: 0, - notifications: 0, - } - ); + const payload = processPreviewResults(previewResult); return response.ok({ - body: alertPreviewSuccessResponsePayloadRT.encode({ - numberOfGroups, - resultTotals, - }), + body: alertPreviewSuccessResponsePayloadRT.encode(payload), }); } default: @@ -150,3 +111,27 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs) }) ); }; + +const processPreviewResults = (previewResult: PreviewResult[]) => { + const numberOfGroups = previewResult.length; + const resultTotals = previewResult.reduce( + (totals, { fired, warning, noData, error, notifications }) => { + return { + ...totals, + fired: totals.fired + fired, + warning: totals.warning + warning, + noData: totals.noData + noData, + error: totals.error + error, + notifications: totals.notifications + notifications, + }; + }, + { + fired: 0, + warning: 0, + noData: 0, + error: 0, + notifications: 0, + } + ); + return { numberOfGroups, resultTotals }; +}; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 278294dea9449..6e9d0329eaff8 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10175,10 +10175,6 @@ "xpack.infra.metrics.alertFlyout.alertPreviewError": "このアラート条件をプレビューするときにエラーが発生しました", "xpack.infra.metrics.alertFlyout.alertPreviewErrorDesc": "しばらくたってから再試行するか、詳細を確認してください。", "xpack.infra.metrics.alertFlyout.alertPreviewErrorResult": "一部のデータを評価するときにエラーが発生しました。", - "xpack.infra.metrics.alertFlyout.alertPreviewGroupsAcross": "すべてを対象にする", - "xpack.infra.metrics.alertFlyout.alertPreviewNoDataResult": "データなしの件数:{boldedResultsNumber}", - "xpack.infra.metrics.alertFlyout.alertPreviewNoDataResultNumber": "{noData, plural, other {件の結果がありました}}", - "xpack.infra.metrics.alertFlyout.alertPreviewResult": "{firedTimes} 回発生しました", "xpack.infra.metrics.alertFlyout.alertPreviewTotalNotifications": "結果として、このアラートは、「{alertThrottle}」に関して選択した[通知間隔]設定に基づいて{notifications}を送信しました。", "xpack.infra.metrics.alertFlyout.alertPreviewTotalNotificationsNumber": "{notifs, plural, other {#通知}}", "xpack.infra.metrics.alertFlyout.conditions": "条件", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4704cb07d27b0..eeda709104479 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10202,12 +10202,7 @@ "xpack.infra.metrics.alertFlyout.alertPreviewError": "尝试预览此告警条件时发生错误", "xpack.infra.metrics.alertFlyout.alertPreviewErrorDesc": "请稍后重试或查看详情了解更多信息。", "xpack.infra.metrics.alertFlyout.alertPreviewErrorResult": "尝试评估部分数据时发生错误。", - "xpack.infra.metrics.alertFlyout.alertPreviewGroups": "{numberOfGroups, plural,other {# 个 {groupName}}}", - "xpack.infra.metrics.alertFlyout.alertPreviewGroupsAcross": "在", - "xpack.infra.metrics.alertFlyout.alertPreviewNoDataResult": "存在 {boldedResultsNumber}无数据结果。", - "xpack.infra.metrics.alertFlyout.alertPreviewNoDataResultNumber": "{noData, plural, other {# 个结果}}", - "xpack.infra.metrics.alertFlyout.alertPreviewResult": "有 {firedTimes}", - "xpack.infra.metrics.alertFlyout.alertPreviewResultLookback": "在过去 {lookback} 满足此告警的条件。", + "xpack.infra.metrics.alertFlyout.alertPreviewGroups": "在 {numberOfGroups, plural,other {# 个 {groupName}}}", "xpack.infra.metrics.alertFlyout.alertPreviewTotalNotifications": "因此,此告警将根据“{alertThrottle}”的选定“通知频率”设置发送{notifications}。", "xpack.infra.metrics.alertFlyout.alertPreviewTotalNotificationsNumber": "{notifs, plural, other {# 个通知}}", "xpack.infra.metrics.alertFlyout.conditions": "条件", From ad2f5d7918cf0e5cadfeba7fb7671abe469f58e8 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Tue, 9 Feb 2021 15:19:08 -0600 Subject: [PATCH 2/9] [Workplace Search] Fix paths to nest correctly (#90831) We have serveral places that I went up one level too many when getting shared components. This refactors that to align with other usages. --- .../components/add_source/add_source_list.test.tsx | 2 +- .../content_sources/components/add_source/add_source_list.tsx | 2 +- .../components/add_source/configure_oauth.test.tsx | 2 +- .../content_sources/components/add_source/configure_oauth.tsx | 4 ++-- .../components/add_source/connect_instance.tsx | 2 +- .../content_sources/components/add_source/re_authenticate.tsx | 2 +- .../content_sources/components/add_source/save_config.tsx | 2 +- .../content_sources/components/add_source/source_features.tsx | 2 +- .../views/content_sources/components/source_content.test.tsx | 2 +- .../views/content_sources/components/source_content.tsx | 2 +- .../views/content_sources/private_sources.tsx | 2 +- .../workplace_search/views/content_sources/sources_router.tsx | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.test.tsx index 6da348c6e2755..90da349ea4f27 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.test.tsx @@ -19,7 +19,7 @@ import { shallow } from 'enzyme'; import { EuiEmptyPrompt, EuiFieldSearch } from '@elastic/eui'; -import { Loading } from '../../../../../../applications/shared/loading'; +import { Loading } from '../../../../../shared/loading'; import { ViewContentHeader } from '../../../../components/shared/view_content_header'; import { AddSourceList } from './add_source_list'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx index d026782d12540..372187485f277 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx @@ -19,7 +19,7 @@ import { EuiEmptyPrompt, } from '@elastic/eui'; -import { Loading } from '../../../../../../applications/shared/loading'; +import { Loading } from '../../../../../shared/loading'; import { AppLogic } from '../../../../app_logic'; import noSharedSourcesIcon from '../../../../assets/share_circle.svg'; import { ContentSection } from '../../../../components/shared/content_section'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.test.tsx index 985488558c984..533dfcda70db1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.test.tsx @@ -14,7 +14,7 @@ import { shallow } from 'enzyme'; import { EuiCheckboxGroup } from '@elastic/eui'; -import { Loading } from '../../../../../../applications/shared/loading'; +import { Loading } from '../../../../../shared/loading'; import { ConfigureOauth } from './configure_oauth'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.tsx index eb7b61ef658db..69a2fbd1495c7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.tsx @@ -21,8 +21,8 @@ import { } from '@elastic/eui'; import { EuiCheckboxGroupIdToSelectedMap } from '@elastic/eui/src/components/form/checkbox/checkbox_group'; -import { Loading } from '../../../../../../applications/shared/loading'; -import { parseQueryParams } from '../../../../../../applications/shared/query_params'; +import { Loading } from '../../../../../shared/loading'; +import { parseQueryParams } from '../../../../../shared/query_params'; import { AddSourceLogic } from './add_source_logic'; import { CONFIG_OAUTH_LABEL, CONFIG_OAUTH_BUTTON } from './constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx index d85bd21c54e5a..08b29075f3d0d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx @@ -28,7 +28,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { LicensingLogic } from '../../../../../../applications/shared/licensing'; +import { LicensingLogic } from '../../../../../shared/licensing'; import { AppLogic } from '../../../../app_logic'; import { DOCUMENT_PERMISSIONS_DOCS_URL } from '../../../../routes'; import { FeatureIds, Configuration, Features } from '../../../../types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx index 15082f6de85bf..eb6736d84a197 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx @@ -14,7 +14,7 @@ import { useActions, useValues } from 'kea'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { parseQueryParams } from '../../../../../../applications/shared/query_params'; +import { parseQueryParams } from '../../../../../shared/query_params'; import { AddSourceLogic } from './add_source_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx index 06ee2b6eb40f1..956d5143ef2c5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx @@ -22,7 +22,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { LicensingLogic } from '../../../../../../applications/shared/licensing'; +import { LicensingLogic } from '../../../../../shared/licensing'; import { ApiKey } from '../../../../components/shared/api_key'; import { PUBLIC_KEY_LABEL, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx index f304a1a36d9dd..0838ab2ccdae2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx @@ -20,7 +20,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { LicensingLogic } from '../../../../../../applications/shared/licensing'; +import { LicensingLogic } from '../../../../../shared/licensing'; import { AppLogic } from '../../../../app_logic'; import { LicenseBadge } from '../../../../components/shared/license_badge'; import { ENT_SEARCH_LICENSE_MANAGEMENT } from '../../../../routes'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.test.tsx index e904efb73afc8..12399d4822a13 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.test.tsx @@ -24,8 +24,8 @@ import { EuiLink, } from '@elastic/eui'; -import { Loading } from '../../../../../applications/shared/loading'; import { DEFAULT_META } from '../../../../shared/constants'; +import { Loading } from '../../../../shared/loading'; import { ComponentLoader } from '../../../components/shared/component_loader'; import { TablePaginationBar } from '../../../components/shared/table_pagination_bar'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx index bf18f88e47537..3dd8ad1dc7899 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx @@ -31,7 +31,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Loading } from '../../../../../applications/shared/loading'; +import { Loading } from '../../../../shared/loading'; import { TruncatedContent } from '../../../../shared/truncate'; import { ComponentLoader } from '../../../components/shared/component_loader'; import { TablePaginationBar } from '../../../components/shared/table_pagination_bar'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx index 61be7b6281e9e..087681fa89603 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx @@ -12,7 +12,7 @@ import { useActions, useValues } from 'kea'; import { EuiCallOut, EuiEmptyPrompt, EuiSpacer, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { LicensingLogic } from '../../../../applications/shared/licensing'; +import { LicensingLogic } from '../../../shared/licensing'; import { Loading } from '../../../shared/loading'; import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { AppLogic } from '../../app_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx index dcc15be4462c7..b7857cf4612a2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx @@ -11,9 +11,9 @@ import { Redirect, Route, Switch, useLocation } from 'react-router-dom'; import { Location } from 'history'; import { useActions, useValues } from 'kea'; -import { LicensingLogic } from '../../../../applications/shared/licensing'; import { FlashMessages } from '../../../shared/flash_messages'; import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { LicensingLogic } from '../../../shared/licensing'; import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; import { AppLogic } from '../../app_logic'; import { NAV } from '../../constants'; From 10adb72ddbf56b7d3a060315b9559900bea2adfc Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Tue, 9 Feb 2021 16:27:36 -0500 Subject: [PATCH 3/9] Plugin introduction doc improvements (#90502) * More plugin docs improvements * remove speculation about the future * remove registries section Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- dev_docs/assets/applications.png | Bin 0 -> 199244 bytes dev_docs/assets/platform_plugins_core.png | Bin 0 -> 5340 bytes dev_docs/kibana_platform_plugin_intro.mdx | 291 +++++----------------- dev_docs/tutorials/building_a_plugin.mdx | 226 +++++++++++++++++ 4 files changed, 291 insertions(+), 226 deletions(-) create mode 100644 dev_docs/assets/applications.png create mode 100644 dev_docs/assets/platform_plugins_core.png create mode 100644 dev_docs/tutorials/building_a_plugin.mdx diff --git a/dev_docs/assets/applications.png b/dev_docs/assets/applications.png new file mode 100644 index 0000000000000000000000000000000000000000..409f3416136ecad21224af9ffd9effc71d355b0d GIT binary patch literal 199244 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

k2nK`W(u5cSo@}8xbyhCYgFZ%PgeSp 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 provides basic utilities to search, query, and filter data in Elasticsearch. -This code is not part of Core, but is still fundamental for building a plugin, - and we strongly encourage using this service over querying Elasticsearch directly. - - -We currently have three kinds of public services: - - - platform services provided by `core` - - platform services provided by plugins, that can, and should, be used by every plugin (e.g. ) . - - shared services provided by plugins, that are only relevant for only a few, specific plugins (e.g. "presentation utils"). - -Two common questions we encounter are: + - Platform services provided by `core` () + - Platform services provided by plugins () + - Shared services provided by plugins, that are only relevant for only a few, specific plugins (e.g. "presentation utils"). -1. Which services are platform services? -2. What is the difference between platform code supplied by core, and platform code supplied by plugins? + The first two items are what make up "Platform services". -We don't have great answers to those questions today. Currently, the best answers we have are: + -1. Platform plugins are _usually_ plugins that are managed by the Platform Group, but we are starting to see some exceptions. -2. `core` code contains the most fundamental and stable services needed for plugin development. Everything else goes in a plugin. +We try to put only the most stable and fundamental code into `Core`, while more application focused functionality goes in a plugin, but the heuristic isn't +clear, and we haven't done a great job of sticking to it. For example, notifications and toasts are core services, but data and search are plugin services. -We will continue to focus on adding clarity around these types of services and what developers can expect from each. - - +Today it looks something like this. +![Core vs platform plugins vs plugins](assets/platform_plugins_core.png) + - When the Kibana platform and plugin infrastructure was built, we thought of two types of code: core services, and other plugin services. We planned to keep the most stable and fundamental code needed to build plugins inside core. @@ -70,125 +59,62 @@ Another side effect of having many small plugins is that common code often ends We recognize the need to better clarify the relationship between core functionality, platform-like plugin functionality, and functionality exposed by other plugins. It's something we will be working on! - -The main difference between core functionality and functionality supplied by plugins, is in how it is accessed. Core is -passed to plugins as the first parameter to their `start` and `setup` lifecycle functions, while plugin supplied functionality is passed as the -second parameter. Plugin dependencies must be declared explicitly inside the `kibana.json` file. Core functionality is always provided. Read the -section on for more information. - -## The anatomy of a plugin - -Plugins are defined as classes and present themselves to Kibana through a simple wrapper function. A plugin can have browser-side code, server-side code, -or both. There is no architectural difference between a plugin in the browser and a plugin on the server. In both places, you describe your plugin similarly, -and you interact with Core and other plugins in the same way. - -The basic file structure of a Kibana plugin named demo that has both client-side and server-side code would be: - -``` -plugins/ - demo - kibana.json [1] - public - index.ts [2] - plugin.ts [3] - server - index.ts [4] - plugin.ts [5] -``` - -### [1] kibana.json - -`kibana.json` is a static manifest file that is used to identify the plugin and to specify if this plugin has server-side code, browser-side code, or both: - -``` -{ - "id": "demo", - "version": "kibana", - "server": true, - "ui": true -} -``` - -### [2] public/index.ts - -`public/index.ts` is the entry point into the client-side code of this plugin. It must export a function named plugin, which will receive a standard set of - core capabilities as an argument. It should return an instance of its plugin class for Kibana to load. - -``` -import type { PluginInitializerContext } from 'kibana/server'; -import { DemoPlugin } from './plugin'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new DemoPlugin(initializerContext); -} -``` - -### [3] public/plugin.ts - -`public/plugin.ts` is the client-side plugin definition itself. Technically speaking, it does not need to be a class or even a separate file from the entry - point, but all plugins at Elastic should be consistent in this way. - +We will continue to focus on adding clarity around these types of services and what developers can expect from each. - ```ts -import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server'; -export class DemoPlugin implements Plugin { - constructor(initializerContext: PluginInitializerContext) {} + - public setup(core: CoreSetup) { - // called when plugin is setting up during Kibana's startup sequence - } +### Core services - public start(core: CoreStart) { - // called after all plugins are set up - } +Sometimes referred to just as Core, Core services provide the most basic and fundamental tools neccessary for building a plugin, like creating saved objects, +routing, application registration, and notifications. The Core platform is not a plugin itself, although +there are some plugins that provide platform functionality. We call these . - public stop() { - // called when plugin is torn down during Kibana's shutdown sequence - } -} - ``` +### Platform plugins +Plugins that provide fundamental services and functionality to extend and customize Kibana, for example, the + plugin. There is no official way to tell if a plugin is a platform plugin or not. +Platform plugins are _usually_ plugins that are managed by the Platform Group, but we are starting to see some exceptions. -### [4] server/index.ts +## Plugins -`server/index.ts` is the entry-point into the server-side code of this plugin. It is identical in almost every way to the client-side entry-point: +Plugins are code that is written to extend and customize Kibana. Plugin's don't have to be part of the Kibana repo, though the Kibana +repo does contain many plugins! Plugins add customizations by +using provided by . +Sometimes people confuse the term "plugin" and "application". While often there is a 1:1 relationship between a plugin and an application, it is not always the case. +A plugin may register many applications, or none. -### [5] server/plugin.ts +### Applications -`server/plugin.ts` is the server-side plugin definition. The shape of this plugin is the same as it’s client-side counter-part: +Applications are top level pages in the Kibana UI. Dashboard, Canvas, Maps, App Search, etc, are all examples of applications: -```ts -import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server'; +![applications in kibana](./assets/applications.png) -export class DemoPlugin implements Plugin { - constructor(initializerContext: PluginInitializerContext) {} +A plugin can register an application by +adding it to core's application . - public setup(core: CoreSetup) { - // called when plugin is setting up during Kibana's startup sequence - } +### Public plugin API - public start(core: CoreStart) { - // called after all plugins are set up - } +A plugin's public API consists of everything exported from a plugin's , +as well as from the top level `index.ts` files that exist in the three "scope" folders: - public stop() { - // called when plugin is torn down during Kibana's shutdown sequence - } -} -``` +- common/index.ts +- public/index.ts +- server/index.ts -Kibana does not impose any technical restrictions on how the the internals of a plugin are architected, though there are certain -considerations related to how plugins integrate with core APIs and APIs exposed by other plugins that may greatly impact how they are built. +Any plugin that exports something from those files, or from the lifecycle methods, is exposing a public service. We sometimes call these things "plugin services" or +"shared services". -## Plugin lifecycles & Core services +## Lifecycle methods -The various independent domains that make up core are represented by a series of services. Those services expose public interfaces that are provided to all plugins. -Services expose different features at different parts of their lifecycle. We describe the lifecycle of core services and plugins with specifically-named functions on the service definition. +Core, and plugins, expose different features at different parts of their lifecycle. We describe the lifecycle of core services and plugins with + specifically-named functions on the service definition. -Kibana has three lifecycles: setup, start, and stop. Each plugin’s setup function is called sequentially while Kibana is setting up on the server or when it is being loaded in the browser. The start functions are called sequentially after setup has been completed for all plugins. The stop functions are called sequentially while Kibana is gracefully shutting down the server or when the browser tab or window is being closed. +Kibana has three lifecycles: setup, start, and stop. Each plugin’s setup function is called sequentially while Kibana is setting up + on the server or when it is being loaded in the browser. The start functions are called sequentially after setup has been completed for all plugins. + The stop functions are called sequentially while Kibana is gracefully shutting down the server or when the browser tab or window is being closed. The table below explains how each lifecycle relates to the state of Kibana. @@ -201,105 +127,18 @@ The table below explains how each lifecycle relates to the state of Kibana. Different service interfaces can and will be passed to setup, start, and stop because certain functionality makes sense in the context of a running plugin while other types of functionality may have restrictions or may only make sense in the context of a plugin that is stopping. -## How plugin's interact with each other, and Core - -The lifecycle-specific contracts exposed by core services are always passed as the first argument to the equivalent lifecycle function in a plugin. -For example, the core http service exposes a function createRouter to all plugin setup functions. To use this function to register an HTTP route handler, -a plugin just accesses it off of the first argument: - -```ts -import type { CoreSetup } from 'kibana/server'; - -export class DemoPlugin { - public setup(core: CoreSetup) { - const router = core.http.createRouter(); - // handler is called when '/path' resource is requested with `GET` method - router.get({ path: '/path', validate: false }, (context, req, res) => res.ok({ content: 'ok' })); - } -} -``` - -Unlike core, capabilities exposed by plugins are not automatically injected into all plugins. -Instead, if a plugin wishes to use the public interface provided by another plugin, it must first declare that plugin as a - dependency in it’s kibana.json manifest file. - -** foobar plugin.ts: ** - -``` -import type { Plugin } from 'kibana/server'; -export interface FoobarPluginSetup { [1] - getFoo(): string; -} - -export interface FoobarPluginStart { [1] - getBar(): string; -} - -export class MyPlugin implements Plugin { - public setup(): FoobarPluginSetup { - return { - getFoo() { - return 'foo'; - }, - }; - } - - public start(): FoobarPluginStart { - return { - getBar() { - return 'bar'; - }, - }; - } -} -``` -[1] We highly encourage plugin authors to explicitly declare public interfaces for their plugins. - - -** demo kibana.json** - -``` -{ - "id": "demo", - "requiredPlugins": ["foobar"], - "server": true, - "ui": true -} -``` - -With that specified in the plugin manifest, the appropriate interfaces are then available via the second argument of setup and/or start: - -```ts -import type { CoreSetup, CoreStart } from 'kibana/server'; -import type { FoobarPluginSetup, FoobarPluginStart } from '../../foobar/server'; - -interface DemoSetupPlugins { [1] - foobar: FoobarPluginSetup; -} - -interface DemoStartPlugins { - foobar: FoobarPluginStart; -} - -export class DemoPlugin { - public setup(core: CoreSetup, plugins: DemoSetupPlugins) { [2] - const { foobar } = plugins; - foobar.getFoo(); // 'foo' - foobar.getBar(); // throws because getBar does not exist - } - - public start(core: CoreStart, plugins: DemoStartPlugins) { [3] - const { foobar } = plugins; - foobar.getFoo(); // throws because getFoo does not exist - foobar.getBar(); // 'bar' - } - - public stop() {} -} -``` - -[1] The interface for plugin’s dependencies must be manually composed. You can do this by importing the appropriate type from the plugin and constructing an interface where the property name is the plugin’s ID. - -[2] These manually constructed types should then be used to specify the type of the second argument to the plugin. - -[3] Notice that the type for the setup and start lifecycles are different. Plugin lifecycle functions can only access the APIs that are exposed during that lifecycle. +## Extension points + +An extension point is a function provided by core, or a plugin's plugin API, that can be used by other +plugins to customize the Kibana experience. Examples of extension points are: + +- core.application.register (The extension point talked about above) +- core.notifications.toasts.addSuccess +- core.overlays.showModal +- embeddables.registerEmbeddableFactory +- uiActions.registerAction +- core.saedObjects.registerType + +## Follow up material + +Learn how to build your own plugin by following \ No newline at end of file diff --git a/dev_docs/tutorials/building_a_plugin.mdx b/dev_docs/tutorials/building_a_plugin.mdx new file mode 100644 index 0000000000000..cee5a9a399de5 --- /dev/null +++ b/dev_docs/tutorials/building_a_plugin.mdx @@ -0,0 +1,226 @@ +--- +id: kibDevTutorialBuildAPlugin +slug: /kibana-dev-docs/tutorials/build-a-plugin +title: Kibana plugin tutorial +summary: Anatomy of a Kibana plugin and how to build one +date: 2021-02-05 +tags: ['kibana','onboarding', 'dev', 'tutorials'] +--- + +Prereading material: + +- + +## The anatomy of a plugin + +Plugins are defined as classes and present themselves to Kibana through a simple wrapper function. A plugin can have browser-side code, server-side code, +or both. There is no architectural difference between a plugin in the browser and a plugin on the server. In both places, you describe your plugin similarly, +and you interact with Core and other plugins in the same way. + +The basic file structure of a Kibana plugin named demo that has both client-side and server-side code would be: + +``` +plugins/ + demo + kibana.json [1] + public + index.ts [2] + plugin.ts [3] + server + index.ts [4] + plugin.ts [5] + common + index.ts [6] +``` + +### [1] kibana.json + +`kibana.json` is a static manifest file that is used to identify the plugin and to specify if this plugin has server-side code, browser-side code, or both: + +``` +{ + "id": "demo", + "version": "kibana", + "server": true, + "ui": true +} +``` + +### [2] public/index.ts + +`public/index.ts` is the entry point into the client-side code of this plugin. It must export a function named plugin, which will receive a standard set of + core capabilities as an argument. It should return an instance of its plugin class for Kibana to load. + +``` +import type { PluginInitializerContext } from 'kibana/server'; +import { DemoPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new DemoPlugin(initializerContext); +} +``` + +### [3] public/plugin.ts + +`public/plugin.ts` is the client-side plugin definition itself. Technically speaking, it does not need to be a class or even a separate file from the entry + point, but all plugins at Elastic should be consistent in this way. + + + ```ts +import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server'; + +export class DemoPlugin implements Plugin { + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup) { + // called when plugin is setting up during Kibana's startup sequence + } + + public start(core: CoreStart) { + // called after all plugins are set up + } + + public stop() { + // called when plugin is torn down during Kibana's shutdown sequence + } +} + ``` + + +### [4] server/index.ts + +`server/index.ts` is the entry-point into the server-side code of this plugin. It is identical in almost every way to the client-side entry-point: + +### [5] server/plugin.ts + +`server/plugin.ts` is the server-side plugin definition. The shape of this plugin is the same as it’s client-side counter-part: + +```ts +import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server'; + +export class DemoPlugin implements Plugin { + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup) { + // called when plugin is setting up during Kibana's startup sequence + } + + public start(core: CoreStart) { + // called after all plugins are set up + } + + public stop() { + // called when plugin is torn down during Kibana's shutdown sequence + } +} +``` + +Kibana does not impose any technical restrictions on how the the internals of a plugin are architected, though there are certain +considerations related to how plugins integrate with core APIs and APIs exposed by other plugins that may greatly impact how they are built. + +### [6] common/index.ts + +`common/index.ts` is the entry-point into code that can be used both server-side or client side. + +## How plugin's interact with each other, and Core + +The lifecycle-specific contracts exposed by core services are always passed as the first argument to the equivalent lifecycle function in a plugin. +For example, the core http service exposes a function createRouter to all plugin setup functions. To use this function to register an HTTP route handler, +a plugin just accesses it off of the first argument: + +```ts +import type { CoreSetup } from 'kibana/server'; + +export class DemoPlugin { + public setup(core: CoreSetup) { + const router = core.http.createRouter(); + // handler is called when '/path' resource is requested with `GET` method + router.get({ path: '/path', validate: false }, (context, req, res) => res.ok({ content: 'ok' })); + } +} +``` + +Unlike core, capabilities exposed by plugins are not automatically injected into all plugins. +Instead, if a plugin wishes to use the public interface provided by another plugin, it must first declare that plugin as a + dependency in it’s kibana.json manifest file. + +** foobar plugin.ts: ** + +``` +import type { Plugin } from 'kibana/server'; +export interface FoobarPluginSetup { [1] + getFoo(): string; +} + +export interface FoobarPluginStart { [1] + getBar(): string; +} + +export class MyPlugin implements Plugin { + public setup(): FoobarPluginSetup { + return { + getFoo() { + return 'foo'; + }, + }; + } + + public start(): FoobarPluginStart { + return { + getBar() { + return 'bar'; + }, + }; + } +} +``` +[1] We highly encourage plugin authors to explicitly declare public interfaces for their plugins. + + +** demo kibana.json** + +``` +{ + "id": "demo", + "requiredPlugins": ["foobar"], + "server": true, + "ui": true +} +``` + +With that specified in the plugin manifest, the appropriate interfaces are then available via the second argument of setup and/or start: + +```ts +import type { CoreSetup, CoreStart } from 'kibana/server'; +import type { FoobarPluginSetup, FoobarPluginStart } from '../../foobar/server'; + +interface DemoSetupPlugins { [1] + foobar: FoobarPluginSetup; +} + +interface DemoStartPlugins { + foobar: FoobarPluginStart; +} + +export class DemoPlugin { + public setup(core: CoreSetup, plugins: DemoSetupPlugins) { [2] + const { foobar } = plugins; + foobar.getFoo(); // 'foo' + foobar.getBar(); // throws because getBar does not exist + } + + public start(core: CoreStart, plugins: DemoStartPlugins) { [3] + const { foobar } = plugins; + foobar.getFoo(); // throws because getFoo does not exist + foobar.getBar(); // 'bar' + } + + public stop() {} +} +``` + +[1] The interface for plugin’s dependencies must be manually composed. You can do this by importing the appropriate type from the plugin and constructing an interface where the property name is the plugin’s ID. + +[2] These manually constructed types should then be used to specify the type of the second argument to the plugin. + +[3] Notice that the type for the setup and start lifecycles are different. Plugin lifecycle functions can only access the APIs that are exposed during that lifecycle. From 6c224e5a107f57f990bf74331ada027e0482e8a4 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 9 Feb 2021 14:34:34 -0700 Subject: [PATCH 4/9] Switch import route tag from access:ml:canFindFileStructure to access:fileUpload:import (#90677) --- x-pack/plugins/file_upload/server/routes.ts | 2 +- x-pack/plugins/maps/server/plugin.ts | 1 + x-pack/plugins/maps_file_upload/server/routes/file_upload.js | 1 + x-pack/plugins/ml/common/types/capabilities.ts | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/file_upload/server/routes.ts b/x-pack/plugins/file_upload/server/routes.ts index 425e5551f2147..d7b7b8f99edd9 100644 --- a/x-pack/plugins/file_upload/server/routes.ts +++ b/x-pack/plugins/file_upload/server/routes.ts @@ -52,7 +52,7 @@ export function fileUploadRoutes(router: IRouter) { accepts: ['application/json'], maxBytes: MAX_FILE_SIZE_BYTES, }, - tags: ['access:ml:canFindFileStructure'], + tags: ['access:fileUpload:import'], }, }, async (context, request, response) => { diff --git a/x-pack/plugins/maps/server/plugin.ts b/x-pack/plugins/maps/server/plugin.ts index 7440b6ee1e1df..cb22a98b70aa8 100644 --- a/x-pack/plugins/maps/server/plugin.ts +++ b/x-pack/plugins/maps/server/plugin.ts @@ -177,6 +177,7 @@ export class MapsPlugin implements Plugin { catalogue: [APP_ID], privileges: { all: { + api: ['fileUpload:import'], app: [APP_ID, 'kibana'], catalogue: [APP_ID], savedObject: { diff --git a/x-pack/plugins/maps_file_upload/server/routes/file_upload.js b/x-pack/plugins/maps_file_upload/server/routes/file_upload.js index 1b617c44113a2..517fab13363a2 100644 --- a/x-pack/plugins/maps_file_upload/server/routes/file_upload.js +++ b/x-pack/plugins/maps_file_upload/server/routes/file_upload.js @@ -41,6 +41,7 @@ const options = { maxBytes: MAX_BYTES, accepts: ['application/json'], }, + tags: ['access:fileUpload:import'], }; export const idConditionalValidation = (body, boolHasId) => diff --git a/x-pack/plugins/ml/common/types/capabilities.ts b/x-pack/plugins/ml/common/types/capabilities.ts index eb7615c79a363..974a1f2243060 100644 --- a/x-pack/plugins/ml/common/types/capabilities.ts +++ b/x-pack/plugins/ml/common/types/capabilities.ts @@ -99,7 +99,7 @@ export function getPluginPrivileges() { return { admin: { ...privilege, - api: allMlCapabilitiesKeys.map((k) => `ml:${k}`), + api: ['fileUpload:import', ...allMlCapabilitiesKeys.map((k) => `ml:${k}`)], catalogue: [PLUGIN_ID, `${PLUGIN_ID}_file_data_visualizer`], ui: allMlCapabilitiesKeys, savedObject: { From 08111e40d32583382ae6e6a300f014cef7d18eba Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 9 Feb 2021 22:41:47 +0100 Subject: [PATCH 5/9] [Uptime-UX] Added nav search keywords for uptime and user experience app (#90616) --- x-pack/plugins/apm/public/plugin.ts | 19 +++++++++++++++++- x-pack/plugins/uptime/public/apps/plugin.ts | 22 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index f585515f79b0c..a1b3af6a9f943 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -162,7 +162,24 @@ export class ApmPlugin implements Plugin { order: 8500, euiIconType: 'logoObservability', category: DEFAULT_APP_CATEGORIES.observability, - + meta: { + keywords: [ + 'RUM', + 'Real User Monitoring', + 'DEM', + 'Digital Experience Monitoring', + 'EUM', + 'End User Monitoring', + 'UX', + 'Javascript', + 'APM', + 'Mobile', + 'digital', + 'performance', + 'web performance', + 'web perf', + ], + }, async mount(params: AppMountParameters) { // Load application bundle and Get start service const [{ renderApp }, [coreStart, corePlugins]] = await Promise.all([ diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index 8bbbecf8108fe..e7a22a080d79a 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -87,6 +87,28 @@ export class UptimePlugin order: 8400, title: PLUGIN.TITLE, category: DEFAULT_APP_CATEGORIES.observability, + meta: { + keywords: [ + 'Synthetics', + 'pings', + 'checks', + 'availability', + 'response duration', + 'response time', + 'outside in', + 'reachability', + 'reachable', + 'digital', + 'performance', + 'web performance', + 'web perf', + ], + searchDeepLinks: [ + { id: 'Down monitors', title: 'Down monitors', path: '/?statusFilter=down' }, + { id: 'Certificates', title: 'TLS Certificates', path: '/certificates' }, + { id: 'Settings', title: 'Settings', path: '/settings' }, + ], + }, mount: async (params: AppMountParameters) => { const [coreStart, corePlugins] = await core.getStartServices(); From 1a9f8cab1775419df669474fee233e51582cb5cf Mon Sep 17 00:00:00 2001 From: Rashmi Kulkarni Date: Tue, 9 Feb 2021 14:12:46 -0800 Subject: [PATCH 6/9] [uptime] unskip previously skipped functional tests (#90702) * fixes https://github.com/elastic/kibana/issues/74449 * unskipping uptime -functional test * unskipping location.ts script --- x-pack/test/functional/apps/uptime/locations.ts | 3 +-- x-pack/test/functional/apps/uptime/ping_redirects.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/apps/uptime/locations.ts b/x-pack/test/functional/apps/uptime/locations.ts index e3f1d04640754..15b4773373bf7 100644 --- a/x-pack/test/functional/apps/uptime/locations.ts +++ b/x-pack/test/functional/apps/uptime/locations.ts @@ -39,8 +39,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await makeChecksWithStatus(es, LessAvailMonitor, 5, 2, 10000, {}, 'down'); }; - // FLAKY: https://github.com/elastic/kibana/issues/85208 - describe.skip('Observer location', () => { + describe('Observer location', () => { const start = '~ 15 minutes ago'; const end = 'now'; diff --git a/x-pack/test/functional/apps/uptime/ping_redirects.ts b/x-pack/test/functional/apps/uptime/ping_redirects.ts index e0abee38f4195..9c39ed7017721 100644 --- a/x-pack/test/functional/apps/uptime/ping_redirects.ts +++ b/x-pack/test/functional/apps/uptime/ping_redirects.ts @@ -19,8 +19,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const monitor = () => uptime.monitor; - // FLAKY: https://github.com/elastic/kibana/issues/84992 - describe.skip('Ping redirects', () => { + describe('Ping redirects', () => { const start = '~ 15 minutes ago'; const end = 'now'; From a9ee83c37c8b5171fdbf03ff20fc0e0788b81eab Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 9 Feb 2021 15:49:44 -0700 Subject: [PATCH 7/9] [Maps] convert HeatmapLayer to TS (#90666) * [Maps] convert HeatmapLayer to TS * make VectorLayer implement IVectorLayer * fix jest test and functional test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../layer_descriptor_types.ts | 10 +- .../public/actions/data_request_actions.ts | 2 +- .../maps/public/actions/layer_actions.ts | 2 +- .../public/classes/joins/inner_join.test.js | 2 +- .../blended_vector_layer.ts | 3 +- .../create_choropleth_layer_descriptor.ts | 2 +- .../create_region_map_layer_descriptor.ts | 3 +- .../create_tile_map_layer_descriptor.ts | 6 +- .../layers/file_upload_wizard/wizard.tsx | 2 +- .../layers/heatmap_layer/heatmap_layer.js | 122 ------------ .../layers/heatmap_layer/heatmap_layer.ts | 182 ++++++++++++++++++ .../classes/layers/heatmap_layer/index.ts | 8 + .../maps/public/classes/layers/layer.tsx | 7 + .../classes/layers/load_layer_wizards.ts | 1 - .../create_layer_descriptor.test.ts | 1 - .../observability/create_layer_descriptor.ts | 6 +- .../security/create_layer_descriptors.ts | 2 +- .../tiled_vector_layer/tiled_vector_layer.tsx | 2 +- .../vector_layer}/assign_feature_ids.test.ts | 2 +- .../vector_layer}/assign_feature_ids.ts | 2 +- .../classes/layers/vector_layer/index.ts | 9 + .../classes/layers/vector_layer/utils.tsx | 114 +++++++++++ .../layers/vector_layer/vector_layer.tsx | 126 ++---------- .../ems_boundaries_layer_wizard.tsx | 2 +- .../ems_file_source/ems_file_source.test.tsx | 2 +- .../clusters_layer_wizard.tsx | 3 +- .../heatmap_layer_wizard.tsx | 4 +- .../es_geo_grid_source/{index.js => index.ts} | 0 .../es_geo_line_source/layer_wizard.tsx | 2 +- .../point_2_point_layer_wizard.tsx | 2 +- .../create_layer_descriptor.ts | 2 +- .../es_documents_layer_wizard.tsx | 2 +- .../es_term_source/es_term_source.test.js | 2 +- .../kibana_regionmap_layer_wizard.tsx | 2 +- .../classes/styles/heatmap/heatmap_style.tsx | 2 +- .../components/vector_style_editor.test.tsx | 2 +- .../vector/components/vector_style_editor.tsx | 2 +- .../dynamic_color_property.test.tsx | 2 +- .../properties/dynamic_icon_property.test.tsx | 2 +- .../properties/dynamic_size_property.test.tsx | 2 +- .../properties/dynamic_size_property.tsx | 2 +- .../properties/dynamic_style_property.tsx | 2 +- .../classes/styles/vector/vector_style.tsx | 2 +- .../public/selectors/map_selectors.test.ts | 4 +- .../maps/public/selectors/map_selectors.ts | 12 +- 45 files changed, 385 insertions(+), 288 deletions(-) delete mode 100644 x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.js create mode 100644 x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts create mode 100644 x-pack/plugins/maps/public/classes/layers/heatmap_layer/index.ts rename x-pack/plugins/maps/public/classes/{util => layers/vector_layer}/assign_feature_ids.test.ts (97%) rename x-pack/plugins/maps/public/classes/{util => layers/vector_layer}/assign_feature_ids.ts (96%) create mode 100644 x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts create mode 100644 x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx rename x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/{index.js => index.ts} (100%) diff --git a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts index 62de8baa66d5f..7a21599605b52 100644 --- a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts @@ -8,7 +8,11 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ import { Query } from 'src/plugins/data/public'; -import { StyleDescriptor, VectorStyleDescriptor } from './style_property_descriptor_types'; +import { + HeatmapStyleDescriptor, + StyleDescriptor, + VectorStyleDescriptor, +} from './style_property_descriptor_types'; import { DataRequestDescriptor } from './data_request_descriptor_types'; import { AbstractSourceDescriptor, TermJoinSourceDescriptor } from './source_descriptor_types'; @@ -40,3 +44,7 @@ export type LayerDescriptor = { export type VectorLayerDescriptor = LayerDescriptor & { style: VectorStyleDescriptor; }; + +export type HeatmapLayerDescriptor = LayerDescriptor & { + style: HeatmapStyleDescriptor; +}; diff --git a/x-pack/plugins/maps/public/actions/data_request_actions.ts b/x-pack/plugins/maps/public/actions/data_request_actions.ts index 2e6a8098e5c21..5e8a18348ac5a 100644 --- a/x-pack/plugins/maps/public/actions/data_request_actions.ts +++ b/x-pack/plugins/maps/public/actions/data_request_actions.ts @@ -40,7 +40,7 @@ import { UPDATE_SOURCE_DATA_REQUEST, } from './map_action_constants'; import { ILayer } from '../classes/layers/layer'; -import { IVectorLayer } from '../classes/layers/vector_layer/vector_layer'; +import { IVectorLayer } from '../classes/layers/vector_layer'; import { DataMeta, MapExtent, MapFilters } from '../../common/descriptor_types'; import { DataRequestAbortError } from '../classes/util/data_request'; import { scaleBounds, turfBboxToBounds } from '../../common/elasticsearch_util'; diff --git a/x-pack/plugins/maps/public/actions/layer_actions.ts b/x-pack/plugins/maps/public/actions/layer_actions.ts index 16aa44af4460f..d68e4744975f1 100644 --- a/x-pack/plugins/maps/public/actions/layer_actions.ts +++ b/x-pack/plugins/maps/public/actions/layer_actions.ts @@ -42,7 +42,7 @@ import { clearDataRequests, syncDataForLayerId, updateStyleMeta } from './data_r import { cleanTooltipStateForLayer } from './tooltip_actions'; import { JoinDescriptor, LayerDescriptor, StyleDescriptor } from '../../common/descriptor_types'; import { ILayer } from '../classes/layers/layer'; -import { IVectorLayer } from '../classes/layers/vector_layer/vector_layer'; +import { IVectorLayer } from '../classes/layers/vector_layer'; import { LAYER_STYLE_TYPE, LAYER_TYPE } from '../../common/constants'; import { IVectorStyle } from '../classes/styles/vector/vector_style'; import { notifyLicensedFeatureUsage } from '../licensed_features'; diff --git a/x-pack/plugins/maps/public/classes/joins/inner_join.test.js b/x-pack/plugins/maps/public/classes/joins/inner_join.test.js index 749745530af7e..67fbf94fd1787 100644 --- a/x-pack/plugins/maps/public/classes/joins/inner_join.test.js +++ b/x-pack/plugins/maps/public/classes/joins/inner_join.test.js @@ -9,7 +9,7 @@ import { InnerJoin } from './inner_join'; import { SOURCE_TYPES } from '../../../common/constants'; jest.mock('../../kibana_services', () => {}); -jest.mock('../layers/vector_layer/vector_layer', () => {}); +jest.mock('../layers/vector_layer', () => {}); const rightSource = { type: SOURCE_TYPES.ES_TERM_SOURCE, diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts index 5d4b915c4e971..d3a4fa4101ac9 100644 --- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { VectorLayer } from '../vector_layer/vector_layer'; +import { IVectorLayer, VectorLayer } from '../vector_layer'; import { IVectorStyle, VectorStyle } from '../../styles/vector/vector_style'; import { getDefaultDynamicProperties } from '../../styles/vector/vector_style_defaults'; import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; @@ -24,7 +24,6 @@ import { } from '../../../../common/constants'; import { ESGeoGridSource } from '../../sources/es_geo_grid_source/es_geo_grid_source'; import { canSkipSourceUpdate } from '../../util/can_skip_fetch'; -import { IVectorLayer } from '../vector_layer/vector_layer'; import { IESSource } from '../../sources/es_source'; import { ISource } from '../../sources/source'; import { DataRequestContext } from '../../../actions'; diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts index a85ba041c4351..a4955a965d77c 100644 --- a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts @@ -23,7 +23,7 @@ import { ESSearchSourceDescriptor, } from '../../../../common/descriptor_types'; import { VectorStyle } from '../../styles/vector/vector_style'; -import { VectorLayer } from '../vector_layer/vector_layer'; +import { VectorLayer } from '../vector_layer'; import { EMSFileSource } from '../../sources/ems_file_source'; // @ts-ignore import { ESSearchSource } from '../../sources/es_search_source'; diff --git a/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts index 8e0d234445355..658a093321500 100644 --- a/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts @@ -22,8 +22,7 @@ import { } from '../../../common/constants'; import { VectorStyle } from '../styles/vector/vector_style'; import { EMSFileSource } from '../sources/ems_file_source'; -// @ts-ignore -import { VectorLayer } from './vector_layer/vector_layer'; +import { VectorLayer } from './vector_layer'; import { getDefaultDynamicProperties } from '../styles/vector/vector_style_defaults'; import { NUMERICAL_COLOR_PALETTES } from '../styles/color_palettes'; import { getJoinAggKey } from '../../../common/get_agg_key'; diff --git a/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts index a9de8c98ee557..e3e5f3878ee56 100644 --- a/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts @@ -23,11 +23,9 @@ import { VECTOR_STYLES, } from '../../../common/constants'; import { VectorStyle } from '../styles/vector/vector_style'; -// @ts-ignore import { ESGeoGridSource } from '../sources/es_geo_grid_source'; -import { VectorLayer } from './vector_layer/vector_layer'; -// @ts-ignore -import { HeatmapLayer } from './heatmap_layer/heatmap_layer'; +import { VectorLayer } from './vector_layer'; +import { HeatmapLayer } from './heatmap_layer'; import { getDefaultDynamicProperties } from '../styles/vector/vector_style_defaults'; import { NUMERICAL_COLOR_PALETTES } from '../styles/color_palettes'; import { getSourceAggKey } from '../../../common/get_agg_key'; diff --git a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx index a61ea4ce713a8..71be12c4304a6 100644 --- a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx +++ b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx @@ -16,7 +16,7 @@ import { } from '../../../../common/constants'; import { getFileUploadComponent } from '../../../kibana_services'; import { GeoJsonFileSource } from '../../sources/geojson_file_source'; -import { VectorLayer } from '../../layers/vector_layer/vector_layer'; +import { VectorLayer } from '../../layers/vector_layer'; import { createDefaultLayerDescriptor } from '../../sources/es_search_source'; import { RenderWizardArguments } from '../../layers/layer_wizard_registry'; import { FileUploadComponentProps } from '../../../../../maps_file_upload/public'; diff --git a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.js b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.js deleted file mode 100644 index 97cc7151112bf..0000000000000 --- a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AbstractLayer } from '../layer'; -import { VectorLayer } from '../vector_layer/vector_layer'; -import { HeatmapStyle } from '../../styles/heatmap/heatmap_style'; -import { EMPTY_FEATURE_COLLECTION, LAYER_TYPE } from '../../../../common/constants'; - -const SCALED_PROPERTY_NAME = '__kbn_heatmap_weight__'; //unique name to store scaled value for weighting - -export class HeatmapLayer extends VectorLayer { - static type = LAYER_TYPE.HEATMAP; - - static createDescriptor(options) { - const heatmapLayerDescriptor = super.createDescriptor(options); - heatmapLayerDescriptor.type = HeatmapLayer.type; - heatmapLayerDescriptor.style = HeatmapStyle.createDescriptor(); - return heatmapLayerDescriptor; - } - - constructor({ layerDescriptor, source }) { - super({ layerDescriptor, source }); - if (!layerDescriptor.style) { - const defaultStyle = HeatmapStyle.createDescriptor(); - this._style = new HeatmapStyle(defaultStyle); - } else { - this._style = new HeatmapStyle(layerDescriptor.style); - } - } - - getStyleForEditing() { - return this._style; - } - - getStyle() { - return this._style; - } - - getCurrentStyle() { - return this._style; - } - - _getPropKeyOfSelectedMetric() { - const metricfields = this.getSource().getMetricFields(); - return metricfields[0].getName(); - } - - _getHeatmapLayerId() { - return this.makeMbLayerId('heatmap'); - } - - getMbLayerIds() { - return [this._getHeatmapLayerId()]; - } - - ownsMbLayerId(mbLayerId) { - return this._getHeatmapLayerId() === mbLayerId; - } - - syncLayerWithMB(mbMap) { - super._syncSourceBindingWithMb(mbMap); - - const heatmapLayerId = this._getHeatmapLayerId(); - if (!mbMap.getLayer(heatmapLayerId)) { - mbMap.addLayer({ - id: heatmapLayerId, - type: 'heatmap', - source: this.getId(), - paint: {}, - }); - } - - const mbSourceAfter = mbMap.getSource(this.getId()); - const sourceDataRequest = this.getSourceDataRequest(); - const featureCollection = sourceDataRequest ? sourceDataRequest.getData() : null; - if (!featureCollection) { - mbSourceAfter.setData(EMPTY_FEATURE_COLLECTION); - return; - } - - const propertyKey = this._getPropKeyOfSelectedMetric(); - const dataBoundToMap = AbstractLayer.getBoundDataForSource(mbMap, this.getId()); - if (featureCollection !== dataBoundToMap) { - let max = 1; //max will be at least one, since counts or sums will be at least one. - for (let i = 0; i < featureCollection.features.length; i++) { - max = Math.max(featureCollection.features[i].properties[propertyKey], max); - } - for (let i = 0; i < featureCollection.features.length; i++) { - featureCollection.features[i].properties[SCALED_PROPERTY_NAME] = - featureCollection.features[i].properties[propertyKey] / max; - } - mbSourceAfter.setData(featureCollection); - } - - this.syncVisibilityWithMb(mbMap, heatmapLayerId); - this.getCurrentStyle().setMBPaintProperties({ - mbMap, - layerId: heatmapLayerId, - propertyName: SCALED_PROPERTY_NAME, - resolution: this.getSource().getGridResolution(), - }); - mbMap.setPaintProperty(heatmapLayerId, 'heatmap-opacity', this.getAlpha()); - mbMap.setLayerZoomRange(heatmapLayerId, this.getMinZoom(), this.getMaxZoom()); - } - - getLayerTypeIconName() { - return 'heatmap'; - } - - async hasLegendDetails() { - return true; - } - - renderLegendDetails() { - const metricFields = this.getSource().getMetricFields(); - return this.getCurrentStyle().renderLegendDetails(metricFields[0]); - } -} diff --git a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts new file mode 100644 index 0000000000000..8eebd7c57afd7 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts @@ -0,0 +1,182 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Map as MbMap, GeoJSONSource as MbGeoJSONSource } from 'mapbox-gl'; +import { FeatureCollection } from 'geojson'; +import { AbstractLayer } from '../layer'; +import { HeatmapStyle } from '../../styles/heatmap/heatmap_style'; +import { EMPTY_FEATURE_COLLECTION, LAYER_TYPE } from '../../../../common/constants'; +import { HeatmapLayerDescriptor, MapQuery } from '../../../../common/descriptor_types'; +import { ESGeoGridSource } from '../../sources/es_geo_grid_source'; +import { addGeoJsonMbSource, syncVectorSource } from '../vector_layer'; +import { DataRequestContext } from '../../../actions'; +import { DataRequestAbortError } from '../../util/data_request'; + +const SCALED_PROPERTY_NAME = '__kbn_heatmap_weight__'; // unique name to store scaled value for weighting + +export class HeatmapLayer extends AbstractLayer { + static type = LAYER_TYPE.HEATMAP; + + private readonly _style: HeatmapStyle; + + static createDescriptor(options: Partial) { + const heatmapLayerDescriptor = super.createDescriptor(options); + heatmapLayerDescriptor.type = HeatmapLayer.type; + heatmapLayerDescriptor.style = HeatmapStyle.createDescriptor(); + return heatmapLayerDescriptor; + } + + constructor({ + layerDescriptor, + source, + }: { + layerDescriptor: HeatmapLayerDescriptor; + source: ESGeoGridSource; + }) { + super({ layerDescriptor, source }); + if (!layerDescriptor.style) { + const defaultStyle = HeatmapStyle.createDescriptor(); + this._style = new HeatmapStyle(defaultStyle); + } else { + this._style = new HeatmapStyle(layerDescriptor.style); + } + } + + getSource(): ESGeoGridSource { + return super.getSource() as ESGeoGridSource; + } + + getStyleForEditing() { + return this._style; + } + + getStyle() { + return this._style; + } + + getCurrentStyle() { + return this._style; + } + + _getPropKeyOfSelectedMetric() { + const metricfields = this.getSource().getMetricFields(); + return metricfields[0].getName(); + } + + _getHeatmapLayerId() { + return this.makeMbLayerId('heatmap'); + } + + getMbLayerIds() { + return [this._getHeatmapLayerId()]; + } + + ownsMbLayerId(mbLayerId: string) { + return this._getHeatmapLayerId() === mbLayerId; + } + + ownsMbSourceId(mbSourceId: string) { + return this.getId() === mbSourceId; + } + + async syncData(syncContext: DataRequestContext) { + if (this.isLoadingBounds()) { + return; + } + + const sourceQuery = this.getQuery() as MapQuery; + try { + await syncVectorSource({ + layerId: this.getId(), + layerName: await this.getDisplayName(this.getSource()), + prevDataRequest: this.getSourceDataRequest(), + requestMeta: { + ...syncContext.dataFilters, + fieldNames: this.getSource().getFieldNames(), + geogridPrecision: this.getSource().getGeoGridPrecision(syncContext.dataFilters.zoom), + sourceQuery: sourceQuery ? sourceQuery : undefined, + applyGlobalQuery: this.getSource().getApplyGlobalQuery(), + applyGlobalTime: this.getSource().getApplyGlobalTime(), + sourceMeta: this.getSource().getSyncMeta(), + }, + syncContext, + source: this.getSource(), + }); + } catch (error) { + if (!(error instanceof DataRequestAbortError)) { + throw error; + } + } + } + + syncLayerWithMB(mbMap: MbMap) { + addGeoJsonMbSource(this._getMbSourceId(), this.getMbLayerIds(), mbMap); + + const heatmapLayerId = this._getHeatmapLayerId(); + if (!mbMap.getLayer(heatmapLayerId)) { + mbMap.addLayer({ + id: heatmapLayerId, + type: 'heatmap', + source: this.getId(), + paint: {}, + }); + } + + const mbGeoJSONSource = mbMap.getSource(this.getId()) as MbGeoJSONSource; + const sourceDataRequest = this.getSourceDataRequest(); + const featureCollection = sourceDataRequest + ? (sourceDataRequest.getData() as FeatureCollection) + : null; + if (!featureCollection) { + mbGeoJSONSource.setData(EMPTY_FEATURE_COLLECTION); + return; + } + + const propertyKey = this._getPropKeyOfSelectedMetric(); + const dataBoundToMap = AbstractLayer.getBoundDataForSource(mbMap, this.getId()); + if (featureCollection !== dataBoundToMap) { + let max = 1; // max will be at least one, since counts or sums will be at least one. + for (let i = 0; i < featureCollection.features.length; i++) { + max = Math.max(featureCollection.features[i].properties?.[propertyKey], max); + } + for (let i = 0; i < featureCollection.features.length; i++) { + if (featureCollection.features[i].properties) { + featureCollection.features[i].properties![SCALED_PROPERTY_NAME] = + featureCollection.features[i].properties![propertyKey] / max; + } + } + mbGeoJSONSource.setData(featureCollection); + } + + this.syncVisibilityWithMb(mbMap, heatmapLayerId); + this.getCurrentStyle().setMBPaintProperties({ + mbMap, + layerId: heatmapLayerId, + propertyName: SCALED_PROPERTY_NAME, + resolution: this.getSource().getGridResolution(), + }); + mbMap.setPaintProperty(heatmapLayerId, 'heatmap-opacity', this.getAlpha()); + mbMap.setLayerZoomRange(heatmapLayerId, this.getMinZoom(), this.getMaxZoom()); + } + + getLayerTypeIconName() { + return 'heatmap'; + } + + async getFields() { + return this.getSource().getFields(); + } + + async hasLegendDetails() { + return true; + } + + renderLegendDetails() { + const metricFields = this.getSource().getMetricFields(); + return this.getCurrentStyle().renderLegendDetails(metricFields[0]); + } +} diff --git a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/index.ts b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/index.ts new file mode 100644 index 0000000000000..ba15d97a39219 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { HeatmapLayer } from './heatmap_layer'; diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index aedf7af08b2c8..89c6d70a217c9 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -21,6 +21,7 @@ import { MAX_ZOOM, MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER, MIN_ZOOM, + SOURCE_BOUNDS_DATA_REQUEST_ID, SOURCE_DATA_REQUEST_ID, SOURCE_TYPES, STYLE_TYPE, @@ -66,6 +67,7 @@ export interface ILayer { getImmutableSourceProperties(): Promise; renderSourceSettingsEditor({ onChange }: SourceEditorArgs): ReactElement | null; isLayerLoading(): boolean; + isLoadingBounds(): boolean; isFilteredByGlobalTime(): Promise; hasErrors(): boolean; getErrors(): string; @@ -401,6 +403,11 @@ export class AbstractLayer implements ILayer { return this._dataRequests.some((dataRequest) => dataRequest.isLoading()); } + isLoadingBounds() { + const boundsDataRequest = this.getDataRequest(SOURCE_BOUNDS_DATA_REQUEST_ID); + return !!boundsDataRequest && boundsDataRequest.isLoading(); + } + hasErrors(): boolean { return _.get(this._descriptor, '__isInErrorState', false); } diff --git a/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts b/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts index a32ae15405fac..bed7599f89073 100644 --- a/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts +++ b/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts @@ -9,7 +9,6 @@ import { registerLayerWizard } from './layer_wizard_registry'; import { uploadLayerWizardConfig } from './file_upload_wizard'; // @ts-ignore import { esDocumentsLayerWizardConfig } from '../sources/es_search_source'; -// @ts-ignore import { clustersLayerWizardConfig, heatmapLayerWizardConfig } from '../sources/es_geo_grid_source'; import { geoLineLayerWizardConfig } from '../sources/es_geo_line_source'; // @ts-ignore diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts index c312ddec42572..b9cfb0067abd2 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts @@ -176,7 +176,6 @@ describe('createLayerDescriptor', () => { __dataRequests: [], alpha: 0.75, id: '12345', - joins: [], label: '[Performance] Duration', maxZoom: 24, minZoom: 0, diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts index fd9147d62cc26..03870e7668189 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts @@ -31,11 +31,9 @@ import { OBSERVABILITY_METRIC_TYPE } from './metric_select'; import { DISPLAY } from './display_select'; import { VectorStyle } from '../../../styles/vector/vector_style'; import { EMSFileSource } from '../../../sources/ems_file_source'; -// @ts-ignore import { ESGeoGridSource } from '../../../sources/es_geo_grid_source'; -import { VectorLayer } from '../../vector_layer/vector_layer'; -// @ts-ignore -import { HeatmapLayer } from '../../heatmap_layer/heatmap_layer'; +import { VectorLayer } from '../../vector_layer'; +import { HeatmapLayer } from '../../heatmap_layer'; import { getDefaultDynamicProperties } from '../../../styles/vector/vector_style_defaults'; // redefining APM constant to avoid making maps app depend on APM plugin diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts index 74a66276459c7..b2283196a41dd 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts @@ -22,7 +22,7 @@ import { SYMBOLIZE_AS_TYPES, VECTOR_STYLES, } from '../../../../../common/constants'; -import { VectorLayer } from '../../vector_layer/vector_layer'; +import { VectorLayer } from '../../vector_layer'; import { VectorStyle } from '../../../styles/vector/vector_style'; // @ts-ignore import { ESSearchSource } from '../../../sources/es_search_source'; diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx index d98396b960cbd..477b17ae03d7b 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx @@ -15,7 +15,7 @@ import { EuiIcon } from '@elastic/eui'; import { Feature } from 'geojson'; import { IVectorStyle, VectorStyle } from '../../styles/vector/vector_style'; import { SOURCE_DATA_REQUEST_ID, LAYER_TYPE } from '../../../../common/constants'; -import { VectorLayer, VectorLayerArguments } from '../vector_layer/vector_layer'; +import { VectorLayer, VectorLayerArguments } from '../vector_layer'; import { ITiledSingleLayerVectorSource } from '../../sources/vector_source'; import { DataRequestContext } from '../../../actions'; import { diff --git a/x-pack/plugins/maps/public/classes/util/assign_feature_ids.test.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/assign_feature_ids.test.ts similarity index 97% rename from x-pack/plugins/maps/public/classes/util/assign_feature_ids.test.ts rename to x-pack/plugins/maps/public/classes/layers/vector_layer/assign_feature_ids.test.ts index 4fe3804968b81..137d443b39b91 100644 --- a/x-pack/plugins/maps/public/classes/util/assign_feature_ids.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/assign_feature_ids.test.ts @@ -6,7 +6,7 @@ */ import { assignFeatureIds } from './assign_feature_ids'; -import { FEATURE_ID_PROPERTY_NAME } from '../../../common/constants'; +import { FEATURE_ID_PROPERTY_NAME } from '../../../../common/constants'; import { FeatureCollection, Feature, Point } from 'geojson'; const featureId = 'myFeature1'; diff --git a/x-pack/plugins/maps/public/classes/util/assign_feature_ids.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/assign_feature_ids.ts similarity index 96% rename from x-pack/plugins/maps/public/classes/util/assign_feature_ids.ts rename to x-pack/plugins/maps/public/classes/layers/vector_layer/assign_feature_ids.ts index f6b7851159586..c40c8299ad04c 100644 --- a/x-pack/plugins/maps/public/classes/util/assign_feature_ids.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/assign_feature_ids.ts @@ -7,7 +7,7 @@ import _ from 'lodash'; import { FeatureCollection, Feature } from 'geojson'; -import { FEATURE_ID_PROPERTY_NAME } from '../../../common/constants'; +import { FEATURE_ID_PROPERTY_NAME } from '../../../../common/constants'; let idCounter = 0; diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts new file mode 100644 index 0000000000000..4b509ba5dff00 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { addGeoJsonMbSource, syncVectorSource } from './utils'; +export { IVectorLayer, VectorLayer, VectorLayerArguments } from './vector_layer'; diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx new file mode 100644 index 0000000000000..a3754b20de818 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FeatureCollection } from 'geojson'; +import { Map as MbMap } from 'mapbox-gl'; +import { + EMPTY_FEATURE_COLLECTION, + SOURCE_DATA_REQUEST_ID, + VECTOR_SHAPE_TYPE, +} from '../../../../common/constants'; +import { VectorSourceRequestMeta } from '../../../../common/descriptor_types'; +import { DataRequestContext } from '../../../actions'; +import { IVectorSource } from '../../sources/vector_source'; +import { DataRequestAbortError } from '../../util/data_request'; +import { DataRequest } from '../../util/data_request'; +import { getCentroidFeatures } from '../../../../common/get_centroid_features'; +import { canSkipSourceUpdate } from '../../util/can_skip_fetch'; +import { assignFeatureIds } from './assign_feature_ids'; + +export function addGeoJsonMbSource(mbSourceId: string, mbLayerIds: string[], mbMap: MbMap) { + const mbSource = mbMap.getSource(mbSourceId); + if (!mbSource) { + mbMap.addSource(mbSourceId, { + type: 'geojson', + data: EMPTY_FEATURE_COLLECTION, + }); + } else if (mbSource.type !== 'geojson') { + // Recreate source when existing source is not geojson. This can occur when layer changes from tile layer to vector layer. + mbLayerIds.forEach((mbLayerId) => { + if (mbMap.getLayer(mbLayerId)) { + mbMap.removeLayer(mbLayerId); + } + }); + + mbMap.removeSource(mbSourceId); + mbMap.addSource(mbSourceId, { + type: 'geojson', + data: EMPTY_FEATURE_COLLECTION, + }); + } +} + +export async function syncVectorSource({ + layerId, + layerName, + prevDataRequest, + requestMeta, + syncContext, + source, +}: { + layerId: string; + layerName: string; + prevDataRequest: DataRequest | undefined; + requestMeta: VectorSourceRequestMeta; + syncContext: DataRequestContext; + source: IVectorSource; +}): Promise<{ refreshed: boolean; featureCollection: FeatureCollection }> { + const { + startLoading, + stopLoading, + onLoadError, + registerCancelCallback, + isRequestStillActive, + } = syncContext; + const dataRequestId = SOURCE_DATA_REQUEST_ID; + const requestToken = Symbol(`${layerId}-${dataRequestId}`); + const canSkipFetch = await canSkipSourceUpdate({ + source, + prevDataRequest, + nextMeta: requestMeta, + }); + if (canSkipFetch) { + return { + refreshed: false, + featureCollection: prevDataRequest + ? (prevDataRequest.getData() as FeatureCollection) + : EMPTY_FEATURE_COLLECTION, + }; + } + + try { + startLoading(dataRequestId, requestToken, requestMeta); + const { data: sourceFeatureCollection, meta } = await source.getGeoJsonWithMeta( + layerName, + requestMeta, + registerCancelCallback.bind(null, requestToken), + () => { + return isRequestStillActive(dataRequestId, requestToken); + } + ); + const layerFeatureCollection = assignFeatureIds(sourceFeatureCollection); + const supportedShapes = await source.getSupportedShapeTypes(); + if ( + supportedShapes.includes(VECTOR_SHAPE_TYPE.LINE) || + supportedShapes.includes(VECTOR_SHAPE_TYPE.POLYGON) + ) { + layerFeatureCollection.features.push(...getCentroidFeatures(layerFeatureCollection)); + } + stopLoading(dataRequestId, requestToken, layerFeatureCollection, meta); + return { + refreshed: true, + featureCollection: layerFeatureCollection, + }; + } catch (error) { + if (!(error instanceof DataRequestAbortError)) { + onLoadError(dataRequestId, requestToken, error.message); + } + throw error; + } +} diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index e9c0cb29c7c17..7e87d99fd4f93 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -13,10 +13,8 @@ import { EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { AbstractLayer } from '../layer'; import { IVectorStyle, VectorStyle } from '../../styles/vector/vector_style'; -import { getCentroidFeatures } from '../../../../common/get_centroid_features'; import { FEATURE_ID_PROPERTY_NAME, - SOURCE_DATA_REQUEST_ID, SOURCE_META_DATA_REQUEST_ID, SOURCE_FORMATTERS_DATA_REQUEST_ID, SOURCE_BOUNDS_DATA_REQUEST_ID, @@ -25,10 +23,8 @@ import { KBN_TOO_MANY_FEATURES_PROPERTY, LAYER_TYPE, FIELD_ORIGIN, - LAYER_STYLE_TYPE, KBN_TOO_MANY_FEATURES_IMAGE_ID, FieldFormatter, - VECTOR_SHAPE_TYPE, } from '../../../../common/constants'; import { JoinTooltipProperty } from '../../tooltips/join_tooltip_property'; import { DataRequestAbortError } from '../../util/data_request'; @@ -37,7 +33,6 @@ import { canSkipStyleMetaUpdate, canSkipFormattersUpdate, } from '../../util/can_skip_fetch'; -import { assignFeatureIds } from '../../util/assign_feature_ids'; import { getFeatureCollectionBounds } from '../../util/get_feature_collection_bounds'; import { getCentroidFilterExpression, @@ -65,6 +60,7 @@ import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_st import { IESSource } from '../../sources/es_source'; import { PropertiesMap } from '../../../../common/elasticsearch_util'; import { ITermJoinSource } from '../../sources/term_join_source'; +import { addGeoJsonMbSource, syncVectorSource } from './utils'; interface SourceResult { refreshed: boolean; @@ -95,7 +91,7 @@ export interface IVectorLayer extends ILayer { hasJoins(): boolean; } -export class VectorLayer extends AbstractLayer { +export class VectorLayer extends AbstractLayer implements IVectorLayer { static type = LAYER_TYPE.VECTOR; protected readonly _style: IVectorStyle; @@ -288,11 +284,6 @@ export class VectorLayer extends AbstractLayer { return bounds; } - isLoadingBounds() { - const boundsDataRequest = this.getDataRequest(SOURCE_BOUNDS_DATA_REQUEST_ID); - return !!boundsDataRequest && boundsDataRequest.isLoading(); - } - async getLeftJoinFields() { return await this.getSource().getLeftJoinFields(); } @@ -420,11 +411,9 @@ export class VectorLayer extends AbstractLayer { source: IVectorSource, style: IVectorStyle ): VectorSourceRequestMeta { - const styleFieldNames = - style.getType() === LAYER_STYLE_TYPE.VECTOR ? style.getSourceFieldNames() : []; const fieldNames = [ ...source.getFieldNames(), - ...styleFieldNames, + ...style.getSourceFieldNames(), ...this.getValidJoins().map((join) => join.getLeftField().getName()), ]; @@ -485,82 +474,11 @@ export class VectorLayer extends AbstractLayer { } } - async _syncSource( - syncContext: DataRequestContext, - source: IVectorSource, - style: IVectorStyle - ): Promise { - const { - startLoading, - stopLoading, - onLoadError, - registerCancelCallback, - dataFilters, - isRequestStillActive, - } = syncContext; - const dataRequestId = SOURCE_DATA_REQUEST_ID; - const requestToken = Symbol(`layer-${this.getId()}-${dataRequestId}`); - const searchFilters: VectorSourceRequestMeta = this._getSearchFilters( - dataFilters, - source, - style - ); - const prevDataRequest = this.getSourceDataRequest(); - const canSkipFetch = await canSkipSourceUpdate({ - source, - prevDataRequest, - nextMeta: searchFilters, - }); - if (canSkipFetch) { - return { - refreshed: false, - featureCollection: prevDataRequest - ? (prevDataRequest.getData() as FeatureCollection) - : EMPTY_FEATURE_COLLECTION, - }; - } - - try { - startLoading(dataRequestId, requestToken, searchFilters); - const layerName = await this.getDisplayName(source); - const { data: sourceFeatureCollection, meta } = await source.getGeoJsonWithMeta( - layerName, - searchFilters, - registerCancelCallback.bind(null, requestToken), - () => { - return isRequestStillActive(dataRequestId, requestToken); - } - ); - const layerFeatureCollection = assignFeatureIds(sourceFeatureCollection); - const supportedShapes = await source.getSupportedShapeTypes(); - if ( - supportedShapes.includes(VECTOR_SHAPE_TYPE.LINE) || - supportedShapes.includes(VECTOR_SHAPE_TYPE.POLYGON) - ) { - layerFeatureCollection.features.push(...getCentroidFeatures(layerFeatureCollection)); - } - stopLoading(dataRequestId, requestToken, layerFeatureCollection, meta); - return { - refreshed: true, - featureCollection: layerFeatureCollection, - }; - } catch (error) { - if (!(error instanceof DataRequestAbortError)) { - onLoadError(dataRequestId, requestToken, error.message); - } - throw error; - } - } - async _syncSourceStyleMeta( syncContext: DataRequestContext, source: IVectorSource, style: IVectorStyle ) { - if (this.getCurrentStyle().getType() !== LAYER_STYLE_TYPE.VECTOR) { - return; - } - const sourceQuery = this.getQuery() as MapQuery; return this._syncStyleMeta({ source, @@ -665,10 +583,6 @@ export class VectorLayer extends AbstractLayer { source: IVectorSource, style: IVectorStyle ) { - if (style.getType() !== LAYER_STYLE_TYPE.VECTOR) { - return; - } - return this._syncFormatters({ source, dataRequestId: SOURCE_FORMATTERS_DATA_REQUEST_ID, @@ -773,7 +687,14 @@ export class VectorLayer extends AbstractLayer { try { await this._syncSourceStyleMeta(syncContext, source, style); await this._syncSourceFormatters(syncContext, source, style); - const sourceResult = await this._syncSource(syncContext, source, style); + const sourceResult = await syncVectorSource({ + layerId: this.getId(), + layerName: await this.getDisplayName(source), + prevDataRequest: this.getSourceDataRequest(), + requestMeta: this._getSearchFilters(syncContext.dataFilters, source, style), + syncContext, + source, + }); if ( !sourceResult.featureCollection || !sourceResult.featureCollection.features.length || @@ -1061,31 +982,8 @@ export class VectorLayer extends AbstractLayer { this._setMbCentroidProperties(mbMap); } - _syncSourceBindingWithMb(mbMap: MbMap) { - const mbSource = mbMap.getSource(this._getMbSourceId()); - if (!mbSource) { - mbMap.addSource(this._getMbSourceId(), { - type: 'geojson', - data: EMPTY_FEATURE_COLLECTION, - }); - } else if (mbSource.type !== 'geojson') { - // Recreate source when existing source is not geojson. This can occur when layer changes from tile layer to vector layer. - this.getMbLayerIds().forEach((mbLayerId) => { - if (mbMap.getLayer(mbLayerId)) { - mbMap.removeLayer(mbLayerId); - } - }); - - mbMap.removeSource(this._getMbSourceId()); - mbMap.addSource(this._getMbSourceId(), { - type: 'geojson', - data: EMPTY_FEATURE_COLLECTION, - }); - } - } - syncLayerWithMB(mbMap: MbMap) { - this._syncSourceBindingWithMb(mbMap); + addGeoJsonMbSource(this._getMbSourceId(), this.getMbLayerIds(), mbMap); this._syncFeatureCollectionWithMb(mbMap); this._syncStylePropertiesWithMb(mbMap); } diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx index 3acc3c59e5930..d4cf4dbee7943 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { VectorLayer } from '../../layers/vector_layer/vector_layer'; +import { VectorLayer } from '../../layers/vector_layer'; import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_registry'; import { EMSFileCreateSourceEditor } from './create_source_editor'; import { EMSFileSource, getSourceTitle } from './ems_file_source'; diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.test.tsx b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.test.tsx index 5a0a3ed8df596..e711fb900e39a 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.test.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.test.tsx @@ -7,7 +7,7 @@ import { EMSFileSource } from './ems_file_source'; -jest.mock('../../layers/vector_layer/vector_layer', () => {}); +jest.mock('../../layers/vector_layer', () => {}); function makeEMSFileSource(tooltipProperties: string[]) { const emsFileSource = new EMSFileSource({ tooltipProperties }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx index 8951b7b278459..36dd28cb5bbf1 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx @@ -9,10 +9,9 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; // @ts-ignore import { CreateSourceEditor } from './create_source_editor'; -// @ts-ignore import { ESGeoGridSource, clustersTitle } from './es_geo_grid_source'; import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_registry'; -import { VectorLayer } from '../../layers/vector_layer/vector_layer'; +import { VectorLayer } from '../../layers/vector_layer'; import { ESGeoGridSourceDescriptor, ColorDynamicOptions, diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/heatmap_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/heatmap_layer_wizard.tsx index 83a7e02383f77..8fc26f3593750 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/heatmap_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/heatmap_layer_wizard.tsx @@ -9,11 +9,9 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; // @ts-ignore import { CreateSourceEditor } from './create_source_editor'; -// @ts-ignore import { ESGeoGridSource, heatmapTitle } from './es_geo_grid_source'; import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_registry'; -// @ts-ignore -import { HeatmapLayer } from '../../layers/heatmap_layer/heatmap_layer'; +import { HeatmapLayer } from '../../layers/heatmap_layer'; import { ESGeoGridSourceDescriptor } from '../../../../common/descriptor_types'; import { LAYER_WIZARD_CATEGORY, RENDER_AS } from '../../../../common/constants'; import { HeatmapLayerIcon } from '../../layers/icons/heatmap_layer_icon'; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/index.js b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/index.ts similarity index 100% rename from x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/index.js rename to x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/index.ts diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/layer_wizard.tsx index 6a1dfc74271d8..8da7037a5a34c 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/layer_wizard.tsx @@ -12,7 +12,7 @@ import { ESGeoLineSource, geoLineTitle, REQUIRES_GOLD_LICENSE_MSG } from './es_g import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_registry'; import { LAYER_WIZARD_CATEGORY, STYLE_TYPE, VECTOR_STYLES } from '../../../../common/constants'; import { VectorStyle } from '../../styles/vector/vector_style'; -import { VectorLayer } from '../../layers/vector_layer/vector_layer'; +import { VectorLayer } from '../../layers/vector_layer'; import { getIsGoldPlus } from '../../../licensed_features'; import { TracksLayerIcon } from '../../layers/icons/tracks_layer_icon'; diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx index 49b161711481c..c94c7859a85e7 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { getDefaultDynamicProperties } from '../../styles/vector/vector_style_defaults'; -import { VectorLayer } from '../../layers/vector_layer/vector_layer'; +import { VectorLayer } from '../../layers/vector_layer'; // @ts-ignore import { ESPewPewSource, sourceTitle } from './es_pew_pew_source'; import { VectorStyle } from '../../styles/vector/vector_style'; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/create_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/create_layer_descriptor.ts index 2734af5742dbb..41b4e8d7a318a 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/create_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/create_layer_descriptor.ts @@ -9,7 +9,7 @@ import { Query } from 'src/plugins/data/public'; import { LayerDescriptor } from '../../../../common/descriptor_types'; import { ES_GEO_FIELD_TYPE, SCALING_TYPES } from '../../../../common/constants'; import { ESSearchSource } from './es_search_source'; -import { VectorLayer } from '../../layers/vector_layer/vector_layer'; +import { VectorLayer } from '../../layers/vector_layer'; import { getIsGoldPlus } from '../../../licensed_features'; export interface CreateLayerDescriptorParams { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx index d01ed459e3171..c0606b5f4aec6 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx @@ -13,7 +13,7 @@ import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_re // @ts-ignore import { ESSearchSource, sourceTitle } from './es_search_source'; import { BlendedVectorLayer } from '../../layers/blended_vector_layer/blended_vector_layer'; -import { VectorLayer } from '../../layers/vector_layer/vector_layer'; +import { VectorLayer } from '../../layers/vector_layer'; import { LAYER_WIZARD_CATEGORY, SCALING_TYPES } from '../../../../common/constants'; import { TiledVectorLayer } from '../../layers/tiled_vector_layer/tiled_vector_layer'; import { DocumentsLayerIcon } from '../../layers/icons/documents_layer_icon'; diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js index a7994db286112..1f4a1ab7c9afa 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js +++ b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js @@ -7,7 +7,7 @@ import { ESTermSource, extractPropertiesMap } from './es_term_source'; -jest.mock('../../layers/vector_layer/vector_layer', () => {}); +jest.mock('../../layers/vector_layer', () => {}); const indexPatternTitle = 'myIndex'; const termFieldName = 'myTermField'; diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx index b41f599ac3d75..907b80e6405a6 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_registry'; // @ts-ignore import { KibanaRegionmapSource, sourceTitle } from './kibana_regionmap_source'; -import { VectorLayer } from '../../layers/vector_layer/vector_layer'; +import { VectorLayer } from '../../layers/vector_layer'; // @ts-ignore import { CreateSourceEditor } from './create_source_editor'; import { getKibanaRegionList } from '../../../meta'; diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx index f30040cf93b57..fe581a1807b28 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx @@ -31,7 +31,7 @@ export class HeatmapStyle implements IStyle { this._descriptor = HeatmapStyle.createDescriptor(descriptor.colorRampName); } - static createDescriptor(colorRampName: string) { + static createDescriptor(colorRampName?: string) { return { type: LAYER_STYLE_TYPE.HEATMAP, colorRampName: colorRampName ? colorRampName : DEFAULT_HEATMAP_COLOR_RAMP_NAME, diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.test.tsx index 3cfae4a836042..d0d3a7c2abe06 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { StyleProperties, VectorStyleEditor } from './vector_style_editor'; import { getDefaultStaticProperties } from '../vector_style_defaults'; -import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; +import { IVectorLayer } from '../../../layers/vector_layer'; import { IVectorSource } from '../../../sources/vector_source'; import { FIELD_ORIGIN, diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx index b36f3a38e2783..91bcc2dc06859 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx @@ -49,7 +49,7 @@ import { SymbolizeAsProperty } from '../properties/symbolize_as_property'; import { LabelBorderSizeProperty } from '../properties/label_border_size_property'; import { StaticTextProperty } from '../properties/static_text_property'; import { StaticSizeProperty } from '../properties/static_size_property'; -import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; +import { IVectorLayer } from '../../../layers/vector_layer'; export interface StyleProperties { [key: string]: IStyleProperty; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx index 03b7ce17063c3..b7e0133881ee1 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx @@ -24,7 +24,7 @@ import { } from '../../../../../common/constants'; import { mockField, MockLayer, MockStyle } from './test_helpers/test_util'; import { ColorDynamicOptions } from '../../../../../common/descriptor_types'; -import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; +import { IVectorLayer } from '../../../layers/vector_layer'; import { IField } from '../../../fields/field'; const makeProperty = (options: ColorDynamicOptions, style?: MockStyle, field?: IField) => { diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx index fc4d495f1e40a..46339c5a4a20d 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx @@ -20,7 +20,7 @@ import { DynamicIconProperty } from './dynamic_icon_property'; import { mockField, MockLayer } from './test_helpers/test_util'; import { IconDynamicOptions } from '../../../../../common/descriptor_types'; import { IField } from '../../../fields/field'; -import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; +import { IVectorLayer } from '../../../layers/vector_layer'; const makeProperty = (options: Partial, field: IField = mockField) => { const defaultOptions: IconDynamicOptions = { diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx index 40d72a357218f..64a3e0cf0e322 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx @@ -21,7 +21,7 @@ import { IField } from '../../../fields/field'; import { Map as MbMap } from 'mapbox-gl'; import { SizeDynamicOptions } from '../../../../../common/descriptor_types'; import { mockField, MockLayer, MockStyle } from './test_helpers/test_util'; -import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; +import { IVectorLayer } from '../../../layers/vector_layer'; export class MockMbMap { _paintPropertyCalls: unknown[]; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx index 52b78b4211a2d..7076775dcce31 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx @@ -20,7 +20,7 @@ import { import { FieldFormatter, MB_LOOKUP_FUNCTION, VECTOR_STYLES } from '../../../../../common/constants'; import { SizeDynamicOptions } from '../../../../../common/descriptor_types'; import { IField } from '../../../fields/field'; -import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; +import { IVectorLayer } from '../../../layers/vector_layer'; export class DynamicSizeProperty extends DynamicStyleProperty { private readonly _isSymbolizedAsIcon: boolean; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx index f62b17ee05ad6..9ffd9a0f1b345 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx @@ -34,7 +34,7 @@ import { StyleMetaData, } from '../../../../../common/descriptor_types'; import { IField } from '../../../fields/field'; -import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; +import { IVectorLayer } from '../../../layers/vector_layer'; import { InnerJoin } from '../../../joins/inner_join'; import { IVectorStyle } from '../vector_style'; import { getComputedFieldName } from '../style_util'; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx index c61e72807224a..692be08d07bc6 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx @@ -70,7 +70,7 @@ import { DataRequest } from '../../util/data_request'; import { IStyle } from '../style'; import { IStyleProperty } from './properties/style_property'; import { IField } from '../../fields/field'; -import { IVectorLayer } from '../../layers/vector_layer/vector_layer'; +import { IVectorLayer } from '../../layers/vector_layer'; import { IVectorSource } from '../../sources/vector_source'; import { createStyleFieldsHelper, StyleFieldsHelper } from './style_fields_helper'; import { IESAggField } from '../../fields/agg'; diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts index dd6a9fc377e5b..c2f5fc02c5df2 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts @@ -5,10 +5,10 @@ * 2.0. */ -jest.mock('../classes/layers/vector_layer/vector_layer', () => {}); +jest.mock('../classes/layers/vector_layer', () => {}); jest.mock('../classes/layers/tiled_vector_layer/tiled_vector_layer', () => {}); jest.mock('../classes/layers/blended_vector_layer/blended_vector_layer', () => {}); -jest.mock('../classes/layers/heatmap_layer/heatmap_layer', () => {}); +jest.mock('../classes/layers/heatmap_layer', () => {}); jest.mock('../classes/layers/vector_tile_layer/vector_tile_layer', () => {}); jest.mock('../classes/joins/inner_join', () => {}); jest.mock('../kibana_services', () => ({ diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index 27281fe17f0fa..f53f39ad2fc0c 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -12,10 +12,9 @@ import { Adapters } from 'src/plugins/inspector/public'; import { TileLayer } from '../classes/layers/tile_layer/tile_layer'; // @ts-ignore import { VectorTileLayer } from '../classes/layers/vector_tile_layer/vector_tile_layer'; -import { IVectorLayer, VectorLayer } from '../classes/layers/vector_layer/vector_layer'; +import { IVectorLayer, VectorLayer } from '../classes/layers/vector_layer'; import { VectorStyle } from '../classes/styles/vector/vector_style'; -// @ts-ignore -import { HeatmapLayer } from '../classes/layers/heatmap_layer/heatmap_layer'; +import { HeatmapLayer } from '../classes/layers/heatmap_layer'; import { BlendedVectorLayer } from '../classes/layers/blended_vector_layer/blended_vector_layer'; import { getTimeFilter } from '../kibana_services'; import { @@ -41,6 +40,7 @@ import { DataRequestDescriptor, DrawState, Goto, + HeatmapLayerDescriptor, LayerDescriptor, MapCenter, MapExtent, @@ -54,6 +54,7 @@ import { Filter, TimeRange } from '../../../../../src/plugins/data/public'; import { ISource } from '../classes/sources/source'; import { ITMSSource } from '../classes/sources/tms_source'; import { IVectorSource } from '../classes/sources/vector_source'; +import { ESGeoGridSource } from '../classes/sources/es_geo_grid_source'; import { ILayer } from '../classes/layers/layer'; export function createLayerInstance( @@ -84,7 +85,10 @@ export function createLayerInstance( case VectorTileLayer.type: return new VectorTileLayer({ layerDescriptor, source: source as ITMSSource }); case HeatmapLayer.type: - return new HeatmapLayer({ layerDescriptor, source }); + return new HeatmapLayer({ + layerDescriptor: layerDescriptor as HeatmapLayerDescriptor, + source: source as ESGeoGridSource, + }); case BlendedVectorLayer.type: return new BlendedVectorLayer({ layerDescriptor: layerDescriptor as VectorLayerDescriptor, From d14e37fd685beec8318323746da998fadce61dc7 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Tue, 9 Feb 2021 15:00:37 -0800 Subject: [PATCH 8/9] Bump @elastic/ems-client@7.12.0 (#90841) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7144745f2ae35..aac576dbc3561 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "@babel/runtime": "^7.12.5", "@elastic/datemath": "link:packages/elastic-datemath", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary", - "@elastic/ems-client": "7.11.0", + "@elastic/ems-client": "7.12.0", "@elastic/eui": "31.4.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "^9.0.1-kibana3", diff --git a/yarn.lock b/yarn.lock index ec6cf338a43da..c9f3186ffcba2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2187,10 +2187,10 @@ pump "^3.0.0" secure-json-parse "^2.1.0" -"@elastic/ems-client@7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@elastic/ems-client/-/ems-client-7.11.0.tgz#d2142d0ef5bd1aff7ae67b37c1394b73cdd48d8b" - integrity sha512-7+gDEkBr8nRS7X9i/UPg1WkS7bEBuNbBBjXCchQeYwqPRmw6vOb4wjlNzVwmOFsp2OH4lVFfZ+XU4pxTt32EXA== +"@elastic/ems-client@7.12.0": + version "7.12.0" + resolved "https://registry.yarnpkg.com/@elastic/ems-client/-/ems-client-7.12.0.tgz#cf83f5ad76e26cedfa6f5b91277d2d919b9423d1" + integrity sha512-Svv3boWL1n14nIt6tL9gaA9Ym1B4AwWl6ISZT62+uKM2G+imZxWLkqpQc/HHcf7TfuAmleF2NFwnT5vw2vZTpA== dependencies: lodash "^4.17.15" semver "7.3.2" From 8e4f8993800f4990c007191980975b71a9173abe Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Tue, 9 Feb 2021 17:21:44 -0600 Subject: [PATCH 9/9] Ignore intermediate records in ML Rule anomalies query (#90316) We were incorrectly including records with is_interim: true in our query, which lead to false positive signals if the rule executed while an anomaly's score was (temporarily) above the specified threshold, but then dipped below after it was finalized. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/lib/machine_learning/index.test.ts | 21 +++++++++++++++++-- .../server/lib/machine_learning/index.ts | 1 + 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/machine_learning/index.test.ts b/x-pack/plugins/security_solution/server/lib/machine_learning/index.test.ts index 86133d93e99c8..1f49ac7bf5019 100644 --- a/x-pack/plugins/security_solution/server/lib/machine_learning/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/machine_learning/index.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ESFilter } from '../../../../../typings/elasticsearch'; import { getExceptionListItemSchemaMock } from '../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { getAnomalies, AnomaliesSearchParams } from '.'; @@ -13,8 +14,8 @@ const getFiltersFromMock = (mock: jest.Mock) => { return searchParams.body.query.bool.filter; }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const getBoolCriteriaFromFilters = (filters: any[]) => filters[1].bool.must; +const getBoolCriteriaFromFilters = (filters: ESFilter[]) => + filters.find((filter) => filter?.bool?.must)?.bool?.must; describe('getAnomalies', () => { let searchParams: AnomaliesSearchParams; @@ -104,4 +105,20 @@ describe('getAnomalies', () => { ]) ); }); + + it('ignores anomalies that do not have finalized scores', () => { + const mockMlAnomalySearch = jest.fn(); + getAnomalies(searchParams, mockMlAnomalySearch); + const filters = getFiltersFromMock(mockMlAnomalySearch); + + expect(filters).toEqual( + expect.arrayContaining([ + { + term: { + is_interim: false, + }, + }, + ]) + ); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts b/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts index 962c44174d891..c3fea9f6d916f 100644 --- a/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts +++ b/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts @@ -47,6 +47,7 @@ export const getAnomalies = async ( analyze_wildcard: false, }, }, + { term: { is_interim: false } }, { bool: { must: boolCriteria,