From 4f5b456ee8754a24b3cb12cb3746ab8f8d1e4f33 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Mon, 24 Feb 2020 19:20:18 -0600 Subject: [PATCH 01/28] Add flyout with expressions --- x-pack/plugins/infra/kibana.json | 2 +- .../plugins/infra/public/apps/start_app.tsx | 36 +-- .../components/alerting/alert_flyout.tsx | 40 ++++ .../public/components/alerting/expression.tsx | 218 ++++++++++++++++++ .../components/alerting/metric_alert_type.ts | 21 ++ .../public/components/alerting/validation.tsx | 29 +++ .../metrics_explorer/chart_context_menu.tsx | 42 +++- .../components/waffle/node_context_menu.tsx | 16 +- x-pack/plugins/infra/public/plugin.ts | 11 +- .../public/utils/triggers_actions_context.tsx | 32 +++ 10 files changed, 417 insertions(+), 30 deletions(-) create mode 100644 x-pack/plugins/infra/public/components/alerting/alert_flyout.tsx create mode 100644 x-pack/plugins/infra/public/components/alerting/expression.tsx create mode 100644 x-pack/plugins/infra/public/components/alerting/metric_alert_type.ts create mode 100644 x-pack/plugins/infra/public/components/alerting/validation.tsx create mode 100644 x-pack/plugins/infra/public/utils/triggers_actions_context.tsx diff --git a/x-pack/plugins/infra/kibana.json b/x-pack/plugins/infra/kibana.json index 7ba81127c1e67..a044643e8ad0e 100644 --- a/x-pack/plugins/infra/kibana.json +++ b/x-pack/plugins/infra/kibana.json @@ -2,7 +2,7 @@ "id": "infra", "version": "8.0.0", "kibanaVersion": "kibana", - "requiredPlugins": ["features", "apm", "usageCollection", "spaces", "home", "data", "data_enhanced", "metrics"], + "requiredPlugins": ["features", "apm", "usageCollection", "spaces", "home", "data", "data_enhanced", "metrics", "triggers_actions_ui"], "server": true, "ui": true, "configPath": ["xpack", "infra"] diff --git a/x-pack/plugins/infra/public/apps/start_app.tsx b/x-pack/plugins/infra/public/apps/start_app.tsx index 300d97d3c45b1..e633fd34e9b1c 100644 --- a/x-pack/plugins/infra/public/apps/start_app.tsx +++ b/x-pack/plugins/infra/public/apps/start_app.tsx @@ -15,7 +15,8 @@ import { CoreStart, AppMountParameters } from 'kibana/public'; // TODO use theme provided from parentApp when kibana supports it import { EuiErrorBoundary } from '@elastic/eui'; -import { EuiThemeProvider } from '../../../observability/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { EuiThemeProvider } from '../../../observability/public/typings/eui_styled_components'; import { InfraFrontendLibs } from '../lib/lib'; import { createStore } from '../store'; import { ApolloClientContext } from '../utils/apollo_context'; @@ -26,6 +27,8 @@ import { KibanaContextProvider, } from '../../../../../src/plugins/kibana_react/public'; import { AppRouter } from '../routers'; +import { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public'; +import { TriggersActionsProvider } from '../utils/triggers_actions_context'; export const CONTAINER_CLASSNAME = 'infra-container-element'; @@ -34,7 +37,8 @@ export async function startApp( core: CoreStart, plugins: object, params: AppMountParameters, - Router: AppRouter + Router: AppRouter, + triggersActionsUI: TriggersAndActionsUIPublicPluginSetup ) { const { element, appBasePath } = params; const history = createBrowserHistory({ basename: appBasePath }); @@ -50,19 +54,21 @@ export async function startApp( return ( - - - - - - - - - - - - - + + + + + + + + + + + + + + + ); diff --git a/x-pack/plugins/infra/public/components/alerting/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/alert_flyout.tsx new file mode 100644 index 0000000000000..df1a8463bc5fb --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/alert_flyout.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import { IFieldType } from 'src/plugins/data/public'; +import { AlertsContextProvider, AlertAdd } from '../../../../triggers_actions_ui/public'; +import { TriggerActionsContext } from '../../utils/triggers_actions_context'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; + +interface Props { + visible?: boolean; + fields: IFieldType[]; + setVisible: React.Dispatch>; +} + +export const AlertFlyout = (props: Props) => { + const { triggersActionsUI } = useContext(TriggerActionsContext); + const { services } = useKibana(); + + return ( + <> + {triggersActionsUI && ( + + + + )} + + ); +}; diff --git a/x-pack/plugins/infra/public/components/alerting/expression.tsx b/x-pack/plugins/infra/public/components/alerting/expression.tsx new file mode 100644 index 0000000000000..536ca6906c6be --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/expression.tsx @@ -0,0 +1,218 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useMemo } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; +import { + WhenExpression, + OfExpression, + ThresholdExpression, + ForLastExpression, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../triggers_actions_ui/public/common'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { IErrorObject } from '../../../../triggers_actions_ui/public/types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { AlertsContextValue } from '../../../../triggers_actions_ui/public/application/context/alerts_context'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { AGGREGATION_TYPES } from '../../../../triggers_actions_ui/public/common/constants'; + +interface MetricExpressionParams { + aggType?: string; + metric?: string; + comparator?: Comparator; + threshold?: number[]; + timeSize?: number; + timeUnit?: TimeUnit; +} + +interface Props { + errors: IErrorObject; + alertParams: { expressions: MetricExpressionParams[] }; + alertContext: AlertsContextValue; + setAlertParams(key: string, value: any): void; + setAlertProperty(key: string, value: any): void; +} + +type Comparator = '>' | '>=' | 'between' | '<' | '<='; +type TimeUnit = 's' | 'm' | 'h' | 'd'; + +export const MetricExpression: React.FC = props => { + const { setAlertParams, alertParams, errors } = props; + + const expressions = useMemo(() => { + return alertParams.expressions || [{}]; + }, [alertParams.expressions]); + + const updateParams = useCallback( + (id, e: MetricExpressionParams) => { + const exp = alertParams.expressions ? alertParams.expressions.slice() : []; + exp[id] = { ...exp[id], ...e }; + setAlertParams('expressions', exp); + }, + [setAlertParams, alertParams.expressions] + ); + + const addExpression = useCallback(() => { + const exp = alertParams.expressions ? alertParams.expressions.slice() : []; + exp.push({}); + setAlertParams('expressions', exp); + }, [setAlertParams, alertParams.expressions]); + + return ( + <> + Add Expression + {expressions.map((e, idx) => { + return ( + + ); + })} + + ); +}; + +interface ExpressionRowProps { + expressionId: number; + expression: MetricExpressionParams; + errors: any; + setAlertParams(id: number, params: MetricExpressionParams): void; +} +export const ExpressionRow: React.FC = props => { + const { setAlertParams, expression, errors, expressionId } = props; + const { + aggType = 'count', + metric, + comparator = '>', + threshold = [], + timeSize, + timeUnit = 's', + } = expression; + + const updateAggType = useCallback( + (at: string) => { + setAlertParams(expressionId, { aggType: at }); + }, + [expressionId, setAlertParams] + ); + + const updateMetric = useCallback( + (m?: string) => { + setAlertParams(expressionId, { metric: m }); + }, + [expressionId, setAlertParams] + ); + + const updateComparator = useCallback( + (c?: string) => { + setAlertParams(expressionId, { comparator: c as Comparator }); + }, + [expressionId, setAlertParams] + ); + + const updateThreshold = useCallback( + t => { + setAlertParams(expressionId, { threshold: t }); + }, + [expressionId, setAlertParams] + ); + + const updateTimeSize = useCallback( + (ts: number | '') => { + setAlertParams(expressionId, { timeSize: ts || undefined }); + }, + [expressionId, setAlertParams] + ); + + const updateTimeUnit = useCallback( + (tu: string) => { + setAlertParams(expressionId, { timeUnit: tu as TimeUnit }); + }, + [expressionId, setAlertParams] + ); + + return ( + <> + + + + + + + + + + + '} + threshold={threshold} + onChangeSelectedThresholdComparator={updateComparator} + onChangeSelectedThreshold={updateThreshold} + errors={errors} + /> + + + + + + + + ); +}; + +export const aggregationType: { [key: string]: any } = { + count: { + text: 'count', + fieldRequired: false, + value: AGGREGATION_TYPES.COUNT, + validNormalizedTypes: [], + }, + avg: { + text: 'average', + fieldRequired: true, + validNormalizedTypes: ['number'], + value: AGGREGATION_TYPES.AVERAGE, + }, + sum: { + text: 'sum', + fieldRequired: true, + validNormalizedTypes: ['number'], + value: AGGREGATION_TYPES.SUM, + }, + min: { + text: 'min', + fieldRequired: true, + validNormalizedTypes: ['number', 'date'], + value: AGGREGATION_TYPES.MIN, + }, + max: { + text: 'max', + fieldRequired: true, + validNormalizedTypes: ['number', 'date'], + value: AGGREGATION_TYPES.MAX, + }, +}; diff --git a/x-pack/plugins/infra/public/components/alerting/metric_alert_type.ts b/x-pack/plugins/infra/public/components/alerting/metric_alert_type.ts new file mode 100644 index 0000000000000..57f6d2275402b --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/metric_alert_type.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IFieldType } from 'src/plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; +import { MetricExpression } from './expression'; +import { validateExampleAlertType } from './validation'; + +export function getAlertType(fields: IFieldType[]): AlertTypeModel { + return { + id: 'example', + name: 'Alert Trigger', + iconClass: 'bell', + alertParamsExpression: MetricExpression, + validate: validateExampleAlertType, + }; +} diff --git a/x-pack/plugins/infra/public/components/alerting/validation.tsx b/x-pack/plugins/infra/public/components/alerting/validation.tsx new file mode 100644 index 0000000000000..50827615cf013 --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/validation.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ValidationResult } from '../../../../triggers_actions_ui/public/types'; + +export function validateExampleAlertType({ + testAggField, +}: { + testAggField: string; +}): ValidationResult { + const validationResult = { errors: {} }; + const errors = { + aggField: new Array(), + }; + validationResult.errors = errors; + if (!testAggField) { + errors.aggField.push( + i18n.translate('xpack.triggersActionsUI.components.example.error.requiredTestAggFieldText', { + defaultMessage: 'Test aggregation field is required.', + }) + ); + } + return validationResult; +} diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx index f7c97033f8d50..1bf48b6895dc8 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useState, useContext } from 'react'; import { i18n } from '@kbn/i18n'; import { @@ -19,12 +19,14 @@ import { MetricsExplorerOptions, MetricsExplorerTimeOptions, MetricsExplorerChartOptions, + MetricsExplorerOptionsContainer, } from '../../containers/metrics_explorer/use_metrics_explorer_options'; import { createTSVBLink } from './helpers/create_tsvb_link'; import { getNodeDetailUrl } from '../../pages/link_to/redirect_to_node_detail'; import { SourceConfiguration } from '../../utils/source_configuration'; import { InventoryItemType } from '../../../common/inventory_models/types'; import { usePrefixPathWithBasepath } from '../../hooks/use_prefix_path_with_basepath'; +import { AlertFlyout } from '../alerting/alert_flyout'; export interface Props { options: MetricsExplorerOptions; @@ -82,6 +84,7 @@ export const MetricsExplorerChartContextMenu: React.FC = ({ }: Props) => { const urlPrefixer = usePrefixPathWithBasepath(); const [isPopoverOpen, setPopoverState] = useState(false); + const [flyoutVisible, setFlyoutVisible] = useState(false); const supportFiltering = options.groupBy != null && onFilter != null; const handleFilter = useCallback(() => { // onFilter needs check for Typescript even though it's @@ -140,7 +143,19 @@ export const MetricsExplorerChartContextMenu: React.FC = ({ ] : []; - const itemPanels = [...filterByItem, ...openInVisualize, ...viewNodeDetail]; + const itemPanels = [ + ...filterByItem, + ...openInVisualize, + ...viewNodeDetail, + { + name: i18n.translate('xpack.infra.alerts.createAlertButton', { + defaultMessage: 'Create alert', + }), + onClick() { + setFlyoutVisible(true); + }, + }, + ]; // If there are no itemPanels then there is no reason to show the actions button. if (itemPanels.length === 0) return null; @@ -173,15 +188,20 @@ export const MetricsExplorerChartContextMenu: React.FC = ({ {actionLabel} ); + + // options. return ( - - - + <> + + + + + ); }; diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index 43d27bb8259b3..925cfb8ef2998 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -8,7 +8,7 @@ import { EuiPopoverProps, EuiCode } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib'; import { getNodeDetailUrl, getNodeLogsUrl } from '../../pages/link_to'; import { createUptimeLink } from './lib/create_uptime_link'; @@ -25,6 +25,7 @@ import { SectionLink, } from '../../../../observability/public'; import { usePrefixPathWithBasepath } from '../../hooks/use_prefix_path_with_basepath'; +import { AlertFlyout } from '../alerting/alert_flyout'; interface Props { options: InfraWaffleMapOptions; @@ -64,6 +65,8 @@ export const NodeContextMenu: React.FC = ({ const showUptimeLink = inventoryModel.crosslinkSupport.uptime && (['pod', 'container'].includes(nodeType) || node.ip); + const [flyoutVisible, setFlyoutVisible] = useState(false); + const inventoryId = useMemo(() => { if (nodeType === 'host') { if (node.ip) { @@ -134,6 +137,15 @@ export const NodeContextMenu: React.FC = ({ isDisabled: !showUptimeLink, }; + const alertMenuItem: SectionLinkProps = { + label: i18n.translate('xpack.infra.alerts.createAlertButton', { + defaultMessage: 'Create alert', + }), + onClick() { + setFlyoutVisible(true); + }, + }; + return ( = ({ href={uptimeMenuItem.href} isDisabled={uptimeMenuItem.isDisabled} /> + + diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index 5d529e1fda0dc..0f0140c88603a 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -29,6 +29,8 @@ import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/pl import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; import { DataEnhancedSetup, DataEnhancedStart } from '../../data_enhanced/public'; import { LogsRouter, MetricsRouter } from './routers'; +import { TriggersAndActionsUIPublicPluginSetup } from '../../../plugins/triggers_actions_ui/public'; +import { getAlertType } from './components/alerting/metric_alert_type'; export type ClientSetup = void; export type ClientStart = void; @@ -38,6 +40,7 @@ export interface ClientPluginsSetup { data: DataPublicPluginSetup; usageCollection: UsageCollectionSetup; dataEnhanced: DataEnhancedSetup; + triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup; } export interface ClientPluginsStart { @@ -59,6 +62,8 @@ export class Plugin setup(core: CoreSetup, pluginsSetup: ClientPluginsSetup) { registerFeatures(pluginsSetup.home); + pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(getAlertType()); + core.application.register({ id: 'logs', title: i18n.translate('xpack.infra.logs.pluginTitle', { @@ -77,7 +82,8 @@ export class Plugin coreStart, plugins, params, - LogsRouter + LogsRouter, + pluginsSetup.triggers_actions_ui ); }, }); @@ -100,7 +106,8 @@ export class Plugin coreStart, plugins, params, - MetricsRouter + MetricsRouter, + pluginsSetup.triggers_actions_ui ); }, }); diff --git a/x-pack/plugins/infra/public/utils/triggers_actions_context.tsx b/x-pack/plugins/infra/public/utils/triggers_actions_context.tsx new file mode 100644 index 0000000000000..4ca4aedb4a08b --- /dev/null +++ b/x-pack/plugins/infra/public/utils/triggers_actions_context.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public'; + +interface ContextProps { + triggersActionsUI: TriggersAndActionsUIPublicPluginSetup | null; +} + +export const TriggerActionsContext = React.createContext({ + triggersActionsUI: null, +}); + +interface Props { + triggersActionsUI: TriggersAndActionsUIPublicPluginSetup; +} + +export const TriggersActionsProvider: React.FC = props => { + return ( + + {props.children} + + ); +}; From f0d8b6181144485a30844a483b09f86d98cef78c Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Thu, 27 Feb 2020 15:30:56 -0600 Subject: [PATCH 02/28] Integrate frontend with backend --- .../alerting/{ => metrics}/alert_flyout.tsx | 16 ++- .../alerting/{ => metrics}/expression.tsx | 116 ++++++++++-------- .../{ => metrics}/metric_alert_type.ts | 14 +-- .../metrics_explorer/chart_context_menu.tsx | 7 +- .../components/waffle/node_context_menu.tsx | 3 +- x-pack/plugins/infra/public/plugin.ts | 2 +- .../sections/alert_add/alert_form.tsx | 12 +- 7 files changed, 96 insertions(+), 74 deletions(-) rename x-pack/plugins/infra/public/components/alerting/{ => metrics}/alert_flyout.tsx (63%) rename x-pack/plugins/infra/public/components/alerting/{ => metrics}/expression.tsx (59%) rename x-pack/plugins/infra/public/components/alerting/{ => metrics}/metric_alert_type.ts (53%) diff --git a/x-pack/plugins/infra/public/components/alerting/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx similarity index 63% rename from x-pack/plugins/infra/public/components/alerting/alert_flyout.tsx rename to x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx index df1a8463bc5fb..1ddf902133ded 100644 --- a/x-pack/plugins/infra/public/components/alerting/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx @@ -5,14 +5,14 @@ */ import React, { useContext } from 'react'; -import { IFieldType } from 'src/plugins/data/public'; -import { AlertsContextProvider, AlertAdd } from '../../../../triggers_actions_ui/public'; -import { TriggerActionsContext } from '../../utils/triggers_actions_context'; -import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { AlertsContextProvider, AlertAdd } from '../../../../../triggers_actions_ui/public'; +import { TriggerActionsContext } from '../../../utils/triggers_actions_context'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/metric_threshold/types'; interface Props { visible?: boolean; - fields: IFieldType[]; setVisible: React.Dispatch>; } @@ -32,7 +32,11 @@ export const AlertFlyout = (props: Props) => { alertTypeRegistry: triggersActionsUI.alertTypeRegistry, }} > - + )} diff --git a/x-pack/plugins/infra/public/components/alerting/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx similarity index 59% rename from x-pack/plugins/infra/public/components/alerting/expression.tsx rename to x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 536ca6906c6be..477249367963c 100644 --- a/x-pack/plugins/infra/public/components/alerting/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -5,20 +5,20 @@ */ import React, { useCallback, useMemo } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonIcon } from '@elastic/eui'; import { WhenExpression, OfExpression, ThresholdExpression, ForLastExpression, // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../triggers_actions_ui/public/common'; +} from '../../../../../triggers_actions_ui/public/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { IErrorObject } from '../../../../triggers_actions_ui/public/types'; +import { IErrorObject } from '../../../../../triggers_actions_ui/public/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { AlertsContextValue } from '../../../../triggers_actions_ui/public/application/context/alerts_context'; +import { AlertsContextValue } from '../../../../../triggers_actions_ui/public/application/context/alerts_context'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { AGGREGATION_TYPES } from '../../../../triggers_actions_ui/public/common/constants'; +import { AGGREGATION_TYPES } from '../../../../../triggers_actions_ui/public/common/constants'; interface MetricExpressionParams { aggType?: string; @@ -31,7 +31,7 @@ interface MetricExpressionParams { interface Props { errors: IErrorObject; - alertParams: { expressions: MetricExpressionParams[] }; + alertParams: { criteria: MetricExpressionParams[] }; alertContext: AlertsContextValue; setAlertParams(key: string, value: any): void; setAlertProperty(key: string, value: any): void; @@ -44,23 +44,32 @@ export const MetricExpression: React.FC = props => { const { setAlertParams, alertParams, errors } = props; const expressions = useMemo(() => { - return alertParams.expressions || [{}]; - }, [alertParams.expressions]); + return alertParams.criteria || [{}]; + }, [alertParams.criteria]); const updateParams = useCallback( (id, e: MetricExpressionParams) => { - const exp = alertParams.expressions ? alertParams.expressions.slice() : []; + const exp = alertParams.criteria ? alertParams.criteria.slice() : []; exp[id] = { ...exp[id], ...e }; - setAlertParams('expressions', exp); + setAlertParams('criteria', exp); }, - [setAlertParams, alertParams.expressions] + [setAlertParams, alertParams.criteria] ); const addExpression = useCallback(() => { - const exp = alertParams.expressions ? alertParams.expressions.slice() : []; + const exp = alertParams.criteria ? alertParams.criteria.slice() : []; exp.push({}); - setAlertParams('expressions', exp); - }, [setAlertParams, alertParams.expressions]); + setAlertParams('criteria', exp); + }, [setAlertParams, alertParams.criteria]); + + const removeExpression = useCallback( + (id: number) => { + const exp = alertParams.criteria ? alertParams.criteria.slice() : []; + exp.splice(id, 1); + setAlertParams('criteria', exp); + }, + [setAlertParams, alertParams.criteria] + ); return ( <> @@ -68,6 +77,7 @@ export const MetricExpression: React.FC = props => { {expressions.map((e, idx) => { return ( = props => { - const { setAlertParams, expression, errors, expressionId } = props; + const { setAlertParams, expression, errors, expressionId, remove } = props; const { aggType = 'count', metric, @@ -142,42 +153,49 @@ export const ExpressionRow: React.FC = props => { return ( <> - - - - - - - - - - '} - threshold={threshold} - onChangeSelectedThresholdComparator={updateComparator} - onChangeSelectedThreshold={updateThreshold} - errors={errors} - /> + + + + + + + + + + + + '} + threshold={threshold} + onChangeSelectedThresholdComparator={updateComparator} + onChangeSelectedThreshold={updateThreshold} + errors={errors} + /> + + + + + + - - + remove(expressionId)} /> diff --git a/x-pack/plugins/infra/public/components/alerting/metric_alert_type.ts b/x-pack/plugins/infra/public/components/alerting/metrics/metric_alert_type.ts similarity index 53% rename from x-pack/plugins/infra/public/components/alerting/metric_alert_type.ts rename to x-pack/plugins/infra/public/components/alerting/metrics/metric_alert_type.ts index 57f6d2275402b..6be9428b07edc 100644 --- a/x-pack/plugins/infra/public/components/alerting/metric_alert_type.ts +++ b/x-pack/plugins/infra/public/components/alerting/metrics/metric_alert_type.ts @@ -3,17 +3,17 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { IFieldType } from 'src/plugins/data/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; +import { AlertTypeModel } from '../../../../../triggers_actions_ui/public/types'; import { MetricExpression } from './expression'; -import { validateExampleAlertType } from './validation'; +import { validateExampleAlertType } from '../validation'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/metric_threshold/types'; -export function getAlertType(fields: IFieldType[]): AlertTypeModel { +export function getAlertType(): AlertTypeModel { return { - id: 'example', - name: 'Alert Trigger', + id: METRIC_THRESHOLD_ALERT_TYPE_ID, + name: 'Threshold Alert', iconClass: 'bell', alertParamsExpression: MetricExpression, validate: validateExampleAlertType, diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx index 1bf48b6895dc8..5f246f35e9561 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useState, useContext } from 'react'; +import React, { useCallback, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { @@ -19,14 +19,13 @@ import { MetricsExplorerOptions, MetricsExplorerTimeOptions, MetricsExplorerChartOptions, - MetricsExplorerOptionsContainer, } from '../../containers/metrics_explorer/use_metrics_explorer_options'; import { createTSVBLink } from './helpers/create_tsvb_link'; import { getNodeDetailUrl } from '../../pages/link_to/redirect_to_node_detail'; import { SourceConfiguration } from '../../utils/source_configuration'; import { InventoryItemType } from '../../../common/inventory_models/types'; import { usePrefixPathWithBasepath } from '../../hooks/use_prefix_path_with_basepath'; -import { AlertFlyout } from '../alerting/alert_flyout'; +import { AlertFlyout } from '../alerting/metrics/alert_flyout'; export interface Props { options: MetricsExplorerOptions; @@ -200,8 +199,8 @@ export const MetricsExplorerChartContextMenu: React.FC = ({ panelPaddingSize="none" > + - ); }; diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index 925cfb8ef2998..93162f40d9f4c 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -25,7 +25,7 @@ import { SectionLink, } from '../../../../observability/public'; import { usePrefixPathWithBasepath } from '../../hooks/use_prefix_path_with_basepath'; -import { AlertFlyout } from '../alerting/alert_flyout'; +import { AlertFlyout } from '../alerting/metrics/alert_flyout'; interface Props { options: InfraWaffleMapOptions; @@ -198,7 +198,6 @@ export const NodeContextMenu: React.FC = ({ isDisabled={uptimeMenuItem.isDisabled} /> - diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index 0f0140c88603a..7939f2ae50889 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -30,7 +30,7 @@ import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/p import { DataEnhancedSetup, DataEnhancedStart } from '../../data_enhanced/public'; import { LogsRouter, MetricsRouter } from './routers'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../plugins/triggers_actions_ui/public'; -import { getAlertType } from './components/alerting/metric_alert_type'; +import { getAlertType } from './components/alerting/metrics/metric_alert_type'; export type ClientSetup = void; export type ClientStart = void; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_form.tsx index 18dc88f54e907..4bb43b4d7c682 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_form.tsx @@ -252,11 +252,13 @@ export const AlertForm = ({ function addActionType(actionTypeModel: ActionTypeModel) { if (!defaultActionGroupId) { - toastNotifications!.addDanger({ - title: i18n.translate('xpack.triggersActionsUI.sections.alertForm.unableToAddAction', { - defaultMessage: 'Unable to add action, because default action group is not defined', - }), - }); + if (toastNotifications) { + toastNotifications!.addDanger({ + title: i18n.translate('xpack.triggersActionsUI.sections.alertForm.unableToAddAction', { + defaultMessage: 'Unable to add action, because default action group is not defined', + }), + }); + } return; } setIsAddActionPanelOpen(false); From b796adab7fb5f344a74bba8176c7f82bb9903a70 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Wed, 4 Mar 2020 16:10:33 -0800 Subject: [PATCH 03/28] Extended AlertContextValue with metadata optional property --- .../threshold/expression.tsx | 3 +- .../application/context/alerts_context.tsx | 1 + .../sections/alert_form/alert_add.test.tsx | 33 +++++++++++++++---- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx index a2ef67be7bca2..37da4d480356b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx @@ -143,7 +143,8 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent 0) { + + if (index && index.length > 0) { const currentEsFields = await getFields(index); const timeFields = getTimeFieldOptions(currentEsFields as any); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx index 1ffebed2eb002..80024775096e1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx @@ -23,6 +23,7 @@ export interface AlertsContextValue { >; charts?: ChartsPluginSetup; dataFieldsFormats?: DataPublicPluginSetup['fieldFormats']; + metadata?: Record; } const AlertsContext = createContext(null as any); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx index 7bc44eafe7543..1177b41788bd6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx @@ -6,11 +6,13 @@ import * as React from 'react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { act } from 'react-dom/test-utils'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFormLabel } from '@elastic/eui'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import { AlertAdd } from './alert_add'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult } from '../../../types'; -import { AlertsContextProvider } from '../../context/alerts_context'; +import { AlertsContextProvider, useAlertsContext } from '../../context/alerts_context'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; import { chartPluginMock } from '../../../../../../../src/plugins/charts/public/mocks'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; @@ -18,6 +20,21 @@ import { ReactWrapper } from 'enzyme'; const actionTypeRegistry = actionTypeRegistryMock.create(); const alertTypeRegistry = alertTypeRegistryMock.create(); +export const TestExpression: React.FunctionComponent = () => { + const alertsContext = useAlertsContext(); + const { metadata } = alertsContext; + + return ( + + + + ); +}; + describe('alert_add', () => { let deps: any; let wrapper: ReactWrapper; @@ -41,7 +58,7 @@ describe('alert_add', () => { validate: (): ValidationResult => { return { errors: {} }; }, - alertParamsExpression: () => , + alertParamsExpression: TestExpression, }; const actionTypeModel = { @@ -77,13 +94,10 @@ describe('alert_add', () => { alertTypeRegistry: deps.alertTypeRegistry, toastNotifications: deps.toastNotifications, uiSettings: deps.uiSettings, + metadata: { test: 'some value', fields: ['test'] }, }} > - {}} - /> + {}} /> ); // Wait for active space to resolve before requesting the component to update @@ -97,5 +111,10 @@ describe('alert_add', () => { await setup(); expect(wrapper.find('[data-test-subj="addAlertFlyoutTitle"]').exists()).toBeTruthy(); expect(wrapper.find('[data-test-subj="saveAlertButton"]').exists()).toBeTruthy(); + wrapper + .find('[data-test-subj="my-alert-type-SelectOption"]') + .first() + .simulate('click'); + expect(wrapper.contains('Metadata: some value. Fields: test.')).toBeTruthy(); }); }); From 8c62cf9189113a8dbe0d28877df52469f75da9f9 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Mon, 9 Mar 2020 14:20:25 -0500 Subject: [PATCH 04/28] Progress --- .../alerting/metrics/alert_flyout.tsx | 9 ++- .../alerting/metrics/expression.tsx | 62 ++++++++++++------- .../public/components/alerting/validation.tsx | 15 ++--- .../components/metrics_explorer/chart.tsx | 5 ++ .../metrics_explorer/chart_context_menu.tsx | 10 ++- .../components/metrics_explorer/charts.tsx | 4 ++ .../infrastructure/metrics_explorer/index.tsx | 1 + .../application/context/alerts_context.tsx | 4 +- 8 files changed, 77 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx index 1ddf902133ded..8c0d4b7d11463 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx @@ -5,15 +5,19 @@ */ import React, { useContext } from 'react'; +import { IIndexPattern } from 'src/plugins/data/public'; import { AlertsContextProvider, AlertAdd } from '../../../../../triggers_actions_ui/public'; import { TriggerActionsContext } from '../../../utils/triggers_actions_context'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/metric_threshold/types'; +import { SourceConfiguration } from '../../../utils/source_configuration'; interface Props { visible?: boolean; setVisible: React.Dispatch>; + derivedIndexPattern: IIndexPattern; + source?: SourceConfiguration; } export const AlertFlyout = (props: Props) => { @@ -25,14 +29,15 @@ export const AlertFlyout = (props: Props) => { {triggersActionsUI && ( =' | 'between' | '<' | '<='; type TimeUnit = 's' | 'm' | 'h' | 'd'; export const MetricExpression: React.FC = props => { - const { setAlertParams, alertParams, errors } = props; + const { setAlertParams, alertParams, errors, alertsContext } = props; + + const defaultExpression = useMemo( + () => ({ + aggType: 'count', + metric: '', + comparator: '>', + threshold: [], + timeSize: 1, + timeUnit: 's', + indexPattern: alertsContext.metadata?.source.metricAlias, + }), + [alertsContext.metadata] + ); const expressions = useMemo(() => { - return alertParams.criteria || [{}]; - }, [alertParams.criteria]); + return alertParams.criteria || [defaultExpression]; + }, [alertParams.criteria, defaultExpression]); const updateParams = useCallback( (id, e: MetricExpressionParams) => { @@ -58,9 +73,9 @@ export const MetricExpression: React.FC = props => { const addExpression = useCallback(() => { const exp = alertParams.criteria ? alertParams.criteria.slice() : []; - exp.push({}); + exp.push(defaultExpression); setAlertParams('criteria', exp); - }, [setAlertParams, alertParams.criteria]); + }, [setAlertParams, alertParams.criteria, defaultExpression]); const removeExpression = useCallback( (id: number) => { @@ -73,10 +88,10 @@ export const MetricExpression: React.FC = props => { return ( <> - Add Expression {expressions.map((e, idx) => { return ( = props => { /> ); })} + Add Expression ); }; interface ExpressionRowProps { + fields: IFieldType[]; expressionId: number; expression: MetricExpressionParams; errors: any; @@ -98,7 +115,7 @@ interface ExpressionRowProps { setAlertParams(id: number, params: MetricExpressionParams): void; } export const ExpressionRow: React.FC = props => { - const { setAlertParams, expression, errors, expressionId, remove } = props; + const { setAlertParams, expression, errors, expressionId, remove, fields } = props; const { aggType = 'count', metric, @@ -110,44 +127,44 @@ export const ExpressionRow: React.FC = props => { const updateAggType = useCallback( (at: string) => { - setAlertParams(expressionId, { aggType: at }); + setAlertParams(expressionId, { ...expression, aggType: at }); }, - [expressionId, setAlertParams] + [expressionId, expression, setAlertParams] ); const updateMetric = useCallback( (m?: string) => { - setAlertParams(expressionId, { metric: m }); + setAlertParams(expressionId, { ...expression, metric: m }); }, - [expressionId, setAlertParams] + [expressionId, expression, setAlertParams] ); const updateComparator = useCallback( (c?: string) => { - setAlertParams(expressionId, { comparator: c as Comparator }); + setAlertParams(expressionId, { ...expression, comparator: c as Comparator }); }, - [expressionId, setAlertParams] + [expressionId, expression, setAlertParams] ); const updateThreshold = useCallback( t => { - setAlertParams(expressionId, { threshold: t }); + setAlertParams(expressionId, { ...expression, threshold: t }); }, - [expressionId, setAlertParams] + [expressionId, expression, setAlertParams] ); const updateTimeSize = useCallback( (ts: number | '') => { - setAlertParams(expressionId, { timeSize: ts || undefined }); + setAlertParams(expressionId, { ...expression, timeSize: ts || undefined }); }, - [expressionId, setAlertParams] + [expressionId, expression, setAlertParams] ); const updateTimeUnit = useCallback( (tu: string) => { - setAlertParams(expressionId, { timeUnit: tu as TimeUnit }); + setAlertParams(expressionId, { ...expression, timeUnit: tu as TimeUnit }); }, - [expressionId, setAlertParams] + [expressionId, expression, setAlertParams] ); return ( @@ -166,7 +183,10 @@ export const ExpressionRow: React.FC = props => { ({ + normalizedType: f.type, + name: f.name, + }))} aggType={aggType} errors={errors} onChangeSelectedAggField={updateMetric} diff --git a/x-pack/plugins/infra/public/components/alerting/validation.tsx b/x-pack/plugins/infra/public/components/alerting/validation.tsx index 50827615cf013..cc6daa9d14c31 100644 --- a/x-pack/plugins/infra/public/components/alerting/validation.tsx +++ b/x-pack/plugins/infra/public/components/alerting/validation.tsx @@ -18,12 +18,13 @@ export function validateExampleAlertType({ aggField: new Array(), }; validationResult.errors = errors; - if (!testAggField) { - errors.aggField.push( - i18n.translate('xpack.triggersActionsUI.components.example.error.requiredTestAggFieldText', { - defaultMessage: 'Test aggregation field is required.', - }) - ); - } + // if (!testAggField) { + // errors.aggField.push( + // i18n.translate('xpack.triggersActionsUI.components.example.error.requiredTestAggFieldText', { + // defaultMessage: 'Test aggregation field is required.', + // }) + // ); + // } + return validationResult; } diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/chart.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/chart.tsx index 43b08f45eed34..963cacc21a876 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/chart.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/chart.tsx @@ -10,6 +10,7 @@ import { EuiTitle, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Axis, Chart, niceTimeFormatter, Position, Settings, TooltipValue } from '@elastic/charts'; import { first, last } from 'lodash'; import moment from 'moment'; +import { IIndexPattern } from 'src/plugins/data/public'; import { MetricsExplorerSeries } from '../../../common/http_api/metrics_explorer'; import { MetricsExplorerOptions, @@ -30,6 +31,7 @@ import { calculateDomain } from './helpers/calculate_domain'; import { useKibana, useUiSetting } from '../../../../../../src/plugins/kibana_react/public'; interface Props { + derivedIndexPattern: IIndexPattern; title?: string | null; onFilter: (query: string) => void; width?: number | string; @@ -43,6 +45,7 @@ interface Props { } export const MetricsExplorerChart = ({ + derivedIndexPattern, source, options, chartOptions, @@ -92,6 +95,7 @@ export const MetricsExplorerChart = ({ void; series: MetricsExplorerSeries; @@ -73,6 +75,7 @@ export const createNodeDetailLink = ( }; export const MetricsExplorerChartContextMenu: React.FC = ({ + derivedIndexPattern, onFilter, options, series, @@ -199,7 +202,12 @@ export const MetricsExplorerChartContextMenu: React.FC = ({ panelPaddingSize="none" > - + ); diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/charts.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/charts.tsx index 64e7b27f5722f..501b074edfeea 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/charts.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/charts.tsx @@ -8,6 +8,7 @@ import { EuiButton, EuiFlexGrid, EuiFlexItem, EuiText, EuiHorizontalRule } from import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; +import { IIndexPattern } from 'src/plugins/data/public'; import { MetricsExplorerResponse } from '../../../common/http_api/metrics_explorer'; import { MetricsExplorerOptions, @@ -20,6 +21,7 @@ import { MetricsExplorerChart } from './chart'; import { SourceQuery } from '../../graphql/types'; interface Props { + derivedIndexPattern: IIndexPattern; loading: boolean; options: MetricsExplorerOptions; chartOptions: MetricsExplorerChartOptions; @@ -32,6 +34,7 @@ interface Props { timeRange: MetricsExplorerTimeOptions; } export const MetricsExplorerCharts = ({ + derivedIndexPattern, loading, data, onLoadMore, @@ -87,6 +90,7 @@ export const MetricsExplorerCharts = ({ title={options.groupBy ? series.id : null} height={data.series.length > 1 ? 200 : 400} series={series} + derivedIndexPattern={derivedIndexPattern} source={source} timeRange={timeRange} onTimeChange={onTimeChange} diff --git a/x-pack/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx b/x-pack/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx index 0999cea59731c..cf64db30e68f4 100644 --- a/x-pack/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx +++ b/x-pack/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx @@ -83,6 +83,7 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl /> ) : ( > { reloadAlerts?: () => Promise; http: HttpSetup; alertTypeRegistry: TypeRegistry; @@ -23,7 +23,7 @@ export interface AlertsContextValue { >; charts?: ChartsPluginSetup; dataFieldsFormats?: DataPublicPluginSetup['fieldFormats']; - metadata?: Record; + metadata?: MetaData; } const AlertsContext = createContext(null as any); From 68fd03bcd647d1e5746a95076bf7678a9138e88c Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Mon, 9 Mar 2020 17:22:34 -0500 Subject: [PATCH 05/28] Pre-fill criteria with current page filters --- .../alerting/metrics/alert_flyout.tsx | 9 ++- .../alerting/metrics/expression.tsx | 63 +++++++++++++++---- .../alerting/metrics/metric_alert_type.ts | 2 +- .../metrics_explorer/chart_context_menu.tsx | 1 + 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx index 8c0d4b7d11463..cca4bfa3e22ba 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx @@ -12,10 +12,12 @@ import { useKibana } from '../../../../../../../src/plugins/kibana_react/public' // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/metric_threshold/types'; import { SourceConfiguration } from '../../../utils/source_configuration'; +import { MetricsExplorerOptions } from '../../../containers/metrics_explorer/use_metrics_explorer_options'; interface Props { visible?: boolean; setVisible: React.Dispatch>; + options: MetricsExplorerOptions; derivedIndexPattern: IIndexPattern; source?: SourceConfiguration; } @@ -29,7 +31,12 @@ export const AlertFlyout = (props: Props) => { {triggersActionsUI && ( ; setAlertParams(key: string, value: any): void; setAlertProperty(key: string, value: any): void; } @@ -47,8 +55,7 @@ export const MetricExpression: React.FC = props => { const defaultExpression = useMemo( () => ({ - aggType: 'count', - metric: '', + aggType: AGGREGATION_TYPES.MAX, comparator: '>', threshold: [], timeSize: 1, @@ -59,8 +66,22 @@ export const MetricExpression: React.FC = props => { ); const expressions = useMemo(() => { - return alertParams.criteria || [defaultExpression]; - }, [alertParams.criteria, defaultExpression]); + if (alertParams.criteria) { + return alertParams.criteria; + } else if (alertsContext.metadata?.currentOptions) { + return alertsContext.metadata.currentOptions.metrics.map(metric => ({ + metric: metric.field, + comparator: '>', + threshold: [], + timeSize: 1, + timeUnit: 's', + indexPattern: alertsContext.metadata?.source.metricAlias, + aggType: metric.aggregation, + })); + } else { + return [defaultExpression]; + } + }, [alertParams.criteria, alertsContext.metadata, defaultExpression]); const updateParams = useCallback( (id, e: MetricExpressionParams) => { @@ -72,18 +93,18 @@ export const MetricExpression: React.FC = props => { ); const addExpression = useCallback(() => { - const exp = alertParams.criteria ? alertParams.criteria.slice() : []; + const exp = expressions.slice(); exp.push(defaultExpression); setAlertParams('criteria', exp); - }, [setAlertParams, alertParams.criteria, defaultExpression]); + }, [setAlertParams, expressions, defaultExpression]); const removeExpression = useCallback( (id: number) => { - const exp = alertParams.criteria ? alertParams.criteria.slice() : []; + const exp = expressions.slice(); exp.splice(id, 1); setAlertParams('criteria', exp); }, - [setAlertParams, alertParams.criteria] + [setAlertParams, expressions] ); return ( @@ -93,6 +114,7 @@ export const MetricExpression: React.FC = props => { = props => { /> ); })} - Add Expression ); }; @@ -111,13 +132,14 @@ interface ExpressionRowProps { expressionId: number; expression: MetricExpressionParams; errors: any; + addExpression(): void; remove(id: number): void; setAlertParams(id: number, params: MetricExpressionParams): void; } export const ExpressionRow: React.FC = props => { const { setAlertParams, expression, errors, expressionId, remove, fields } = props; const { - aggType = 'count', + aggType = AGGREGATION_TYPES.MAX, metric, comparator = '>', threshold = [], @@ -215,7 +237,22 @@ export const ExpressionRow: React.FC = props => { - remove(expressionId)} /> + + + + + + remove(expressionId)} + /> + + diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/metric_alert_type.ts b/x-pack/plugins/infra/public/components/alerting/metrics/metric_alert_type.ts index 6be9428b07edc..f942d92864201 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/metric_alert_type.ts +++ b/x-pack/plugins/infra/public/components/alerting/metrics/metric_alert_type.ts @@ -13,7 +13,7 @@ import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/ export function getAlertType(): AlertTypeModel { return { id: METRIC_THRESHOLD_ALERT_TYPE_ID, - name: 'Threshold Alert', + name: 'Alert Trigger', iconClass: 'bell', alertParamsExpression: MetricExpression, validate: validateExampleAlertType, diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx index e1df441997697..6bac7fafe06b2 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx @@ -203,6 +203,7 @@ export const MetricsExplorerChartContextMenu: React.FC = ({ > Date: Tue, 10 Mar 2020 11:03:21 -0500 Subject: [PATCH 06/28] Better validation. Naming for clarity --- .../alerting/metrics/expression.tsx | 80 +++++++++++++++---- ...type.ts => metric_threshold_alert_type.ts} | 13 +-- .../alerting/metrics/validation.tsx | 80 +++++++++++++++++++ .../public/components/alerting/validation.tsx | 30 ------- .../metrics_explorer/chart_context_menu.tsx | 3 +- .../components/waffle/node_context_menu.tsx | 13 --- x-pack/plugins/infra/public/plugin.ts | 2 +- 7 files changed, 155 insertions(+), 66 deletions(-) rename x-pack/plugins/infra/public/components/alerting/metrics/{metric_alert_type.ts => metric_threshold_alert_type.ts} (67%) create mode 100644 x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx delete mode 100644 x-pack/plugins/infra/public/components/alerting/validation.tsx diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 0b8a77da966bd..31dffa691437f 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -5,8 +5,10 @@ */ import React, { useCallback, useMemo } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonIcon } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiSpacer, EuiText } from '@elastic/eui'; import { IFieldType, IIndexPattern } from 'src/plugins/data/public'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { WhenExpression, OfExpression, @@ -22,8 +24,9 @@ import { AlertsContextValue } from '../../../../../triggers_actions_ui/public/ap import { AGGREGATION_TYPES } from '../../../../../triggers_actions_ui/public/common/constants'; import { MetricsExplorerOptions } from '../../../containers/metrics_explorer/use_metrics_explorer_options'; import { SourceConfiguration } from '../../../utils/source_configuration'; +import { MetricsExplorerKueryBar } from '../../metrics_explorer/kuery_bar'; -interface MetricExpressionParams { +export interface MetricExpression { aggType?: string; metric?: string; comparator?: Comparator; @@ -40,8 +43,8 @@ interface AlertContextMeta { } interface Props { - errors: IErrorObject; - alertParams: { criteria: MetricExpressionParams[] }; + errors: IErrorObject[]; + alertParams: { criteria: MetricExpression[] }; alertsContext: AlertsContextValue; setAlertParams(key: string, value: any): void; setAlertProperty(key: string, value: any): void; @@ -50,10 +53,10 @@ interface Props { type Comparator = '>' | '>=' | 'between' | '<' | '<='; type TimeUnit = 's' | 'm' | 'h' | 'd'; -export const MetricExpression: React.FC = props => { +export const Expressions: React.FC = props => { const { setAlertParams, alertParams, errors, alertsContext } = props; - const defaultExpression = useMemo( + const defaultExpression = useMemo( () => ({ aggType: AGGREGATION_TYPES.MAX, comparator: '>', @@ -65,7 +68,7 @@ export const MetricExpression: React.FC = props => { [alertsContext.metadata] ); - const expressions = useMemo(() => { + const expressions = useMemo(() => { if (alertParams.criteria) { return alertParams.criteria; } else if (alertsContext.metadata?.currentOptions) { @@ -84,7 +87,7 @@ export const MetricExpression: React.FC = props => { }, [alertParams.criteria, alertsContext.metadata, defaultExpression]); const updateParams = useCallback( - (id, e: MetricExpressionParams) => { + (id, e: MetricExpression) => { const exp = alertParams.criteria ? alertParams.criteria.slice() : []; exp[id] = { ...exp[id], ...e }; setAlertParams('criteria', exp); @@ -107,8 +110,33 @@ export const MetricExpression: React.FC = props => { [setAlertParams, expressions] ); + const onFilterQuerySubmit = useCallback( + (filter: any) => { + setAlertParams('filterQuery', filter); + }, + [setAlertParams] + ); + + const emptyError = useMemo(() => { + return { + aggField: [], + timeSizeUnit: [], + timeWindowSize: [], + }; + }, []); + return ( <> + + +
+ +
+
+ {expressions.map((e, idx) => { return ( = props => { key={idx} // idx's don't usually make good key's but here the index has semantic meaning expressionId={idx} setAlertParams={updateParams} - errors={errors} + errors={errors[idx] || emptyError} expression={e || {}} /> ); })} + + +
+ +
+
+ + + ); }; @@ -130,11 +171,11 @@ export const MetricExpression: React.FC = props => { interface ExpressionRowProps { fields: IFieldType[]; expressionId: number; - expression: MetricExpressionParams; - errors: any; + expression: MetricExpression; + errors: IErrorObject; addExpression(): void; remove(id: number): void; - setAlertParams(id: number, params: MetricExpressionParams): void; + setAlertParams(id: number, params: MetricExpression): void; } export const ExpressionRow: React.FC = props => { const { setAlertParams, expression, errors, expressionId, remove, fields } = props; @@ -229,7 +270,7 @@ export const ExpressionRow: React.FC = props => { @@ -237,16 +278,22 @@ export const ExpressionRow: React.FC = props => {
- - + + - + remove(expressionId)} @@ -255,6 +302,7 @@ export const ExpressionRow: React.FC = props => { + ); }; diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/metric_alert_type.ts b/x-pack/plugins/infra/public/components/alerting/metrics/metric_threshold_alert_type.ts similarity index 67% rename from x-pack/plugins/infra/public/components/alerting/metrics/metric_alert_type.ts rename to x-pack/plugins/infra/public/components/alerting/metrics/metric_threshold_alert_type.ts index f942d92864201..d3b5aaa7c8796 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/metric_alert_type.ts +++ b/x-pack/plugins/infra/public/components/alerting/metrics/metric_threshold_alert_type.ts @@ -3,19 +3,22 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../../triggers_actions_ui/public/types'; -import { MetricExpression } from './expression'; -import { validateExampleAlertType } from '../validation'; +import { Expressions } from './expression'; +import { validateMetricThreshold } from './validation'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/metric_threshold/types'; export function getAlertType(): AlertTypeModel { return { id: METRIC_THRESHOLD_ALERT_TYPE_ID, - name: 'Alert Trigger', + name: i18n.translate('xpack.infra.metrics.alertFlyout.alertName', { + defaultMessage: 'Alert Trigger', + }), iconClass: 'bell', - alertParamsExpression: MetricExpression, - validate: validateExampleAlertType, + alertParamsExpression: Expressions, + validate: validateMetricThreshold, }; } diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx new file mode 100644 index 0000000000000..0f5b07f8c0e13 --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths + +import { MetricExpression } from './expression'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ValidationResult } from '../../../../../triggers_actions_ui/public/types'; + +export function validateMetricThreshold({ + criteria, +}: { + criteria: MetricExpression[]; +}): ValidationResult { + const validationResult = { errors: {} }; + const errors: { + [id: string]: { + aggField: string[]; + timeSizeUnit: string[]; + timeWindowSize: string[]; + threshold0: string[]; + threshold1: string[]; + }; + } = {}; + validationResult.errors = errors; + + if (!criteria || !criteria.length) { + return validationResult; + } + + criteria.forEach((c, idx) => { + // Create an id for each criteria, so we can map errors to specific criteria. + const id = idx.toString(); + + errors[id] = errors[id] || { + aggField: [], + timeSizeUnit: [], + timeWindowSize: [], + threshold0: [], + threshold1: [], + }; + if (!c.aggType) { + errors[id].aggField.push( + i18n.translate('xpack.infra.metrics.alertFlyout.error.aggregationRequired', { + defaultMessage: 'Aggreation is required.', + }) + ); + } + + if (!c.threshold || !c.threshold.length) { + errors[id].threshold0.push( + i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', { + defaultMessage: 'Threshold is required.', + }) + ); + } + + if (c.comparator === 'between' && (!c.threshold || c.threshold.length < 2)) { + errors[id].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', { + defaultMessage: 'Time size is Required.', + }) + ); + } + }); + + return validationResult; +} diff --git a/x-pack/plugins/infra/public/components/alerting/validation.tsx b/x-pack/plugins/infra/public/components/alerting/validation.tsx deleted file mode 100644 index cc6daa9d14c31..0000000000000 --- a/x-pack/plugins/infra/public/components/alerting/validation.tsx +++ /dev/null @@ -1,30 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ValidationResult } from '../../../../triggers_actions_ui/public/types'; - -export function validateExampleAlertType({ - testAggField, -}: { - testAggField: string; -}): ValidationResult { - const validationResult = { errors: {} }; - const errors = { - aggField: new Array(), - }; - validationResult.errors = errors; - // if (!testAggField) { - // errors.aggField.push( - // i18n.translate('xpack.triggersActionsUI.components.example.error.requiredTestAggFieldText', { - // defaultMessage: 'Test aggregation field is required.', - // }) - // ); - // } - - return validationResult; -} diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx index 6bac7fafe06b2..5b7f79da6603f 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx @@ -150,9 +150,10 @@ export const MetricsExplorerChartContextMenu: React.FC = ({ ...openInVisualize, ...viewNodeDetail, { - name: i18n.translate('xpack.infra.alerts.createAlertButton', { + name: i18n.translate('xpack.infra.metricsExplorer.alerts.createAlertButton', { defaultMessage: 'Create alert', }), + icon: 'bell', onClick() { setFlyoutVisible(true); }, diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index 93162f40d9f4c..200f48224a157 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -25,7 +25,6 @@ import { SectionLink, } from '../../../../observability/public'; import { usePrefixPathWithBasepath } from '../../hooks/use_prefix_path_with_basepath'; -import { AlertFlyout } from '../alerting/metrics/alert_flyout'; interface Props { options: InfraWaffleMapOptions; @@ -65,8 +64,6 @@ export const NodeContextMenu: React.FC = ({ const showUptimeLink = inventoryModel.crosslinkSupport.uptime && (['pod', 'container'].includes(nodeType) || node.ip); - const [flyoutVisible, setFlyoutVisible] = useState(false); - const inventoryId = useMemo(() => { if (nodeType === 'host') { if (node.ip) { @@ -137,15 +134,6 @@ export const NodeContextMenu: React.FC = ({ isDisabled: !showUptimeLink, }; - const alertMenuItem: SectionLinkProps = { - label: i18n.translate('xpack.infra.alerts.createAlertButton', { - defaultMessage: 'Create alert', - }), - onClick() { - setFlyoutVisible(true); - }, - }; - return ( = ({ href={uptimeMenuItem.href} isDisabled={uptimeMenuItem.isDisabled} /> - diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index afa44fcb0f247..15796f35856bd 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -30,7 +30,7 @@ import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/p import { DataEnhancedSetup, DataEnhancedStart } from '../../data_enhanced/public'; import { LogsRouter, MetricsRouter } from './routers'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../plugins/triggers_actions_ui/public'; -import { getAlertType } from './components/alerting/metrics/metric_alert_type'; +import { getAlertType } from './components/alerting/metrics/metric_threshold_alert_type'; export type ClientSetup = void; export type ClientStart = void; From e62143298d7696039a58827823e8532a056beca6 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Tue, 10 Mar 2020 11:46:32 -0500 Subject: [PATCH 07/28] Fix types for flyout --- .../infra/public/components/alerting/metrics/alert_flyout.tsx | 1 - .../public/application/context/alerts_context.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx index cca4bfa3e22ba..30dae5b265717 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx @@ -31,7 +31,6 @@ export const AlertFlyout = (props: Props) => { {triggersActionsUI && ( > { http: HttpSetup; alertTypeRegistry: TypeRegistry; actionTypeRegistry: TypeRegistry; - toastNotifications: Pick< + toastNotifications?: Pick< ToastsApi, 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' >; From 7956d6de045b24ec1d8c5a36a7da0c4e7591ee32 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Wed, 11 Mar 2020 09:36:14 -0500 Subject: [PATCH 08/28] Respect the groupby property in metric explorer --- .../alerting/metrics/alert_flyout.tsx | 5 ++++- .../components/alerting/metrics/expression.tsx | 18 +++++++++++++++++- .../metrics_explorer/chart_context_menu.tsx | 1 + 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx index 30dae5b265717..4c17e7a3ef051 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx @@ -13,13 +13,15 @@ import { useKibana } from '../../../../../../../src/plugins/kibana_react/public' import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/metric_threshold/types'; import { SourceConfiguration } from '../../../utils/source_configuration'; import { MetricsExplorerOptions } from '../../../containers/metrics_explorer/use_metrics_explorer_options'; +import { MetricsExplorerSeries } from '../../../../common/http_api/metrics_explorer'; interface Props { visible?: boolean; - setVisible: React.Dispatch>; options: MetricsExplorerOptions; derivedIndexPattern: IIndexPattern; + series: MetricsExplorerSeries; source?: SourceConfiguration; + setVisible: React.Dispatch>; } export const AlertFlyout = (props: Props) => { @@ -35,6 +37,7 @@ export const AlertFlyout = (props: Props) => { source: props.source, derivedIndexPattern: props.derivedIndexPattern, currentOptions: props.options, + series: props.series, }, http: services.http, actionTypeRegistry: triggersActionsUI.actionTypeRegistry, diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 31dffa691437f..04e210acd04fe 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -25,6 +25,7 @@ import { AGGREGATION_TYPES } from '../../../../../triggers_actions_ui/public/com import { MetricsExplorerOptions } from '../../../containers/metrics_explorer/use_metrics_explorer_options'; import { SourceConfiguration } from '../../../utils/source_configuration'; import { MetricsExplorerKueryBar } from '../../metrics_explorer/kuery_bar'; +import { MetricsExplorerSeries } from '../../../../common/http_api/metrics_explorer'; export interface MetricExpression { aggType?: string; @@ -40,6 +41,7 @@ interface AlertContextMeta { currentOptions: MetricsExplorerOptions; derivedIndexPattern: IIndexPattern; source: SourceConfiguration; + series: MetricsExplorerSeries; } interface Props { @@ -125,6 +127,20 @@ export const Expressions: React.FC = props => { }; }, []); + const filterValue = useMemo(() => { + const options = alertsContext.metadata?.currentOptions; + const series = alertsContext.metadata?.series; + if (!options) { + return; + } + + if (options.filterQuery) { + return options.filterQuery; + } else if (options.groupBy && series) { + return `${options.groupBy}: "${series.id}"`; + } + }, [alertsContext.metadata]); + return ( <> @@ -161,7 +177,7 @@ export const Expressions: React.FC = props => { diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx index 7f7b7b11dc1b8..f25123578c688 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx @@ -205,6 +205,7 @@ export const MetricsExplorerChartContextMenu: React.FC = ({ > Date: Wed, 11 Mar 2020 10:03:49 -0500 Subject: [PATCH 09/28] Fix lint errors --- .../infra/public/components/alerting/metrics/expression.tsx | 6 ++++-- .../infra/public/components/waffle/node_context_menu.tsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 04e210acd04fe..5622d2de3c96d 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -137,9 +137,11 @@ export const Expressions: React.FC = props => { if (options.filterQuery) { return options.filterQuery; } else if (options.groupBy && series) { - return `${options.groupBy}: "${series.id}"`; + const filter = `${options.groupBy}: "${series.id}"`; + onFilterQuerySubmit(filter); + return filter; } - }, [alertsContext.metadata]); + }, [alertsContext.metadata, onFilterQuerySubmit]); return ( <> diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index ab13b6130e88b..cc6a94c8a41a2 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -8,7 +8,7 @@ import { EuiPopoverProps, EuiCode } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib'; import { getNodeDetailUrl, getNodeLogsUrl } from '../../pages/link_to'; import { createUptimeLink } from './lib/create_uptime_link'; From 7dc0df9f39e1a3eeeb626c1aba000879f117fd8f Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Thu, 12 Mar 2020 14:43:19 -0500 Subject: [PATCH 10/28] Fix text, add toast notifications --- .../components/alerting/metrics/alert_flyout.tsx | 1 + .../metrics_explorer/chart_context_menu.test.tsx | 15 ++++++++++++++- .../public/application/context/alerts_context.tsx | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx index 4c17e7a3ef051..1090cab537e0a 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx @@ -39,6 +39,7 @@ export const AlertFlyout = (props: Props) => { currentOptions: props.options, series: props.series, }, + toastNotifications: services.notifications?.toasts, http: services.http, actionTypeRegistry: triggersActionsUI.actionTypeRegistry, alertTypeRegistry: triggersActionsUI.alertTypeRegistry, diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx index a23a2739a8e23..3962cdc3807f2 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx @@ -7,7 +7,13 @@ import React from 'react'; import { MetricsExplorerChartContextMenu, createNodeDetailLink, Props } from './chart_context_menu'; import { ReactWrapper, mount } from 'enzyme'; -import { options, source, timeRange, chartOptions } from '../../utils/fixtures/metrics_explorer'; +import { + options, + source, + timeRange, + chartOptions, + derivedIndexPattern, +} from '../../utils/fixtures/metrics_explorer'; import { Capabilities } from 'src/core/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { coreMock } from 'src/core/public/mocks'; @@ -45,6 +51,7 @@ describe('MetricsExplorerChartContextMenu', () => { onFilter, uiCapabilities, chartOptions, + derivedIndexPattern, }); component.find('button').simulate('click'); expect(getTestSubject(component, 'metricsExplorerAction-AddFilter').length).toBe(1); @@ -63,6 +70,7 @@ describe('MetricsExplorerChartContextMenu', () => { onFilter, uiCapabilities, chartOptions, + derivedIndexPattern, }); component.find('button').simulate('click'); expect(getTestSubject(component, 'metricsExplorerAction-ViewNodeMetrics').length).toBe(0); @@ -76,6 +84,7 @@ describe('MetricsExplorerChartContextMenu', () => { options, uiCapabilities, chartOptions, + derivedIndexPattern, }); component.find('button').simulate('click'); expect(getTestSubject(component, 'metricsExplorerAction-AddFilter').length).toBe(0); @@ -92,6 +101,7 @@ describe('MetricsExplorerChartContextMenu', () => { onFilter, uiCapabilities, chartOptions, + derivedIndexPattern, }); component.find('button').simulate('click'); expect(getTestSubject(component, 'metricsExplorerAction-AddFilter').length).toBe(0); @@ -106,6 +116,7 @@ describe('MetricsExplorerChartContextMenu', () => { options: customOptions, uiCapabilities, chartOptions, + derivedIndexPattern, }); component.find('button').simulate('click'); expect( @@ -124,6 +135,7 @@ describe('MetricsExplorerChartContextMenu', () => { onFilter, uiCapabilities: customUICapabilities, chartOptions, + derivedIndexPattern, }); component.find('button').simulate('click'); @@ -142,6 +154,7 @@ describe('MetricsExplorerChartContextMenu', () => { onFilter, uiCapabilities: customUICapabilities, chartOptions, + derivedIndexPattern, }); expect(component.find('button').length).toBe(0); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx index c1bb3433067d2..1944cdeab7552 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx @@ -16,7 +16,7 @@ export interface AlertsContextValue> { http: HttpSetup; alertTypeRegistry: TypeRegistry; actionTypeRegistry: TypeRegistry; - toastNotifications?: Pick< + toastNotifications: Pick< ToastsApi, 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' >; From 2a8b1fbfd70f4e61a575f9835b4be9f7bf9b41ab Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Mon, 16 Mar 2020 09:51:33 -0500 Subject: [PATCH 11/28] Fix tests. Make sure update handles predefined expressions --- .../infra/public/components/alerting/metrics/expression.tsx | 4 ++-- .../components/metrics_explorer/chart_context_menu.test.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 5622d2de3c96d..dff32e40d5901 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -90,11 +90,11 @@ export const Expressions: React.FC = props => { const updateParams = useCallback( (id, e: MetricExpression) => { - const exp = alertParams.criteria ? alertParams.criteria.slice() : []; + const exp = expressions ? expressions.slice() : []; exp[id] = { ...exp[id], ...e }; setAlertParams('criteria', exp); }, - [setAlertParams, alertParams.criteria] + [setAlertParams, expressions] ); const addExpression = useCallback(() => { diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx index 3962cdc3807f2..02ba6d0c7a771 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx @@ -156,7 +156,7 @@ describe('MetricsExplorerChartContextMenu', () => { chartOptions, derivedIndexPattern, }); - expect(component.find('button').length).toBe(0); + expect(component.find('button').length).toBe(1); }); }); From 542d76655c07c2e018680437ca0d72fb683de219 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Mon, 16 Mar 2020 13:13:25 -0500 Subject: [PATCH 12/28] Dynamically load source from alert flyout --- .../alerting/metrics/alert_flyout.tsx | 5 ---- .../alerting/metrics/expression.tsx | 23 +++++++++++-------- .../metrics_explorer/chart_context_menu.tsx | 2 -- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx index 1090cab537e0a..73a298d69d8d4 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx @@ -11,16 +11,13 @@ import { TriggerActionsContext } from '../../../utils/triggers_actions_context'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/metric_threshold/types'; -import { SourceConfiguration } from '../../../utils/source_configuration'; import { MetricsExplorerOptions } from '../../../containers/metrics_explorer/use_metrics_explorer_options'; import { MetricsExplorerSeries } from '../../../../common/http_api/metrics_explorer'; interface Props { visible?: boolean; options: MetricsExplorerOptions; - derivedIndexPattern: IIndexPattern; series: MetricsExplorerSeries; - source?: SourceConfiguration; setVisible: React.Dispatch>; } @@ -34,8 +31,6 @@ export const AlertFlyout = (props: Props) => { = props => { const { setAlertParams, alertParams, errors, alertsContext } = props; + const { source, createDerivedIndexPattern } = useSource({ sourceId: 'default' }); + + const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [ + createDerivedIndexPattern, + ]); const defaultExpression = useMemo( () => ({ @@ -65,9 +68,9 @@ export const Expressions: React.FC = props => { threshold: [], timeSize: 1, timeUnit: 's', - indexPattern: alertsContext.metadata?.source.metricAlias, + indexPattern: source?.configuration.metricAlias, }), - [alertsContext.metadata] + [source] ); const expressions = useMemo(() => { @@ -80,13 +83,13 @@ export const Expressions: React.FC = props => { threshold: [], timeSize: 1, timeUnit: 's', - indexPattern: alertsContext.metadata?.source.metricAlias, + indexPattern: source?.configuration.metricAlias, aggType: metric.aggregation, })); } else { return [defaultExpression]; } - }, [alertParams.criteria, alertsContext.metadata, defaultExpression]); + }, [alertParams.criteria, source, alertsContext.metadata, defaultExpression]); const updateParams = useCallback( (id, e: MetricExpression) => { @@ -158,7 +161,7 @@ export const Expressions: React.FC = props => { {expressions.map((e, idx) => { return ( = props => { diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx index f25123578c688..a016d15ff0cf0 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx @@ -207,8 +207,6 @@ export const MetricsExplorerChartContextMenu: React.FC = ({ From e3f3669d480abff510cd6e046a320e5d5ec647e4 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Mon, 16 Mar 2020 14:14:22 -0500 Subject: [PATCH 13/28] Remove unused import --- .../infra/public/components/alerting/metrics/alert_flyout.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx index 73a298d69d8d4..e0b414a3735b6 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx @@ -5,7 +5,6 @@ */ import React, { useContext } from 'react'; -import { IIndexPattern } from 'src/plugins/data/public'; import { AlertsContextProvider, AlertAdd } from '../../../../../triggers_actions_ui/public'; import { TriggerActionsContext } from '../../../utils/triggers_actions_context'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; From 552a9bde2adfcb307af7379a8684ffd4e6379909 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Tue, 17 Mar 2020 08:45:21 -0500 Subject: [PATCH 14/28] Simplify and add group by functionality --- .../alerting/metrics/alert_flyout.tsx | 2 +- .../alerting/metrics/expression.tsx | 145 +++++++++++------- .../components/metrics_explorer/toolbar.tsx | 20 ++- 3 files changed, 109 insertions(+), 58 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx index e0b414a3735b6..edcd6a54dcd0b 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx @@ -16,7 +16,7 @@ import { MetricsExplorerSeries } from '../../../../common/http_api/metrics_explo interface Props { visible?: boolean; options: MetricsExplorerOptions; - series: MetricsExplorerSeries; + series?: MetricsExplorerSeries; setVisible: React.Dispatch>; } diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 3cdcaa78b0deb..eefcb880c0b76 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useEffect } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiSpacer, EuiText } from '@elastic/eui'; import { IFieldType } from 'src/plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -26,6 +26,7 @@ import { MetricsExplorerOptions } from '../../../containers/metrics_explorer/use import { MetricsExplorerKueryBar } from '../../metrics_explorer/kuery_bar'; import { MetricsExplorerSeries } from '../../../../common/http_api/metrics_explorer'; import { useSource } from '../../../containers/source'; +import { MetricsExplorerGroupBy } from '../../metrics_explorer/group_by'; export interface MetricExpression { aggType?: string; @@ -39,12 +40,16 @@ export interface MetricExpression { interface AlertContextMeta { currentOptions: MetricsExplorerOptions; - series: MetricsExplorerSeries; + series?: MetricsExplorerSeries; } interface Props { errors: IErrorObject[]; - alertParams: { criteria: MetricExpression[] }; + alertParams: { + criteria: MetricExpression[]; + groupBy?: string | null; + filterQuery?: string; + }; alertsContext: AlertsContextValue; setAlertParams(key: string, value: any): void; setAlertProperty(key: string, value: any): void; @@ -73,46 +78,28 @@ export const Expressions: React.FC = props => { [source] ); - const expressions = useMemo(() => { - if (alertParams.criteria) { - return alertParams.criteria; - } else if (alertsContext.metadata?.currentOptions) { - return alertsContext.metadata.currentOptions.metrics.map(metric => ({ - metric: metric.field, - comparator: '>', - threshold: [], - timeSize: 1, - timeUnit: 's', - indexPattern: source?.configuration.metricAlias, - aggType: metric.aggregation, - })); - } else { - return [defaultExpression]; - } - }, [alertParams.criteria, source, alertsContext.metadata, defaultExpression]); - const updateParams = useCallback( (id, e: MetricExpression) => { - const exp = expressions ? expressions.slice() : []; + const exp = alertParams.criteria ? alertParams.criteria.slice() : []; exp[id] = { ...exp[id], ...e }; setAlertParams('criteria', exp); }, - [setAlertParams, expressions] + [setAlertParams, alertParams.criteria] ); const addExpression = useCallback(() => { - const exp = expressions.slice(); + const exp = alertParams.criteria.slice(); exp.push(defaultExpression); setAlertParams('criteria', exp); - }, [setAlertParams, expressions, defaultExpression]); + }, [setAlertParams, alertParams.criteria, defaultExpression]); const removeExpression = useCallback( (id: number) => { - const exp = expressions.slice(); + const exp = alertParams.criteria.slice(); exp.splice(id, 1); setAlertParams('criteria', exp); }, - [setAlertParams, expressions] + [setAlertParams, alertParams.criteria] ); const onFilterQuerySubmit = useCallback( @@ -122,6 +109,13 @@ export const Expressions: React.FC = props => { [setAlertParams] ); + const onGroupByChange = useCallback( + (group: string | null) => { + setAlertParams('groupBy', group); + }, + [setAlertParams] + ); + const emptyError = useMemo(() => { return { aggField: [], @@ -130,21 +124,45 @@ export const Expressions: React.FC = props => { }; }, []); - const filterValue = useMemo(() => { - const options = alertsContext.metadata?.currentOptions; - const series = alertsContext.metadata?.series; - if (!options) { - return; - } + useEffect(() => { + const md = alertsContext.metadata; + if (md) { + if (md.currentOptions) { + setAlertParams( + 'criteria', + md.currentOptions.metrics.map(metric => ({ + metric: metric.field, + comparator: '>', + threshold: [], + timeSize: 1, + timeUnit: 'm', + indexPattern: source?.configuration.metricAlias, + aggType: metric.aggregation, + })) + ); + } else { + setAlertParams('criteria', [defaultExpression]); + } - if (options.filterQuery) { - return options.filterQuery; - } else if (options.groupBy && series) { - const filter = `${options.groupBy}: "${series.id}"`; - onFilterQuerySubmit(filter); - return filter; + if (md.currentOptions.filterQuery) { + setAlertParams('filterQuery', md.currentOptions.filterQuery); + } else if (md.currentOptions.groupBy && md.series) { + const filter = `${md.currentOptions.groupBy}: "${md.series.id}"`; + setAlertParams('filterQuery', filter); + } + + setAlertParams('groupBy', md.currentOptions.groupBy); } - }, [alertsContext.metadata, onFilterQuerySubmit]); + }, [alertsContext.metadata, defaultExpression, setAlertParams, source]); + + useEffect(() => { + return () => { + // When flyout closes, reset alert params + setAlertParams('criteria', []); + setAlertParams('filterQuery', ''); + setAlertParams('groupBy', undefined); + }; + }, [setAlertParams]); return ( <> @@ -158,20 +176,21 @@ export const Expressions: React.FC = props => { - {expressions.map((e, idx) => { - return ( - - ); - })} + {alertParams.criteria && + alertParams.criteria.map((e, idx) => { + return ( + + ); + })}
@@ -182,9 +201,25 @@ export const Expressions: React.FC = props => { + +
+ +
+
+ + {alertsContext.metadata && ( + + )} ); }; diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx index 9e96819a36cac..7df46adc10864 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker, EuiText, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { useState, useCallback } from 'react'; import { IIndexPattern } from 'src/plugins/data/public'; import { MetricsExplorerMetric, @@ -28,6 +28,7 @@ import { MetricExplorerViewState } from '../../pages/infrastructure/metrics_expl import { metricsExplorerViewSavedObjectType } from '../../../common/saved_objects/metrics_explorer_view'; import { useKibanaUiSetting } from '../../utils/use_kibana_ui_setting'; import { mapKibanaQuickRangesToDatePickerRanges } from '../../utils/map_timepicker_quickranges_to_datepicker_ranges'; +import { AlertFlyout } from '../alerting/metrics/alert_flyout'; interface Props { derivedIndexPattern: IIndexPattern; @@ -63,6 +64,12 @@ export const MetricsExplorerToolbar = ({ const isDefaultOptions = options.aggregation === 'avg' && options.metrics.length === 0; const [timepickerQuickRanges] = useKibanaUiSetting('timepicker:quickRanges'); const commonlyUsedRanges = mapKibanaQuickRangesToDatePickerRanges(timepickerQuickRanges); + const [flyoutVisible, setFlyoutVisible] = useState(false); + + const showAlertFlyout = useCallback(() => { + setFlyoutVisible(true); + }, [setFlyoutVisible]); + return ( @@ -113,6 +120,15 @@ export const MetricsExplorerToolbar = ({ value={options.filterQuery} /> + + + + + + Date: Tue, 17 Mar 2020 09:08:33 -0500 Subject: [PATCH 15/28] Remove unecessary useEffect --- .../public/components/alerting/metrics/expression.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index eefcb880c0b76..cb0928dd78b96 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -155,15 +155,6 @@ export const Expressions: React.FC = props => { } }, [alertsContext.metadata, defaultExpression, setAlertParams, source]); - useEffect(() => { - return () => { - // When flyout closes, reset alert params - setAlertParams('criteria', []); - setAlertParams('filterQuery', ''); - setAlertParams('groupBy', undefined); - }; - }, [setAlertParams]); - return ( <> From 6c5eb6c37c9f4c3817421b5d8c6b6727544b7601 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Tue, 17 Mar 2020 10:08:25 -0500 Subject: [PATCH 16/28] disable exhastive deps --- .../public/components/alerting/metrics/expression.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index cb0928dd78b96..f68d16e9bab1a 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -153,7 +153,7 @@ export const Expressions: React.FC = props => { setAlertParams('groupBy', md.currentOptions.groupBy); } - }, [alertsContext.metadata, defaultExpression, setAlertParams, source]); + }, [alertsContext.metadata, defaultExpression, source]); // eslint-disable-line react-hooks/exhaustive-deps return ( <> @@ -208,7 +208,10 @@ export const Expressions: React.FC = props => { )} @@ -279,7 +282,7 @@ export const ExpressionRow: React.FC = props => { return ( <> - + @@ -325,7 +328,7 @@ export const ExpressionRow: React.FC = props => { - + Date: Tue, 17 Mar 2020 09:08:33 -0500 Subject: [PATCH 17/28] Remove unecessary useEffect --- .../alerting/metrics/expression.tsx | 127 +++++++++++++++--- 1 file changed, 110 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index eefcb880c0b76..49815ec235a0b 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -5,10 +5,19 @@ */ import React, { useCallback, useMemo, useEffect } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiSpacer, EuiText } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButtonIcon, + EuiSpacer, + EuiText, + EuiFormRow, +} from '@elastic/eui'; import { IFieldType } from 'src/plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { euiStyled } from '../../../../../observability/public'; import { WhenExpression, OfExpression, @@ -155,25 +164,16 @@ export const Expressions: React.FC = props => { } }, [alertsContext.metadata, defaultExpression, setAlertParams, source]); - useEffect(() => { - return () => { - // When flyout closes, reset alert params - setAlertParams('criteria', []); - setAlertParams('filterQuery', ''); - setAlertParams('groupBy', undefined); - }; - }, [setAlertParams]); - return ( <> - -
+ +

-

+
{alertParams.criteria && @@ -191,18 +191,55 @@ export const Expressions: React.FC = props => { /> ); })} + + {/* The "for last" expression should appear here, only once. It applies to all the metrics above */} + For last expression goes last here + {/* */} + + + Add condition + + + + + + + + + + {alertsContext.metadata && ( + + + + )} + + {/*
- +
@@ -219,7 +256,7 @@ export const Expressions: React.FC = props => { fields={derivedIndexPattern.fields} options={alertsContext.metadata.currentOptions} /> - )} + )} */} ); }; @@ -233,6 +270,17 @@ interface ExpressionRowProps { remove(id: number): void; setAlertParams(id: number, params: MetricExpression): void; } + +const StyledExpressionRow = euiStyled.div` + display: flex; + flex-wrap: wrap; + margin: 0 -${props => props.theme.eui.euiSizeXS}; +`; + +const StyledExpression = euiStyled.div` + padding: 0 ${props => props.theme.eui.euiSizeXS}; +`; + export const ExpressionRow: React.FC = props => { const { setAlertParams, expression, errors, expressionId, remove, fields } = props; const { @@ -288,7 +336,52 @@ export const ExpressionRow: React.FC = props => { return ( <> - + + + + + + + + ({ + normalizedType: f.type, + name: f.name, + }))} + aggType={aggType} + errors={errors} + onChangeSelectedAggField={updateMetric} + /> + + + '} + threshold={threshold} + onChangeSelectedThresholdComparator={updateComparator} + onChangeSelectedThreshold={updateThreshold} + errors={errors} + /> + + + + + remove(expressionId)} + /> + + + + {/* @@ -357,7 +450,7 @@ export const ExpressionRow: React.FC = props => { - + */} ); From 0d694c4d3d009e8d3710c427734671856297ea0c Mon Sep 17 00:00:00 2001 From: Henry Harding Date: Tue, 17 Mar 2020 13:40:10 -0400 Subject: [PATCH 18/28] change language --- .../infra/public/components/alerting/metrics/expression.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 5026efcefcaa0..9e3e20e0ee961 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -219,7 +219,7 @@ export const Expressions: React.FC = props => { {alertsContext.metadata && ( - + props.theme.eui.euiSizeXS}; From f75d15737f748d57f3f00b87d777315ea1f79d28 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Tue, 17 Mar 2020 14:45:51 -0500 Subject: [PATCH 19/28] Implement design feedback --- .../alerting/metrics/expression.tsx | 240 +++++++----------- .../public/pages/infrastructure/index.tsx | 58 +++-- .../register_metric_threshold_alert_type.ts | 2 +- 3 files changed, 121 insertions(+), 179 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 9e3e20e0ee961..373c1e7a3b112 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -4,15 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo, useEffect } from 'react'; +import React, { useCallback, useMemo, useEffect, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, - EuiButtonEmpty, EuiButtonIcon, EuiSpacer, EuiText, EuiFormRow, + EuiButtonEmpty, } from '@elastic/eui'; import { IFieldType } from 'src/plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -70,6 +70,8 @@ type TimeUnit = 's' | 'm' | 'h' | 'd'; export const Expressions: React.FC = props => { const { setAlertParams, alertParams, errors, alertsContext } = props; const { source, createDerivedIndexPattern } = useSource({ sourceId: 'default' }); + const [timeSize, setTimeSize] = useState(1); + const [timeUnit, setTimeUnit] = useState('s'); const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [ createDerivedIndexPattern, @@ -105,8 +107,10 @@ export const Expressions: React.FC = props => { const removeExpression = useCallback( (id: number) => { const exp = alertParams.criteria.slice(); - exp.splice(id, 1); - setAlertParams('criteria', exp); + if (exp.length > 1) { + exp.splice(id, 1); + setAlertParams('criteria', exp); + } }, [setAlertParams, alertParams.criteria] ); @@ -133,6 +137,30 @@ export const Expressions: React.FC = props => { }; }, []); + const updateTimeSize = useCallback( + (ts: number | '') => { + const criteria = alertParams.criteria.map(c => ({ + ...c, + timeSize: ts, + })); + setTimeSize(ts || undefined); + setAlertParams('criteria', criteria); + }, + [alertParams.criteria, setAlertParams] + ); + + const updateTimeUnit = useCallback( + (tu: string) => { + const criteria = alertParams.criteria.map(c => ({ + ...c, + timeUnit: tu, + })); + setTimeUnit(tu as TimeUnit); + setAlertParams('criteria', criteria); + }, + [alertParams.criteria, setAlertParams] + ); + useEffect(() => { const md = alertsContext.metadata; if (md) { @@ -143,8 +171,8 @@ export const Expressions: React.FC = props => { metric: metric.field, comparator: '>', threshold: [], - timeSize: 1, - timeUnit: 'm', + timeSize, + timeUnit, indexPattern: source?.configuration.metricAlias, aggType: metric.aggregation, })) @@ -180,6 +208,7 @@ export const Expressions: React.FC = props => { alertParams.criteria.map((e, idx) => { return ( 1} fields={derivedIndexPattern.fields} remove={removeExpression} addExpression={addExpression} @@ -192,23 +221,41 @@ export const Expressions: React.FC = props => { ); })} - {/* The "for last" expression should appear here, only once. It applies to all the metrics above */} - For last expression goes last here - {/* */} + /> - - Add condition - +
+ + + +
- + = props => { {alertsContext.metadata && ( - + = props => { /> )} - - {/* - -
- -
-
- - - - -
- -
-
- - {alertsContext.metadata && ( - - )} */} ); }; @@ -269,6 +292,7 @@ interface ExpressionRowProps { expressionId: number; expression: MetricExpression; errors: IErrorObject; + canDelete: boolean; addExpression(): void; remove(id: number): void; setAlertParams(id: number, params: MetricExpression): void; @@ -285,15 +309,8 @@ const StyledExpression = euiStyled.div` `; export const ExpressionRow: React.FC = props => { - const { setAlertParams, expression, errors, expressionId, remove, fields } = props; - const { - aggType = AGGREGATION_TYPES.MAX, - metric, - comparator = '>', - threshold = [], - timeSize, - timeUnit = 's', - } = expression; + const { setAlertParams, expression, errors, expressionId, remove, fields, canDelete } = props; + const { aggType = AGGREGATION_TYPES.MAX, metric, comparator = '>', threshold = [] } = expression; const updateAggType = useCallback( (at: string) => { @@ -323,20 +340,6 @@ export const ExpressionRow: React.FC = props => { [expressionId, expression, setAlertParams] ); - const updateTimeSize = useCallback( - (ts: number | '') => { - setAlertParams(expressionId, { ...expression, timeSize: ts || undefined }); - }, - [expressionId, expression, setAlertParams] - ); - - const updateTimeUnit = useCallback( - (tu: string) => { - setAlertParams(expressionId, { ...expression, timeUnit: tu as TimeUnit }); - }, - [expressionId, expression, setAlertParams] - ); - return ( <> @@ -372,88 +375,19 @@ export const ExpressionRow: React.FC = props => { - - remove(expressionId)} - /> - + {canDelete && ( + + remove(expressionId)} + /> + + )} - - {/* - - - - - - - - ({ - normalizedType: f.type, - name: f.name, - }))} - aggType={aggType} - errors={errors} - onChangeSelectedAggField={updateMetric} - /> - - - - '} - threshold={threshold} - onChangeSelectedThresholdComparator={updateComparator} - onChangeSelectedThreshold={updateThreshold} - errors={errors} - /> - - - - - - - - - - - - - - remove(expressionId)} - /> - - - - */} ); diff --git a/x-pack/plugins/infra/public/pages/infrastructure/index.tsx b/x-pack/plugins/infra/public/pages/infrastructure/index.tsx index b4ff7aeff696c..7aa1c4c1fa583 100644 --- a/x-pack/plugins/infra/public/pages/infrastructure/index.tsx +++ b/x-pack/plugins/infra/public/pages/infrastructure/index.tsx @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { Route, RouteComponentProps, Switch } from 'react-router-dom'; +import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { DocumentTitle } from '../../components/document_title'; import { HelpCenterContent } from '../../components/help_center_content'; import { RoutedTabs } from '../../components/navigation/routed_tabs'; @@ -59,31 +60,38 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { defaultMessage: 'Metrics', })} > - + + + + + +
Alerts
} /> +
+
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 d318171f3bb48..74e51aa222cc2 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 @@ -135,8 +135,8 @@ const getMetric: ( field: metric, }, }, + { ...parsedFilterQuery }, ], - ...parsedFilterQuery, }, }, size: 0, From 8befaaa5dc1545f78b3d13ababe549e0c222609d Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Tue, 17 Mar 2020 18:17:46 -0500 Subject: [PATCH 20/28] Add alert dropdown to the header and snapshot screen --- .../alerting/metrics/alert_dropdown.tsx | 66 +++++++++++++ .../alerting/metrics/alert_flyout.tsx | 2 +- .../alerting/metrics/expression.tsx | 40 +++++--- .../metrics_explorer/chart_context_menu.tsx | 2 - .../components/metrics_explorer/kuery_bar.tsx | 19 +++- .../components/metrics_explorer/toolbar.tsx | 9 -- .../components/waffle/node_context_menu.tsx | 95 +++++++++++-------- .../public/pages/infrastructure/index.tsx | 4 +- 8 files changed, 170 insertions(+), 67 deletions(-) create mode 100644 x-pack/plugins/infra/public/components/alerting/metrics/alert_dropdown.tsx diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_dropdown.tsx new file mode 100644 index 0000000000000..8022471c6f6cd --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_dropdown.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useCallback, useMemo } from 'react'; +import { EuiPopover, EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { AlertFlyout } from './alert_flyout'; +import { useLinkProps } from '../../../hooks/use_link_props'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; + +export const AlertDropdown = () => { + const [popoverOpen, setPopoverOpen] = useState(false); + const [flyoutVisible, setFlyoutVisible] = useState(false); + const kibana = useKibana(); + + const closePopover = useCallback(() => { + setPopoverOpen(false); + }, [setPopoverOpen]); + + const openPopover = useCallback(() => { + setPopoverOpen(true); + }, [setPopoverOpen]); + + const menuItems = useMemo(() => { + return [ + setFlyoutVisible(true)}> + + , + + + , + ]; + }, [kibana.services]); + + return ( + <> + + + + } + isOpen={popoverOpen} + closePopover={closePopover} + > + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx index edcd6a54dcd0b..a00d63af8aac2 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx @@ -15,7 +15,7 @@ import { MetricsExplorerSeries } from '../../../../common/http_api/metrics_explo interface Props { visible?: boolean; - options: MetricsExplorerOptions; + options?: Partial; series?: MetricsExplorerSeries; setVisible: React.Dispatch>; } diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 373c1e7a3b112..7acf02710574a 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -48,7 +48,7 @@ export interface MetricExpression { } interface AlertContextMeta { - currentOptions: MetricsExplorerOptions; + currentOptions?: Partial; series?: MetricsExplorerSeries; } @@ -77,6 +77,17 @@ export const Expressions: React.FC = props => { createDerivedIndexPattern, ]); + const options = useMemo(() => { + if (alertsContext.metadata?.currentOptions?.metrics) { + return alertsContext.metadata.currentOptions as MetricsExplorerOptions; + } else { + return { + metrics: [], + aggregation: 'avg', + }; + } + }, [alertsContext.metadata]); + const defaultExpression = useMemo( () => ({ aggType: AGGREGATION_TYPES.MAX, @@ -164,7 +175,7 @@ export const Expressions: React.FC = props => { useEffect(() => { const md = alertsContext.metadata; if (md) { - if (md.currentOptions) { + if (md.currentOptions?.metrics) { setAlertParams( 'criteria', md.currentOptions.metrics.map(metric => ({ @@ -181,14 +192,16 @@ export const Expressions: React.FC = props => { setAlertParams('criteria', [defaultExpression]); } - if (md.currentOptions.filterQuery) { - setAlertParams('filterQuery', md.currentOptions.filterQuery); - } else if (md.currentOptions.groupBy && md.series) { - const filter = `${md.currentOptions.groupBy}: "${md.series.id}"`; - setAlertParams('filterQuery', filter); + if (md.currentOptions) { + if (md.currentOptions.filterQuery) { + setAlertParams('filterQuery', md.currentOptions.filterQuery); + } else if (md.currentOptions.groupBy && md.series) { + const filter = `${md.currentOptions.groupBy}: "${md.series.id}"`; + setAlertParams('filterQuery', filter); + } + + setAlertParams('groupBy', md.currentOptions.groupBy); } - - setAlertParams('groupBy', md.currentOptions.groupBy); } }, [alertsContext.metadata, defaultExpression, source]); // eslint-disable-line react-hooks/exhaustive-deps @@ -265,13 +278,13 @@ export const Expressions: React.FC = props => { - {alertsContext.metadata && ( + {alertsContext.metadata && options.metrics.length > 0 && ( = props => { )} diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx index a016d15ff0cf0..7431e10b37927 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx @@ -75,7 +75,6 @@ export const createNodeDetailLink = ( }; export const MetricsExplorerChartContextMenu: React.FC = ({ - derivedIndexPattern, onFilter, options, series, @@ -193,7 +192,6 @@ export const MetricsExplorerChartContextMenu: React.FC = ({ ); - // options. return ( <> void; value?: string | null; + placeholder?: string; } function validateQuery(query: string) { @@ -27,7 +28,12 @@ function validateQuery(query: string) { return true; } -export const MetricsExplorerKueryBar = ({ derivedIndexPattern, onSubmit, value }: Props) => { +export const MetricsExplorerKueryBar = ({ + derivedIndexPattern, + onSubmit, + value, + placeholder, +}: Props) => { const [draftQuery, setDraftQuery] = useState(value || ''); const [isValid, setValidation] = useState(true); @@ -48,9 +54,12 @@ export const MetricsExplorerKueryBar = ({ derivedIndexPattern, onSubmit, value } fields: derivedIndexPattern.fields.filter(field => isDisplayable(field)), }; - const placeholder = i18n.translate('xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder', { - defaultMessage: 'Search for infrastructure data… (e.g. host.name:host-1)', - }); + const defaultPlaceholder = i18n.translate( + 'xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder', + { + defaultMessage: 'Search for infrastructure data… (e.g. host.name:host-1)', + } + ); return ( @@ -62,7 +71,7 @@ export const MetricsExplorerKueryBar = ({ derivedIndexPattern, onSubmit, value } loadSuggestions={loadSuggestions} onChange={handleChange} onSubmit={onSubmit} - placeholder={placeholder} + placeholder={placeholder || defaultPlaceholder} suggestions={suggestions} value={draftQuery} /> diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx index 7df46adc10864..eaf2f0127f182 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx @@ -120,15 +120,6 @@ export const MetricsExplorerToolbar = ({ value={options.filterQuery} /> - - - - - - = ({ nodeType, popoverPosition, }) => { + const [flyoutVisible, setFlyoutVisible] = useState(false); const inventoryModel = findInventoryModel(nodeType); const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000; const uiCapabilities = useKibana().services.application?.capabilities; @@ -143,42 +145,61 @@ export const NodeContextMenu: React.FC = ({ isDisabled: !showUptimeLink, }; + const alertMenuItem: SectionLinkProps = { + label: i18n.translate('xpack.infra.nodeContextMenu.createAlertLink', { + defaultMessage: 'Create Alert', + values: { inventoryName: inventoryModel.singularDisplayName }, + }), + onClick: () => { + setFlyoutVisible(true); + }, + iconType: 'bell', + }; + return ( - -
-
- - - - {inventoryId.label && ( - -
- -
-
- )} - - - - - - -
-
-
+ <> + +
+
+ + + + {inventoryId.label && ( + +
+ +
+
+ )} + + + + + + + +
+
+
+ + ); }; diff --git a/x-pack/plugins/infra/public/pages/infrastructure/index.tsx b/x-pack/plugins/infra/public/pages/infrastructure/index.tsx index 7aa1c4c1fa583..730f67ab2bdca 100644 --- a/x-pack/plugins/infra/public/pages/infrastructure/index.tsx +++ b/x-pack/plugins/infra/public/pages/infrastructure/index.tsx @@ -25,9 +25,11 @@ import { MetricsSettingsPage } from './settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; import { SourceLoadingPage } from '../../components/source_loading_page'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { AlertDropdown } from '../../components/alerting/metrics/alert_dropdown'; export const InfrastructurePage = ({ match }: RouteComponentProps) => { const uiCapabilities = useKibana().services.application?.capabilities; + return ( @@ -89,7 +91,7 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { />
-
Alerts
} /> +
From f8102543adf154c0b448162cf513c63058ee935c Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Tue, 17 Mar 2020 18:20:49 -0500 Subject: [PATCH 21/28] Remove icon --- .../plugins/infra/public/components/waffle/node_context_menu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index 628b51fdfd54a..01ed47637d071 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -153,7 +153,6 @@ export const NodeContextMenu: React.FC = ({ onClick: () => { setFlyoutVisible(true); }, - iconType: 'bell', }; return ( From ba0588f326c75c6325af989b27dd88d4d6ee392e Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Tue, 17 Mar 2020 18:41:33 -0500 Subject: [PATCH 22/28] Remove unused props. Code cleanup --- .../alerting/metrics/alert_dropdown.tsx | 8 ++------ .../components/alerting/metrics/expression.tsx | 2 +- .../public/components/metrics_explorer/chart.tsx | 5 ----- .../metrics_explorer/chart_context_menu.test.tsx | 15 +-------------- .../metrics_explorer/chart_context_menu.tsx | 2 -- .../public/components/metrics_explorer/charts.tsx | 4 ---- .../components/metrics_explorer/toolbar.tsx | 10 ++-------- .../infrastructure/metrics_explorer/index.tsx | 1 - 8 files changed, 6 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_dropdown.tsx index 8022471c6f6cd..0a464d91fbe06 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_dropdown.tsx @@ -8,7 +8,6 @@ import React, { useState, useCallback, useMemo } from 'react'; import { EuiPopover, EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { AlertFlyout } from './alert_flyout'; -import { useLinkProps } from '../../../hooks/use_link_props'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; export const AlertDropdown = () => { @@ -28,7 +27,7 @@ export const AlertDropdown = () => { return [ setFlyoutVisible(true)}> , @@ -39,10 +38,7 @@ export const AlertDropdown = () => { 'kibana#/management/kibana/triggersActions/alerts' )} > - + , ]; }, [kibana.services]); diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 7acf02710574a..8e3e20c160147 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -278,7 +278,7 @@ export const Expressions: React.FC = props => { - {alertsContext.metadata && options.metrics.length > 0 && ( + {alertsContext.metadata && ( void; width?: number | string; @@ -45,7 +43,6 @@ interface Props { } export const MetricsExplorerChart = ({ - derivedIndexPattern, source, options, chartOptions, @@ -95,7 +92,6 @@ export const MetricsExplorerChart = ({ { onFilter, uiCapabilities, chartOptions, - derivedIndexPattern, }); component.find('button').simulate('click'); expect(getTestSubject(component, 'metricsExplorerAction-AddFilter').length).toBe(1); @@ -70,7 +63,6 @@ describe('MetricsExplorerChartContextMenu', () => { onFilter, uiCapabilities, chartOptions, - derivedIndexPattern, }); component.find('button').simulate('click'); expect(getTestSubject(component, 'metricsExplorerAction-ViewNodeMetrics').length).toBe(0); @@ -84,7 +76,6 @@ describe('MetricsExplorerChartContextMenu', () => { options, uiCapabilities, chartOptions, - derivedIndexPattern, }); component.find('button').simulate('click'); expect(getTestSubject(component, 'metricsExplorerAction-AddFilter').length).toBe(0); @@ -101,7 +92,6 @@ describe('MetricsExplorerChartContextMenu', () => { onFilter, uiCapabilities, chartOptions, - derivedIndexPattern, }); component.find('button').simulate('click'); expect(getTestSubject(component, 'metricsExplorerAction-AddFilter').length).toBe(0); @@ -116,7 +106,6 @@ describe('MetricsExplorerChartContextMenu', () => { options: customOptions, uiCapabilities, chartOptions, - derivedIndexPattern, }); component.find('button').simulate('click'); expect( @@ -135,7 +124,6 @@ describe('MetricsExplorerChartContextMenu', () => { onFilter, uiCapabilities: customUICapabilities, chartOptions, - derivedIndexPattern, }); component.find('button').simulate('click'); @@ -154,7 +142,6 @@ describe('MetricsExplorerChartContextMenu', () => { onFilter, uiCapabilities: customUICapabilities, chartOptions, - derivedIndexPattern, }); expect(component.find('button').length).toBe(1); }); diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx index 7431e10b37927..75a04cbe9799e 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx @@ -14,7 +14,6 @@ import { } from '@elastic/eui'; import DateMath from '@elastic/datemath'; import { Capabilities } from 'src/core/public'; -import { IIndexPattern } from 'src/plugins/data/public'; import { MetricsExplorerSeries } from '../../../common/http_api/metrics_explorer'; import { MetricsExplorerOptions, @@ -29,7 +28,6 @@ import { AlertFlyout } from '../alerting/metrics/alert_flyout'; import { useLinkProps } from '../../hooks/use_link_props'; export interface Props { - derivedIndexPattern: IIndexPattern; options: MetricsExplorerOptions; onFilter?: (query: string) => void; series: MetricsExplorerSeries; diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/charts.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/charts.tsx index 501b074edfeea..64e7b27f5722f 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/charts.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/charts.tsx @@ -8,7 +8,6 @@ import { EuiButton, EuiFlexGrid, EuiFlexItem, EuiText, EuiHorizontalRule } from import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; -import { IIndexPattern } from 'src/plugins/data/public'; import { MetricsExplorerResponse } from '../../../common/http_api/metrics_explorer'; import { MetricsExplorerOptions, @@ -21,7 +20,6 @@ import { MetricsExplorerChart } from './chart'; import { SourceQuery } from '../../graphql/types'; interface Props { - derivedIndexPattern: IIndexPattern; loading: boolean; options: MetricsExplorerOptions; chartOptions: MetricsExplorerChartOptions; @@ -34,7 +32,6 @@ interface Props { timeRange: MetricsExplorerTimeOptions; } export const MetricsExplorerCharts = ({ - derivedIndexPattern, loading, data, onLoadMore, @@ -90,7 +87,6 @@ export const MetricsExplorerCharts = ({ title={options.groupBy ? series.id : null} height={data.series.length > 1 ? 200 : 400} series={series} - derivedIndexPattern={derivedIndexPattern} source={source} timeRange={timeRange} onTimeChange={onTimeChange} diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx index eaf2f0127f182..0fbb0b6acad17 100644 --- a/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx +++ b/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker, EuiText, EuiButton } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useState, useCallback } from 'react'; +import React from 'react'; import { IIndexPattern } from 'src/plugins/data/public'; import { MetricsExplorerMetric, @@ -28,7 +28,6 @@ import { MetricExplorerViewState } from '../../pages/infrastructure/metrics_expl import { metricsExplorerViewSavedObjectType } from '../../../common/saved_objects/metrics_explorer_view'; import { useKibanaUiSetting } from '../../utils/use_kibana_ui_setting'; import { mapKibanaQuickRangesToDatePickerRanges } from '../../utils/map_timepicker_quickranges_to_datepicker_ranges'; -import { AlertFlyout } from '../alerting/metrics/alert_flyout'; interface Props { derivedIndexPattern: IIndexPattern; @@ -64,11 +63,6 @@ export const MetricsExplorerToolbar = ({ const isDefaultOptions = options.aggregation === 'avg' && options.metrics.length === 0; const [timepickerQuickRanges] = useKibanaUiSetting('timepicker:quickRanges'); const commonlyUsedRanges = mapKibanaQuickRangesToDatePickerRanges(timepickerQuickRanges); - const [flyoutVisible, setFlyoutVisible] = useState(false); - - const showAlertFlyout = useCallback(() => { - setFlyoutVisible(true); - }, [setFlyoutVisible]); return ( diff --git a/x-pack/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx b/x-pack/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx index cf64db30e68f4..0999cea59731c 100644 --- a/x-pack/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx +++ b/x-pack/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx @@ -83,7 +83,6 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl /> ) : ( Date: Tue, 17 Mar 2020 22:11:57 -0500 Subject: [PATCH 23/28] Remove unused values --- .../plugins/infra/public/components/waffle/node_context_menu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index 01ed47637d071..8f67ae6af604f 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -148,7 +148,6 @@ export const NodeContextMenu: React.FC = ({ const alertMenuItem: SectionLinkProps = { label: i18n.translate('xpack.infra.nodeContextMenu.createAlertLink', { defaultMessage: 'Create Alert', - values: { inventoryName: inventoryModel.singularDisplayName }, }), onClick: () => { setFlyoutVisible(true); From 5b0b2388549ed601fac5a90a1990f6ed2071d955 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Tue, 17 Mar 2020 23:06:47 -0500 Subject: [PATCH 24/28] Fix formatted message id --- .../infra/public/components/alerting/metrics/expression.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 8e3e20c160147..245ac79092bf6 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -251,8 +251,8 @@ export const Expressions: React.FC = props => { onClick={addExpression} > From 99c81e22384fafad31419432e156d77c08323a99 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Wed, 18 Mar 2020 15:17:21 -0500 Subject: [PATCH 25/28] Remove create alert option for now. --- .../plugins/infra/public/components/waffle/node_context_menu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index 8f67ae6af604f..6791920cb55f8 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -188,7 +188,6 @@ export const NodeContextMenu: React.FC = ({ - From 70abcc03144751bcece604c2109c0ef33b8eecb8 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Wed, 18 Mar 2020 17:17:36 -0500 Subject: [PATCH 26/28] Fix type issue --- .../public/components/alerting/metrics/expression.tsx | 4 ++-- .../infra/public/components/waffle/node_context_menu.tsx | 9 --------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 245ac79092bf6..7c820c47e3ab3 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -56,7 +56,7 @@ interface Props { errors: IErrorObject[]; alertParams: { criteria: MetricExpression[]; - groupBy?: string | null; + groupBy?: string; filterQuery?: string; }; alertsContext: AlertsContextValue; @@ -135,7 +135,7 @@ export const Expressions: React.FC = props => { const onGroupByChange = useCallback( (group: string | null) => { - setAlertParams('groupBy', group); + setAlertParams('groupBy', group || undefined); }, [setAlertParams] ); diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index 6791920cb55f8..5f05cebd8f616 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -145,15 +145,6 @@ export const NodeContextMenu: React.FC = ({ isDisabled: !showUptimeLink, }; - const alertMenuItem: SectionLinkProps = { - label: i18n.translate('xpack.infra.nodeContextMenu.createAlertLink', { - defaultMessage: 'Create Alert', - }), - onClick: () => { - setFlyoutVisible(true); - }, - }; - return ( <> Date: Thu, 19 Mar 2020 16:26:14 -0500 Subject: [PATCH 27/28] Add rate, card and count as aggs --- .../alerting/metrics/expression.tsx | 89 ++++++++++++------- 1 file changed, 59 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 7c820c47e3ab3..0597b5dc9ccbe 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -30,7 +30,6 @@ import { IErrorObject } from '../../../../../triggers_actions_ui/public/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertsContextValue } from '../../../../../triggers_actions_ui/public/application/context/alerts_context'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { AGGREGATION_TYPES } from '../../../../../triggers_actions_ui/public/common/constants'; import { MetricsExplorerOptions } from '../../../containers/metrics_explorer/use_metrics_explorer_options'; import { MetricsExplorerKueryBar } from '../../metrics_explorer/kuery_bar'; import { MetricsExplorerSeries } from '../../../../common/http_api/metrics_explorer'; @@ -368,18 +367,20 @@ export const ExpressionRow: React.FC = props => { onChangeSelectedAggType={updateAggType} /> - - ({ - normalizedType: f.type, - name: f.name, - }))} - aggType={aggType} - errors={errors} - onChangeSelectedAggField={updateMetric} - /> - + {aggType !== 'count' && ( + + ({ + normalizedType: f.type, + name: f.name, + }))} + aggType={aggType} + errors={errors} + onChangeSelectedAggField={updateMetric} + /> + + )} '} @@ -409,35 +410,63 @@ export const ExpressionRow: React.FC = props => { ); }; +enum AGGREGATION_TYPES { + COUNT = 'count', + AVERAGE = 'avg', + SUM = 'sum', + MIN = 'min', + MAX = 'max', + RATE = 'rate', + CARDINALITY = 'cardinality', +} + export const aggregationType: { [key: string]: any } = { - count: { - text: 'count', - fieldRequired: false, - value: AGGREGATION_TYPES.COUNT, - validNormalizedTypes: [], - }, avg: { - text: 'average', + text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.avg', { + defaultMessage: 'Average', + }), fieldRequired: true, validNormalizedTypes: ['number'], value: AGGREGATION_TYPES.AVERAGE, }, - sum: { - text: 'sum', + max: { + text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.max', { + defaultMessage: 'Max', + }), fieldRequired: true, - validNormalizedTypes: ['number'], - value: AGGREGATION_TYPES.SUM, + validNormalizedTypes: ['number', 'date'], + value: AGGREGATION_TYPES.MAX, }, min: { - text: 'min', + text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.min', { + defaultMessage: 'Min', + }), fieldRequired: true, validNormalizedTypes: ['number', 'date'], value: AGGREGATION_TYPES.MIN, }, - max: { - text: 'max', - fieldRequired: true, - validNormalizedTypes: ['number', 'date'], - value: AGGREGATION_TYPES.MAX, + cardinality: { + text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.cardinality', { + defaultMessage: 'Cardinality', + }), + fieldRequired: false, + value: AGGREGATION_TYPES.CARDINALITY, + validNormalizedTypes: ['number'], + }, + rate: { + text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.rate', { + defaultMessage: 'Rate', + }), + fieldRequired: false, + value: AGGREGATION_TYPES.RATE, + validNormalizedTypes: ['number'], + }, + count: { + text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.count', { + defaultMessage: 'Document count', + }), + fieldRequired: false, + value: AGGREGATION_TYPES.COUNT, + validNormalizedTypes: ['number'], }, }; From 68b8a27396daa0915333fae0755d6230160c5e91 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Mon, 23 Mar 2020 08:02:43 -0500 Subject: [PATCH 28/28] Fix types --- .../infra/public/components/alerting/metrics/expression.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 0597b5dc9ccbe..ea8dd1484a670 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -148,7 +148,7 @@ export const Expressions: React.FC = props => { }, []); const updateTimeSize = useCallback( - (ts: number | '') => { + (ts: number | undefined) => { const criteria = alertParams.criteria.map(c => ({ ...c, timeSize: ts, @@ -370,6 +370,7 @@ export const ExpressionRow: React.FC = props => { {aggType !== 'count' && ( ({ normalizedType: f.type,