Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[7.x] [Logs UI] Alerting (#62806) #64530

Merged
merged 1 commit into from
Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions x-pack/plugins/infra/common/alerting/logs/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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';

export const LOG_DOCUMENT_COUNT_ALERT_TYPE_ID = 'logs.alert.document.count';

export enum Comparator {
GT = 'more than',
GT_OR_EQ = 'more than or equals',
LT = 'less than',
LT_OR_EQ = 'less than or equals',
EQ = 'equals',
NOT_EQ = 'does not equal',
MATCH = 'matches',
NOT_MATCH = 'does not match',
MATCH_PHRASE = 'matches phrase',
NOT_MATCH_PHRASE = 'does not match phrase',
}

// Maps our comparators to i18n strings, some comparators have more specific wording
// depending on the field type the comparator is being used with.
export const ComparatorToi18nMap = {
[Comparator.GT]: i18n.translate('xpack.infra.logs.alerting.comparator.gt', {
defaultMessage: 'more than',
}),
[Comparator.GT_OR_EQ]: i18n.translate('xpack.infra.logs.alerting.comparator.gtOrEq', {
defaultMessage: 'more than or equals',
}),
[Comparator.LT]: i18n.translate('xpack.infra.logs.alerting.comparator.lt', {
defaultMessage: 'less than',
}),
[Comparator.LT_OR_EQ]: i18n.translate('xpack.infra.logs.alerting.comparator.ltOrEq', {
defaultMessage: 'less than or equals',
}),
[Comparator.EQ]: i18n.translate('xpack.infra.logs.alerting.comparator.eq', {
defaultMessage: 'is',
}),
[Comparator.NOT_EQ]: i18n.translate('xpack.infra.logs.alerting.comparator.notEq', {
defaultMessage: 'is not',
}),
[`${Comparator.EQ}:number`]: i18n.translate('xpack.infra.logs.alerting.comparator.eqNumber', {
defaultMessage: 'equals',
}),
[`${Comparator.NOT_EQ}:number`]: i18n.translate(
'xpack.infra.logs.alerting.comparator.notEqNumber',
{
defaultMessage: 'does not equal',
}
),
[Comparator.MATCH]: i18n.translate('xpack.infra.logs.alerting.comparator.match', {
defaultMessage: 'matches',
}),
[Comparator.NOT_MATCH]: i18n.translate('xpack.infra.logs.alerting.comparator.notMatch', {
defaultMessage: 'does not match',
}),
[Comparator.MATCH_PHRASE]: i18n.translate('xpack.infra.logs.alerting.comparator.matchPhrase', {
defaultMessage: 'matches phrase',
}),
[Comparator.NOT_MATCH_PHRASE]: i18n.translate(
'xpack.infra.logs.alerting.comparator.notMatchPhrase',
{
defaultMessage: 'does not match phrase',
}
),
};

export enum AlertStates {
OK,
ALERT,
NO_DATA,
ERROR,
}

export interface DocumentCount {
comparator: Comparator;
value: number;
}

export interface Criterion {
field: string;
comparator: Comparator;
value: string | number;
}

export interface LogDocumentCountAlertParams {
count: DocumentCount;
criteria: Criterion[];
timeUnit: 's' | 'm' | 'h' | 'd';
timeSize: number;
}

export type TimeUnit = 's' | 'm' | 'h' | 'd';
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import 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';

export const AlertDropdown = () => {
const [popoverOpen, setPopoverOpen] = useState(false);
const [flyoutVisible, setFlyoutVisible] = useState(false);
const manageAlertsLinkProps = useLinkProps(
{
app: 'kibana',
hash: 'management/kibana/triggersActions/alerts',
},
{
hrefOnly: true,
}
);

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.logs.createAlertButton"
defaultMessage="Create alert"
/>
</EuiContextMenuItem>,
<EuiContextMenuItem icon="tableOfContents" key="manageLink" {...manageAlertsLinkProps}>
<FormattedMessage
id="xpack.infra.alerting.logs.manageAlerts"
defaultMessage="Manage alerts"
/>
</EuiContextMenuItem>,
];
}, [manageAlertsLinkProps]);

return (
<>
<EuiPopover
button={
<EuiButtonEmpty iconSide={'right'} iconType={'arrowDown'} onClick={openPopover}>
<FormattedMessage id="xpack.infra.alerting.logs.alertsButton" defaultMessage="Alerts" />
</EuiButtonEmpty>
}
isOpen={popoverOpen}
closePopover={closePopover}
>
<EuiContextMenuPanel items={menuItems} />
</EuiPopover>
<AlertFlyout setVisible={setFlyoutVisible} visible={flyoutVisible} />
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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';
import { LOG_DOCUMENT_COUNT_ALERT_TYPE_ID } from '../../../../common/alerting/logs/types';

interface Props {
visible?: boolean;
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
}

export const AlertFlyout = (props: Props) => {
const { triggersActionsUI } = useContext(TriggerActionsContext);
const { services } = useKibana();

return (
<>
{triggersActionsUI && (
<AlertsContextProvider
value={{
metadata: {},
toastNotifications: services.notifications?.toasts,
http: services.http,
docLinks: services.docLinks,
actionTypeRegistry: triggersActionsUI.actionTypeRegistry,
alertTypeRegistry: triggersActionsUI.alertTypeRegistry,
}}
>
<AlertAdd
addFlyoutVisible={props.visible!}
setAddFlyoutVisibility={props.setVisible}
alertTypeId={LOG_DOCUMENT_COUNT_ALERT_TYPE_ID}
canChangeTrigger={false}
consumer={'logs'}
/>
</AlertsContextProvider>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import { IFieldType } from 'src/plugins/data/public';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { IErrorObject } from '../../../../../../triggers_actions_ui/public/types';
import { Criterion } from './criterion';
import {
LogDocumentCountAlertParams,
Criterion as CriterionType,
} from '../../../../../common/alerting/logs/types';

interface Props {
fields: IFieldType[];
criteria?: LogDocumentCountAlertParams['criteria'];
updateCriterion: (idx: number, params: Partial<CriterionType>) => void;
removeCriterion: (idx: number) => void;
errors: IErrorObject;
}

export const Criteria: React.FC<Props> = ({
fields,
criteria,
updateCriterion,
removeCriterion,
errors,
}) => {
if (!criteria) return null;
return (
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow>
{criteria.map((criterion, idx) => {
return (
<Criterion
key={idx}
idx={idx}
fields={fields}
criterion={criterion}
updateCriterion={updateCriterion}
removeCriterion={removeCriterion}
canDelete={criteria.length > 1}
errors={errors[idx.toString()] as IErrorObject}
/>
);
})}
</EuiFlexItem>
</EuiFlexGroup>
);
};
Loading