mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Metrics UI] Alerting for metrics explorer and inventory (#58779)
* Add flyout with expressions * Integrate frontend with backend * Extended AlertContextValue with metadata optional property * Progress * Pre-fill criteria with current page filters * Better validation. Naming for clarity * Fix types for flyout * Respect the groupby property in metric explorer * Fix lint errors * Fix text, add toast notifications * Fix tests. Make sure update handles predefined expressions * Dynamically load source from alert flyout * Remove unused import * Simplify and add group by functionality * Remove unecessary useEffect * disable exhastive deps * Remove unecessary useEffect * change language * Implement design feedback * Add alert dropdown to the header and snapshot screen * Remove icon * Remove unused props. Code cleanup * Remove unused values * Fix formatted message id * Remove create alert option for now. * Fix type issue * Add rate, card and count as aggs * Fix types Co-authored-by: Yuliia Naumenko <yuliia.naumenko@elastic.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Henry Harding <henry.harding@elastic.co>
This commit is contained in:
parent
8572e3f18f
commit
a790877694
15 changed files with 887 additions and 96 deletions
|
@ -11,7 +11,8 @@
|
|||
"data",
|
||||
"dataEnhanced",
|
||||
"metrics",
|
||||
"alerting"
|
||||
"alerting",
|
||||
"triggers_actions_ui"
|
||||
],
|
||||
"server": true,
|
||||
"ui": true,
|
||||
|
|
|
@ -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';
|
||||
import '../index.scss';
|
||||
|
||||
export const CONTAINER_CLASSNAME = 'infra-container-element';
|
||||
|
@ -35,7 +38,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 });
|
||||
|
@ -51,19 +55,21 @@ export async function startApp(
|
|||
return (
|
||||
<core.i18n.Context>
|
||||
<EuiErrorBoundary>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<ReduxStateContextProvider>
|
||||
<ApolloProvider client={libs.apolloClient}>
|
||||
<ApolloClientContext.Provider value={libs.apolloClient}>
|
||||
<EuiThemeProvider darkMode={darkMode}>
|
||||
<HistoryContext.Provider value={history}>
|
||||
<Router history={history} />
|
||||
</HistoryContext.Provider>
|
||||
</EuiThemeProvider>
|
||||
</ApolloClientContext.Provider>
|
||||
</ApolloProvider>
|
||||
</ReduxStateContextProvider>
|
||||
</ReduxStoreProvider>
|
||||
<TriggersActionsProvider triggersActionsUI={triggersActionsUI}>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<ReduxStateContextProvider>
|
||||
<ApolloProvider client={libs.apolloClient}>
|
||||
<ApolloClientContext.Provider value={libs.apolloClient}>
|
||||
<EuiThemeProvider darkMode={darkMode}>
|
||||
<HistoryContext.Provider value={history}>
|
||||
<Router history={history} />
|
||||
</HistoryContext.Provider>
|
||||
</EuiThemeProvider>
|
||||
</ApolloClientContext.Provider>
|
||||
</ApolloProvider>
|
||||
</ReduxStateContextProvider>
|
||||
</ReduxStoreProvider>
|
||||
</TriggersActionsProvider>
|
||||
</EuiErrorBoundary>
|
||||
</core.i18n.Context>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { 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 [
|
||||
<EuiContextMenuItem icon="bell" key="createLink" onClick={() => setFlyoutVisible(true)}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.alerting.createAlertButton"
|
||||
defaultMessage="Create alert"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
icon="tableOfContents"
|
||||
key="manageLink"
|
||||
href={kibana.services?.application?.getUrlForApp(
|
||||
'kibana#/management/kibana/triggersActions/alerts'
|
||||
)}
|
||||
>
|
||||
<FormattedMessage id="xpack.infra.alerting.manageAlerts" defaultMessage="Manage Alerts" />
|
||||
</EuiContextMenuItem>,
|
||||
];
|
||||
}, [kibana.services]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButtonEmpty iconSide={'right'} iconType={'arrowDown'} onClick={openPopover}>
|
||||
<FormattedMessage id="xpack.infra.alerting.alertsButton" defaultMessage="Alerts" />
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
isOpen={popoverOpen}
|
||||
closePopover={closePopover}
|
||||
>
|
||||
<EuiContextMenuPanel items={menuItems} />
|
||||
</EuiPopover>
|
||||
<AlertFlyout setVisible={setFlyoutVisible} visible={flyoutVisible} />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 { 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 { MetricsExplorerOptions } from '../../../containers/metrics_explorer/use_metrics_explorer_options';
|
||||
import { MetricsExplorerSeries } from '../../../../common/http_api/metrics_explorer';
|
||||
|
||||
interface Props {
|
||||
visible?: boolean;
|
||||
options?: Partial<MetricsExplorerOptions>;
|
||||
series?: MetricsExplorerSeries;
|
||||
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export const AlertFlyout = (props: Props) => {
|
||||
const { triggersActionsUI } = useContext(TriggerActionsContext);
|
||||
const { services } = useKibana();
|
||||
|
||||
return (
|
||||
<>
|
||||
{triggersActionsUI && (
|
||||
<AlertsContextProvider
|
||||
value={{
|
||||
metadata: {
|
||||
currentOptions: props.options,
|
||||
series: props.series,
|
||||
},
|
||||
toastNotifications: services.notifications?.toasts,
|
||||
http: services.http,
|
||||
actionTypeRegistry: triggersActionsUI.actionTypeRegistry,
|
||||
alertTypeRegistry: triggersActionsUI.alertTypeRegistry,
|
||||
}}
|
||||
>
|
||||
<AlertAdd
|
||||
addFlyoutVisible={props.visible!}
|
||||
setAddFlyoutVisibility={props.setVisible}
|
||||
alertTypeId={METRIC_THRESHOLD_ALERT_TYPE_ID}
|
||||
canChangeTrigger={false}
|
||||
consumer={'metrics'}
|
||||
/>
|
||||
</AlertsContextProvider>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,473 @@
|
|||
/*
|
||||
* 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, useEffect, useState } from 'react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonIcon,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiFormRow,
|
||||
EuiButtonEmpty,
|
||||
} 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,
|
||||
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 { MetricsExplorerOptions } from '../../../containers/metrics_explorer/use_metrics_explorer_options';
|
||||
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;
|
||||
metric?: string;
|
||||
comparator?: Comparator;
|
||||
threshold?: number[];
|
||||
timeSize?: number;
|
||||
timeUnit?: TimeUnit;
|
||||
indexPattern?: string;
|
||||
}
|
||||
|
||||
interface AlertContextMeta {
|
||||
currentOptions?: Partial<MetricsExplorerOptions>;
|
||||
series?: MetricsExplorerSeries;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
errors: IErrorObject[];
|
||||
alertParams: {
|
||||
criteria: MetricExpression[];
|
||||
groupBy?: string;
|
||||
filterQuery?: string;
|
||||
};
|
||||
alertsContext: AlertsContextValue<AlertContextMeta>;
|
||||
setAlertParams(key: string, value: any): void;
|
||||
setAlertProperty(key: string, value: any): void;
|
||||
}
|
||||
|
||||
type Comparator = '>' | '>=' | 'between' | '<' | '<=';
|
||||
type TimeUnit = 's' | 'm' | 'h' | 'd';
|
||||
|
||||
export const Expressions: React.FC<Props> = props => {
|
||||
const { setAlertParams, alertParams, errors, alertsContext } = props;
|
||||
const { source, createDerivedIndexPattern } = useSource({ sourceId: 'default' });
|
||||
const [timeSize, setTimeSize] = useState<number | undefined>(1);
|
||||
const [timeUnit, setTimeUnit] = useState<TimeUnit>('s');
|
||||
|
||||
const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [
|
||||
createDerivedIndexPattern,
|
||||
]);
|
||||
|
||||
const options = useMemo<MetricsExplorerOptions>(() => {
|
||||
if (alertsContext.metadata?.currentOptions?.metrics) {
|
||||
return alertsContext.metadata.currentOptions as MetricsExplorerOptions;
|
||||
} else {
|
||||
return {
|
||||
metrics: [],
|
||||
aggregation: 'avg',
|
||||
};
|
||||
}
|
||||
}, [alertsContext.metadata]);
|
||||
|
||||
const defaultExpression = useMemo<MetricExpression>(
|
||||
() => ({
|
||||
aggType: AGGREGATION_TYPES.MAX,
|
||||
comparator: '>',
|
||||
threshold: [],
|
||||
timeSize: 1,
|
||||
timeUnit: 's',
|
||||
indexPattern: source?.configuration.metricAlias,
|
||||
}),
|
||||
[source]
|
||||
);
|
||||
|
||||
const updateParams = useCallback(
|
||||
(id, e: MetricExpression) => {
|
||||
const exp = alertParams.criteria ? alertParams.criteria.slice() : [];
|
||||
exp[id] = { ...exp[id], ...e };
|
||||
setAlertParams('criteria', exp);
|
||||
},
|
||||
[setAlertParams, alertParams.criteria]
|
||||
);
|
||||
|
||||
const addExpression = useCallback(() => {
|
||||
const exp = alertParams.criteria.slice();
|
||||
exp.push(defaultExpression);
|
||||
setAlertParams('criteria', exp);
|
||||
}, [setAlertParams, alertParams.criteria, defaultExpression]);
|
||||
|
||||
const removeExpression = useCallback(
|
||||
(id: number) => {
|
||||
const exp = alertParams.criteria.slice();
|
||||
if (exp.length > 1) {
|
||||
exp.splice(id, 1);
|
||||
setAlertParams('criteria', exp);
|
||||
}
|
||||
},
|
||||
[setAlertParams, alertParams.criteria]
|
||||
);
|
||||
|
||||
const onFilterQuerySubmit = useCallback(
|
||||
(filter: any) => {
|
||||
setAlertParams('filterQuery', filter);
|
||||
},
|
||||
[setAlertParams]
|
||||
);
|
||||
|
||||
const onGroupByChange = useCallback(
|
||||
(group: string | null) => {
|
||||
setAlertParams('groupBy', group || undefined);
|
||||
},
|
||||
[setAlertParams]
|
||||
);
|
||||
|
||||
const emptyError = useMemo(() => {
|
||||
return {
|
||||
aggField: [],
|
||||
timeSizeUnit: [],
|
||||
timeWindowSize: [],
|
||||
};
|
||||
}, []);
|
||||
|
||||
const updateTimeSize = useCallback(
|
||||
(ts: number | undefined) => {
|
||||
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) {
|
||||
if (md.currentOptions?.metrics) {
|
||||
setAlertParams(
|
||||
'criteria',
|
||||
md.currentOptions.metrics.map(metric => ({
|
||||
metric: metric.field,
|
||||
comparator: '>',
|
||||
threshold: [],
|
||||
timeSize,
|
||||
timeUnit,
|
||||
indexPattern: source?.configuration.metricAlias,
|
||||
aggType: metric.aggregation,
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
setAlertParams('criteria', [defaultExpression]);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}, [alertsContext.metadata, defaultExpression, source]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size={'m'} />
|
||||
<EuiText size="xs">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metrics.alertFlyout.conditions"
|
||||
defaultMessage="Conditions"
|
||||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
<EuiSpacer size={'xs'} />
|
||||
{alertParams.criteria &&
|
||||
alertParams.criteria.map((e, idx) => {
|
||||
return (
|
||||
<ExpressionRow
|
||||
canDelete={alertParams.criteria.length > 1}
|
||||
fields={derivedIndexPattern.fields}
|
||||
remove={removeExpression}
|
||||
addExpression={addExpression}
|
||||
key={idx} // idx's don't usually make good key's but here the index has semantic meaning
|
||||
expressionId={idx}
|
||||
setAlertParams={updateParams}
|
||||
errors={errors[idx] || emptyError}
|
||||
expression={e || {}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<ForLastExpression
|
||||
timeWindowSize={timeSize}
|
||||
timeWindowUnit={timeUnit}
|
||||
errors={emptyError}
|
||||
onChangeWindowSize={updateTimeSize}
|
||||
onChangeWindowUnit={updateTimeUnit}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<EuiButtonEmpty
|
||||
color={'primary'}
|
||||
iconSide={'left'}
|
||||
flush={'left'}
|
||||
iconType={'plusInCircleFilled'}
|
||||
onClick={addExpression}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metrics.alertFlyout.addCondition"
|
||||
defaultMessage="Add condition"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</div>
|
||||
|
||||
<EuiSpacer size={'m'} />
|
||||
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.infra.metrics.alertFlyout.filterLabel', {
|
||||
defaultMessage: 'Filter',
|
||||
})}
|
||||
helpText={i18n.translate('xpack.infra.metrics.alertFlyout.filterHelpText', {
|
||||
defaultMessage: 'Filter help text',
|
||||
})}
|
||||
fullWidth
|
||||
compressed
|
||||
>
|
||||
<MetricsExplorerKueryBar
|
||||
derivedIndexPattern={derivedIndexPattern}
|
||||
onSubmit={onFilterQuerySubmit}
|
||||
value={alertParams.filterQuery}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiSpacer size={'m'} />
|
||||
|
||||
{alertsContext.metadata && (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.infra.metrics.alertFlyout.createAlertPerText', {
|
||||
defaultMessage: 'Create alert per',
|
||||
})}
|
||||
helpText={i18n.translate('xpack.infra.metrics.alertFlyout.createAlertPerHelpText', {
|
||||
defaultMessage: 'Create alert help text',
|
||||
})}
|
||||
fullWidth
|
||||
compressed
|
||||
>
|
||||
<MetricsExplorerGroupBy
|
||||
onChange={onGroupByChange}
|
||||
fields={derivedIndexPattern.fields}
|
||||
options={{
|
||||
...options,
|
||||
groupBy: alertParams.groupBy || undefined,
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface ExpressionRowProps {
|
||||
fields: IFieldType[];
|
||||
expressionId: number;
|
||||
expression: MetricExpression;
|
||||
errors: IErrorObject;
|
||||
canDelete: boolean;
|
||||
addExpression(): void;
|
||||
remove(id: number): void;
|
||||
setAlertParams(id: number, params: MetricExpression): void;
|
||||
}
|
||||
|
||||
const StyledExpressionRow = euiStyled(EuiFlexGroup)`
|
||||
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<ExpressionRowProps> = props => {
|
||||
const { setAlertParams, expression, errors, expressionId, remove, fields, canDelete } = props;
|
||||
const { aggType = AGGREGATION_TYPES.MAX, metric, comparator = '>', threshold = [] } = expression;
|
||||
|
||||
const updateAggType = useCallback(
|
||||
(at: string) => {
|
||||
setAlertParams(expressionId, { ...expression, aggType: at });
|
||||
},
|
||||
[expressionId, expression, setAlertParams]
|
||||
);
|
||||
|
||||
const updateMetric = useCallback(
|
||||
(m?: string) => {
|
||||
setAlertParams(expressionId, { ...expression, metric: m });
|
||||
},
|
||||
[expressionId, expression, setAlertParams]
|
||||
);
|
||||
|
||||
const updateComparator = useCallback(
|
||||
(c?: string) => {
|
||||
setAlertParams(expressionId, { ...expression, comparator: c as Comparator });
|
||||
},
|
||||
[expressionId, expression, setAlertParams]
|
||||
);
|
||||
|
||||
const updateThreshold = useCallback(
|
||||
t => {
|
||||
setAlertParams(expressionId, { ...expression, threshold: t });
|
||||
},
|
||||
[expressionId, expression, setAlertParams]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow>
|
||||
<StyledExpressionRow>
|
||||
<StyledExpression>
|
||||
<WhenExpression
|
||||
customAggTypesOptions={aggregationType}
|
||||
aggType={aggType}
|
||||
onChangeSelectedAggType={updateAggType}
|
||||
/>
|
||||
</StyledExpression>
|
||||
{aggType !== 'count' && (
|
||||
<StyledExpression>
|
||||
<OfExpression
|
||||
customAggTypesOptions={aggregationType}
|
||||
aggField={metric}
|
||||
fields={fields.map(f => ({
|
||||
normalizedType: f.type,
|
||||
name: f.name,
|
||||
}))}
|
||||
aggType={aggType}
|
||||
errors={errors}
|
||||
onChangeSelectedAggField={updateMetric}
|
||||
/>
|
||||
</StyledExpression>
|
||||
)}
|
||||
<StyledExpression>
|
||||
<ThresholdExpression
|
||||
thresholdComparator={comparator || '>'}
|
||||
threshold={threshold}
|
||||
onChangeSelectedThresholdComparator={updateComparator}
|
||||
onChangeSelectedThreshold={updateThreshold}
|
||||
errors={errors}
|
||||
/>
|
||||
</StyledExpression>
|
||||
</StyledExpressionRow>
|
||||
</EuiFlexItem>
|
||||
{canDelete && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.translate('xpack.infra.metrics.alertFlyout.removeCondition', {
|
||||
defaultMessage: 'Remove condition',
|
||||
})}
|
||||
color={'danger'}
|
||||
iconType={'trash'}
|
||||
onClick={() => remove(expressionId)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size={'s'} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
enum AGGREGATION_TYPES {
|
||||
COUNT = 'count',
|
||||
AVERAGE = 'avg',
|
||||
SUM = 'sum',
|
||||
MIN = 'min',
|
||||
MAX = 'max',
|
||||
RATE = 'rate',
|
||||
CARDINALITY = 'cardinality',
|
||||
}
|
||||
|
||||
export const aggregationType: { [key: string]: any } = {
|
||||
avg: {
|
||||
text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.avg', {
|
||||
defaultMessage: 'Average',
|
||||
}),
|
||||
fieldRequired: true,
|
||||
validNormalizedTypes: ['number'],
|
||||
value: AGGREGATION_TYPES.AVERAGE,
|
||||
},
|
||||
max: {
|
||||
text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.max', {
|
||||
defaultMessage: 'Max',
|
||||
}),
|
||||
fieldRequired: true,
|
||||
validNormalizedTypes: ['number', 'date'],
|
||||
value: AGGREGATION_TYPES.MAX,
|
||||
},
|
||||
min: {
|
||||
text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.min', {
|
||||
defaultMessage: 'Min',
|
||||
}),
|
||||
fieldRequired: true,
|
||||
validNormalizedTypes: ['number', 'date'],
|
||||
value: AGGREGATION_TYPES.MIN,
|
||||
},
|
||||
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'],
|
||||
},
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { AlertTypeModel } from '../../../../../triggers_actions_ui/public/types';
|
||||
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: i18n.translate('xpack.infra.metrics.alertFlyout.alertName', {
|
||||
defaultMessage: 'Alert Trigger',
|
||||
}),
|
||||
iconClass: 'bell',
|
||||
alertParamsExpression: Expressions,
|
||||
validate: validateMetricThreshold,
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -143,7 +143,7 @@ describe('MetricsExplorerChartContextMenu', () => {
|
|||
uiCapabilities: customUICapabilities,
|
||||
chartOptions,
|
||||
});
|
||||
expect(component.find('button').length).toBe(0);
|
||||
expect(component.find('button').length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ 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 { AlertFlyout } from '../alerting/metrics/alert_flyout';
|
||||
import { useLinkProps } from '../../hooks/use_link_props';
|
||||
|
||||
export interface Props {
|
||||
|
@ -81,6 +82,7 @@ export const MetricsExplorerChartContextMenu: React.FC<Props> = ({
|
|||
chartOptions,
|
||||
}: Props) => {
|
||||
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
|
||||
|
@ -141,7 +143,20 @@ export const MetricsExplorerChartContextMenu: React.FC<Props> = ({
|
|||
]
|
||||
: [];
|
||||
|
||||
const itemPanels = [...filterByItem, ...openInVisualize, ...viewNodeDetail];
|
||||
const itemPanels = [
|
||||
...filterByItem,
|
||||
...openInVisualize,
|
||||
...viewNodeDetail,
|
||||
{
|
||||
name: i18n.translate('xpack.infra.metricsExplorer.alerts.createAlertButton', {
|
||||
defaultMessage: 'Create alert',
|
||||
}),
|
||||
icon: 'bell',
|
||||
onClick() {
|
||||
setFlyoutVisible(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// If there are no itemPanels then there is no reason to show the actions button.
|
||||
if (itemPanels.length === 0) return null;
|
||||
|
@ -174,15 +189,24 @@ export const MetricsExplorerChartContextMenu: React.FC<Props> = ({
|
|||
{actionLabel}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
closePopover={handleClose}
|
||||
id={`${series.id}-popover`}
|
||||
button={button}
|
||||
isOpen={isPopoverOpen}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
<>
|
||||
<EuiPopover
|
||||
closePopover={handleClose}
|
||||
id={`${series.id}-popover`}
|
||||
button={button}
|
||||
isOpen={isPopoverOpen}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
<AlertFlyout
|
||||
series={series}
|
||||
options={options}
|
||||
setVisible={setFlyoutVisible}
|
||||
visible={flyoutVisible}
|
||||
/>
|
||||
</EuiPopover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -16,6 +16,7 @@ interface Props {
|
|||
derivedIndexPattern: IIndexPattern;
|
||||
onSubmit: (query: string) => 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<string>(value || '');
|
||||
const [isValid, setValidation] = useState<boolean>(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 (
|
||||
<WithKueryAutocompletion indexPattern={filteredDerivedIndexPattern}>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -63,6 +63,7 @@ export const MetricsExplorerToolbar = ({
|
|||
const isDefaultOptions = options.aggregation === 'avg' && options.metrics.length === 0;
|
||||
const [timepickerQuickRanges] = useKibanaUiSetting('timepicker:quickRanges');
|
||||
const commonlyUsedRanges = mapKibanaQuickRangesToDatePickerRanges(timepickerQuickRanges);
|
||||
|
||||
return (
|
||||
<Toolbar>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
|
|
|
@ -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 { useLinkProps } from '../../hooks/use_link_props';
|
||||
import { AlertFlyout } from '../alerting/metrics/alert_flyout';
|
||||
|
||||
interface Props {
|
||||
options: InfraWaffleMapOptions;
|
||||
|
@ -46,6 +47,7 @@ export const NodeContextMenu: React.FC<Props> = ({
|
|||
nodeType,
|
||||
popoverPosition,
|
||||
}) => {
|
||||
const [flyoutVisible, setFlyoutVisible] = useState(false);
|
||||
const inventoryModel = findInventoryModel(nodeType);
|
||||
const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000;
|
||||
const uiCapabilities = useKibana().services.application?.capabilities;
|
||||
|
@ -144,41 +146,48 @@ export const NodeContextMenu: React.FC<Props> = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<ActionMenu
|
||||
closePopover={closePopover}
|
||||
id={`${node.pathId}-popover`}
|
||||
isOpen={isPopoverOpen}
|
||||
button={children!}
|
||||
anchorPosition={popoverPosition}
|
||||
>
|
||||
<div style={{ maxWidth: 300 }} data-test-subj="nodeContextMenu">
|
||||
<Section>
|
||||
<SectionTitle>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.nodeContextMenu.title"
|
||||
defaultMessage="{inventoryName} details"
|
||||
values={{ inventoryName: inventoryModel.singularDisplayName }}
|
||||
/>
|
||||
</SectionTitle>
|
||||
{inventoryId.label && (
|
||||
<SectionSubtitle>
|
||||
<div style={{ wordBreak: 'break-all' }}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.nodeContextMenu.description"
|
||||
defaultMessage="View details for {label} {value}"
|
||||
values={{ label: inventoryId.label, value: inventoryId.value }}
|
||||
/>
|
||||
</div>
|
||||
</SectionSubtitle>
|
||||
)}
|
||||
<SectionLinks>
|
||||
<SectionLink data-test-subj="viewLogsContextMenuItem" {...nodeLogsMenuItem} />
|
||||
<SectionLink {...nodeDetailMenuItem} />
|
||||
<SectionLink data-test-subj="viewApmTracesContextMenuItem" {...apmTracesMenuItem} />
|
||||
<SectionLink {...uptimeMenuItem} />
|
||||
</SectionLinks>
|
||||
</Section>
|
||||
</div>
|
||||
</ActionMenu>
|
||||
<>
|
||||
<ActionMenu
|
||||
closePopover={closePopover}
|
||||
id={`${node.pathId}-popover`}
|
||||
isOpen={isPopoverOpen}
|
||||
button={children!}
|
||||
anchorPosition={popoverPosition}
|
||||
>
|
||||
<div style={{ maxWidth: 300 }} data-test-subj="nodeContextMenu">
|
||||
<Section>
|
||||
<SectionTitle>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.nodeContextMenu.title"
|
||||
defaultMessage="{inventoryName} details"
|
||||
values={{ inventoryName: inventoryModel.singularDisplayName }}
|
||||
/>
|
||||
</SectionTitle>
|
||||
{inventoryId.label && (
|
||||
<SectionSubtitle>
|
||||
<div style={{ wordBreak: 'break-all' }}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.nodeContextMenu.description"
|
||||
defaultMessage="View details for {label} {value}"
|
||||
values={{ label: inventoryId.label, value: inventoryId.value }}
|
||||
/>
|
||||
</div>
|
||||
</SectionSubtitle>
|
||||
)}
|
||||
<SectionLinks>
|
||||
<SectionLink data-test-subj="viewLogsContextMenuItem" {...nodeLogsMenuItem} />
|
||||
<SectionLink {...nodeDetailMenuItem} />
|
||||
<SectionLink data-test-subj="viewApmTracesContextMenuItem" {...apmTracesMenuItem} />
|
||||
<SectionLink {...uptimeMenuItem} />
|
||||
</SectionLinks>
|
||||
</Section>
|
||||
</div>
|
||||
</ActionMenu>
|
||||
<AlertFlyout
|
||||
options={{ filterQuery: `${nodeType}: ${node.id}` }}
|
||||
setVisible={setFlyoutVisible}
|
||||
visible={flyoutVisible}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
@ -24,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 (
|
||||
<Source.Provider sourceId="default">
|
||||
<ColumnarPage>
|
||||
|
@ -59,31 +62,38 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
|
|||
defaultMessage: 'Metrics',
|
||||
})}
|
||||
>
|
||||
<RoutedTabs
|
||||
tabs={[
|
||||
{
|
||||
app: 'metrics',
|
||||
title: i18n.translate('xpack.infra.homePage.inventoryTabTitle', {
|
||||
defaultMessage: 'Inventory',
|
||||
}),
|
||||
pathname: '/inventory',
|
||||
},
|
||||
{
|
||||
app: 'metrics',
|
||||
title: i18n.translate('xpack.infra.homePage.metricsExplorerTabTitle', {
|
||||
defaultMessage: 'Metrics Explorer',
|
||||
}),
|
||||
pathname: '/explorer',
|
||||
},
|
||||
{
|
||||
app: 'metrics',
|
||||
title: i18n.translate('xpack.infra.homePage.settingsTabTitle', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
pathname: '/settings',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<EuiFlexGroup gutterSize={'none'} alignItems={'center'}>
|
||||
<EuiFlexItem>
|
||||
<RoutedTabs
|
||||
tabs={[
|
||||
{
|
||||
app: 'metrics',
|
||||
title: i18n.translate('xpack.infra.homePage.inventoryTabTitle', {
|
||||
defaultMessage: 'Inventory',
|
||||
}),
|
||||
pathname: '/inventory',
|
||||
},
|
||||
{
|
||||
app: 'metrics',
|
||||
title: i18n.translate('xpack.infra.homePage.metricsExplorerTabTitle', {
|
||||
defaultMessage: 'Metrics Explorer',
|
||||
}),
|
||||
pathname: '/explorer',
|
||||
},
|
||||
{
|
||||
app: 'metrics',
|
||||
title: i18n.translate('xpack.infra.homePage.settingsTabTitle', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
pathname: '/settings',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<AlertDropdown />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</AppNavigation>
|
||||
|
||||
<Switch>
|
||||
|
|
|
@ -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/metrics/metric_threshold_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 {
|
||||
|
@ -58,6 +61,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', {
|
||||
|
@ -76,7 +81,8 @@ export class Plugin
|
|||
coreStart,
|
||||
plugins,
|
||||
params,
|
||||
LogsRouter
|
||||
LogsRouter,
|
||||
pluginsSetup.triggers_actions_ui
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -99,7 +105,8 @@ export class Plugin
|
|||
coreStart,
|
||||
plugins,
|
||||
params,
|
||||
MetricsRouter
|
||||
MetricsRouter,
|
||||
pluginsSetup.triggers_actions_ui
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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<ContextProps>({
|
||||
triggersActionsUI: null,
|
||||
});
|
||||
|
||||
interface Props {
|
||||
triggersActionsUI: TriggersAndActionsUIPublicPluginSetup;
|
||||
}
|
||||
|
||||
export const TriggersActionsProvider: React.FC<Props> = props => {
|
||||
return (
|
||||
<TriggerActionsContext.Provider
|
||||
value={{
|
||||
triggersActionsUI: props.triggersActionsUI,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</TriggerActionsContext.Provider>
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue