mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
parent
742cca48a3
commit
6297981519
31 changed files with 1291 additions and 636 deletions
|
@ -16007,7 +16007,6 @@
|
|||
"xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradeCompleteTitle": "クラスターがアップグレードされました",
|
||||
"xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradingDescription": "1 つまたは複数の Elasticsearch ノードに、 Kibana よりも新しいバージョンの Elasticsearch があります。すべてのノードがアップグレードされた後で Kibana をアップグレードしてください。",
|
||||
"xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradingTitle": "クラスターをアップグレード中です",
|
||||
"xpack.uptime.alerts.locationSelectionItem.ariaLabel": "「{location}」の場所選択項目",
|
||||
"xpack.uptime.alerts.message.emptyTitle": "停止状況監視 ID を受信していません。",
|
||||
"xpack.uptime.alerts.message.fullListOverflow": "... とその他 {overflowCount} {pluralizedMonitor}",
|
||||
"xpack.uptime.alerts.message.multipleTitle": "停止状況監視: ",
|
||||
|
@ -16015,9 +16014,6 @@
|
|||
"xpack.uptime.alerts.message.singularTitle": "停止状況監視: ",
|
||||
"xpack.uptime.alerts.monitorStatus": "稼働状況監視ステータス",
|
||||
"xpack.uptime.alerts.monitorStatus.filterBar.ariaLabel": "監視状態アラートのフィルター基準を許可するインプット",
|
||||
"xpack.uptime.alerts.monitorStatus.locationSelection": "場所 {location} を選択します",
|
||||
"xpack.uptime.alerts.monitorStatus.locationSelectionSwitch.ariaLabel": "アラートをトリガーする場所を選択します",
|
||||
"xpack.uptime.alerts.monitorStatus.locationsSelectionExpression.ariaLabel": "ポップオーバーを開いてアラートをトリガーする場所を選択する",
|
||||
"xpack.uptime.alerts.monitorStatus.numTimesExpression.ariaLabel": "ダウンカウントインプットのポップオーバーを開く",
|
||||
"xpack.uptime.alerts.monitorStatus.numTimesField.ariaLabel": "アラートのトリガーに必要な停止回数を入力します",
|
||||
"xpack.uptime.alerts.monitorStatus.timerangeOption.days": "日",
|
||||
|
|
|
@ -16015,7 +16015,6 @@
|
|||
"xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradeCompleteTitle": "您的集群已升级",
|
||||
"xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradingDescription": "一个或多个 Elasticsearch 节点的 Elasticsearch 版本比 Kibana 版本新。所有节点升级后,请升级 Kibana。",
|
||||
"xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradingTitle": "您的集群正在升级",
|
||||
"xpack.uptime.alerts.locationSelectionItem.ariaLabel": "“{location}”的位置选择项",
|
||||
"xpack.uptime.alerts.message.emptyTitle": "未接收到已关闭监测 ID",
|
||||
"xpack.uptime.alerts.message.fullListOverflow": "...以及 {overflowCount} 个其他{pluralizedMonitor}",
|
||||
"xpack.uptime.alerts.message.multipleTitle": "已关闭监测: ",
|
||||
|
@ -16023,9 +16022,6 @@
|
|||
"xpack.uptime.alerts.message.singularTitle": "已关闭监测: ",
|
||||
"xpack.uptime.alerts.monitorStatus": "运行时间监测状态",
|
||||
"xpack.uptime.alerts.monitorStatus.filterBar.ariaLabel": "允许对监测状态告警使用筛选条件的输入",
|
||||
"xpack.uptime.alerts.monitorStatus.locationSelection": "选择位置 {location}",
|
||||
"xpack.uptime.alerts.monitorStatus.locationSelectionSwitch.ariaLabel": "选择告警应触发的位置",
|
||||
"xpack.uptime.alerts.monitorStatus.locationsSelectionExpression.ariaLabel": "打开弹出框以选择告警应触发的位置",
|
||||
"xpack.uptime.alerts.monitorStatus.numTimesExpression.ariaLabel": "打开弹出框以输入已关闭计数",
|
||||
"xpack.uptime.alerts.monitorStatus.numTimesField.ariaLabel": "输入触发告警的已关闭计数",
|
||||
"xpack.uptime.alerts.monitorStatus.timerangeOption.days": "天",
|
||||
|
|
|
@ -104,7 +104,7 @@ export interface AlertTableItem extends Alert {
|
|||
|
||||
export interface AlertTypeModel {
|
||||
id: string;
|
||||
name: string;
|
||||
name: string | JSX.Element;
|
||||
iconClass: string;
|
||||
validate: (alertParams: any) => ValidationResult;
|
||||
alertParamsExpression: React.FunctionComponent<any>;
|
||||
|
|
|
@ -5,12 +5,8 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
selectedLocationsToString,
|
||||
AlertFieldNumber,
|
||||
handleAlertFieldNumberChange,
|
||||
} from '../alert_monitor_status';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { AlertFieldNumber, handleAlertFieldNumberChange } from '../alert_field_number';
|
||||
|
||||
describe('alert monitor status component', () => {
|
||||
describe('handleAlertFieldNumberChange', () => {
|
||||
|
@ -146,34 +142,4 @@ describe('alert monitor status component', () => {
|
|||
expect(mockValueHandler.mock.calls).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectedLocationsToString', () => {
|
||||
it('generates a formatted string for a valid list of options', () => {
|
||||
const locations = [
|
||||
{
|
||||
checked: 'on',
|
||||
label: 'fairbanks',
|
||||
},
|
||||
{
|
||||
checked: 'on',
|
||||
label: 'harrisburg',
|
||||
},
|
||||
{
|
||||
checked: undefined,
|
||||
label: 'orlando',
|
||||
},
|
||||
];
|
||||
expect(selectedLocationsToString(locations)).toEqual('fairbanks, harrisburg');
|
||||
});
|
||||
|
||||
it('generates a formatted string for a single item', () => {
|
||||
expect(selectedLocationsToString([{ checked: 'on', label: 'fairbanks' }])).toEqual(
|
||||
'fairbanks'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns an empty string when no valid options are available', () => {
|
||||
expect(selectedLocationsToString([{ checked: 'off', label: 'harrisburg' }])).toEqual('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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 } from 'react';
|
||||
import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
|
||||
import { useFilterUpdate } from '../../../hooks/use_filter_update';
|
||||
import * as labels from './translations';
|
||||
|
||||
interface Props {
|
||||
newFilters: string[];
|
||||
onNewFilter: (val: string) => void;
|
||||
}
|
||||
|
||||
export const AddFilterButton: React.FC<Props> = ({ newFilters, onNewFilter }) => {
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
|
||||
const currentFilters = useFilterUpdate();
|
||||
|
||||
const getSelectedItems = (fieldName: string) => currentFilters.get(fieldName) || [];
|
||||
|
||||
const onButtonClick = () => {
|
||||
setPopover(!isPopoverOpen);
|
||||
};
|
||||
|
||||
const closePopover = () => {
|
||||
setPopover(false);
|
||||
};
|
||||
|
||||
const items: JSX.Element[] = [];
|
||||
|
||||
const allFilters = [
|
||||
{ id: 'observer.geo.name', label: labels.LOCATION },
|
||||
{ id: 'tags', label: labels.TAG },
|
||||
{ id: 'url.port', label: labels.PORT },
|
||||
{ id: 'monitor.type', label: labels.TYPE },
|
||||
];
|
||||
|
||||
allFilters.forEach(filter => {
|
||||
if (getSelectedItems(filter.id)?.length === 0 && !newFilters.includes(filter.id)) {
|
||||
items.push(
|
||||
<EuiContextMenuItem
|
||||
data-test-subj={'uptimeAlertAddFilter.' + filter.id}
|
||||
key={filter.id}
|
||||
onClick={() => {
|
||||
closePopover();
|
||||
onNewFilter(filter.id);
|
||||
}}
|
||||
>
|
||||
{filter.label}
|
||||
</EuiContextMenuItem>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const button = (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="uptimeCreateAlertAddFilter"
|
||||
disabled={items.length === 0}
|
||||
iconType="plusInCircleFilled"
|
||||
onClick={onButtonClick}
|
||||
>
|
||||
{labels.ADD_FILTER}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
id="singlePanel"
|
||||
button={button}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenuPanel items={items} />
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 } from 'react';
|
||||
import { EuiExpression, EuiPopover } from '@elastic/eui';
|
||||
|
||||
interface AlertExpressionPopoverProps {
|
||||
'aria-label': string;
|
||||
content: React.ReactElement;
|
||||
description: string;
|
||||
'data-test-subj': string;
|
||||
id: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const AlertExpressionPopover: React.FC<AlertExpressionPopoverProps> = ({
|
||||
'aria-label': ariaLabel,
|
||||
content,
|
||||
'data-test-subj': dataTestSubj,
|
||||
description,
|
||||
id,
|
||||
value,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
return (
|
||||
<EuiPopover
|
||||
id={id}
|
||||
anchorPosition="downLeft"
|
||||
button={
|
||||
<EuiExpression
|
||||
aria-label={ariaLabel}
|
||||
color={isOpen ? 'primary' : 'secondary'}
|
||||
data-test-subj={dataTestSubj}
|
||||
description={description}
|
||||
isActive={isOpen}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
value={value}
|
||||
/>
|
||||
}
|
||||
isOpen={isOpen}
|
||||
closePopover={() => setIsOpen(false)}
|
||||
>
|
||||
{content}
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
|
@ -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, { useState } from 'react';
|
||||
import { EuiFieldNumber } from '@elastic/eui';
|
||||
|
||||
interface AlertFieldNumberProps {
|
||||
'aria-label': string;
|
||||
'data-test-subj': string;
|
||||
disabled: boolean;
|
||||
fieldValue: number;
|
||||
setFieldValue: React.Dispatch<React.SetStateAction<number>>;
|
||||
}
|
||||
|
||||
export const handleAlertFieldNumberChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
isInvalid: boolean,
|
||||
setIsInvalid: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
setFieldValue: React.Dispatch<React.SetStateAction<number>>
|
||||
) => {
|
||||
const num = parseInt(e.target.value, 10);
|
||||
if (isNaN(num) || num < 1) {
|
||||
setIsInvalid(true);
|
||||
} else {
|
||||
if (isInvalid) setIsInvalid(false);
|
||||
setFieldValue(num);
|
||||
}
|
||||
};
|
||||
|
||||
export const AlertFieldNumber = ({
|
||||
'aria-label': ariaLabel,
|
||||
'data-test-subj': dataTestSubj,
|
||||
disabled,
|
||||
fieldValue,
|
||||
setFieldValue,
|
||||
}: AlertFieldNumberProps) => {
|
||||
const [isInvalid, setIsInvalid] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<EuiFieldNumber
|
||||
aria-label={ariaLabel}
|
||||
compressed
|
||||
data-test-subj={dataTestSubj}
|
||||
min={1}
|
||||
onChange={e => handleAlertFieldNumberChange(e, isInvalid, setIsInvalid, setFieldValue)}
|
||||
disabled={disabled}
|
||||
value={fieldValue}
|
||||
isInvalid={isInvalid}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -4,123 +4,19 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
EuiExpression,
|
||||
EuiFieldNumber,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPopover,
|
||||
EuiSelectable,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { DataPublicPluginSetup } from 'src/plugins/data/public';
|
||||
import * as labels from './translations';
|
||||
import {
|
||||
DownNoExpressionSelect,
|
||||
TimeExpressionSelect,
|
||||
FiltersExpressionsSelect,
|
||||
} from './monitor_expressions';
|
||||
|
||||
import { AddFilterButton } from './add_filter_btn';
|
||||
import { KueryBar } from '..';
|
||||
|
||||
interface AlertFieldNumberProps {
|
||||
'aria-label': string;
|
||||
'data-test-subj': string;
|
||||
disabled: boolean;
|
||||
fieldValue: number;
|
||||
setFieldValue: React.Dispatch<React.SetStateAction<number>>;
|
||||
}
|
||||
|
||||
export const handleAlertFieldNumberChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
isInvalid: boolean,
|
||||
setIsInvalid: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
setFieldValue: React.Dispatch<React.SetStateAction<number>>
|
||||
) => {
|
||||
const num = parseInt(e.target.value, 10);
|
||||
if (isNaN(num) || num < 1) {
|
||||
setIsInvalid(true);
|
||||
} else {
|
||||
if (isInvalid) setIsInvalid(false);
|
||||
setFieldValue(num);
|
||||
}
|
||||
};
|
||||
|
||||
export const AlertFieldNumber = ({
|
||||
'aria-label': ariaLabel,
|
||||
'data-test-subj': dataTestSubj,
|
||||
disabled,
|
||||
fieldValue,
|
||||
setFieldValue,
|
||||
}: AlertFieldNumberProps) => {
|
||||
const [isInvalid, setIsInvalid] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<EuiFieldNumber
|
||||
aria-label={ariaLabel}
|
||||
compressed
|
||||
data-test-subj={dataTestSubj}
|
||||
min={1}
|
||||
onChange={e => handleAlertFieldNumberChange(e, isInvalid, setIsInvalid, setFieldValue)}
|
||||
disabled={disabled}
|
||||
value={fieldValue}
|
||||
isInvalid={isInvalid}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface AlertExpressionPopoverProps {
|
||||
'aria-label': string;
|
||||
content: React.ReactElement;
|
||||
description: string;
|
||||
'data-test-subj': string;
|
||||
id: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const AlertExpressionPopover: React.FC<AlertExpressionPopoverProps> = ({
|
||||
'aria-label': ariaLabel,
|
||||
content,
|
||||
'data-test-subj': dataTestSubj,
|
||||
description,
|
||||
id,
|
||||
value,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
return (
|
||||
<EuiPopover
|
||||
id={id}
|
||||
anchorPosition="downLeft"
|
||||
button={
|
||||
<EuiExpression
|
||||
aria-label={ariaLabel}
|
||||
color={isOpen ? 'primary' : 'secondary'}
|
||||
data-test-subj={dataTestSubj}
|
||||
description={description}
|
||||
isActive={isOpen}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
value={value}
|
||||
/>
|
||||
}
|
||||
isOpen={isOpen}
|
||||
closePopover={() => setIsOpen(false)}
|
||||
>
|
||||
{content}
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
||||
export const selectedLocationsToString = (selectedLocations: any[]) =>
|
||||
// create a nicely-formatted description string for all `on` locations
|
||||
selectedLocations
|
||||
.filter(({ checked }) => checked === 'on')
|
||||
.map(({ label }) => label)
|
||||
.sort()
|
||||
.reduce((acc, cur) => {
|
||||
if (acc === '') {
|
||||
return cur;
|
||||
}
|
||||
return acc + `, ${cur}`;
|
||||
}, '');
|
||||
|
||||
interface AlertMonitorStatusProps {
|
||||
autocomplete: DataPublicPluginSetup['autocomplete'];
|
||||
enabled: boolean;
|
||||
|
@ -135,101 +31,9 @@ interface AlertMonitorStatusProps {
|
|||
}
|
||||
|
||||
export const AlertMonitorStatusComponent: React.FC<AlertMonitorStatusProps> = props => {
|
||||
const { filters, locations } = props;
|
||||
const [numTimes, setNumTimes] = useState<number>(5);
|
||||
const [numMins, setNumMins] = useState<number>(15);
|
||||
const [allLabels, setAllLabels] = useState<boolean>(true);
|
||||
const { filters, setAlertParams } = props;
|
||||
|
||||
// locations is an array of `Option[]`, but that type doesn't seem to be exported by EUI
|
||||
const [selectedLocations, setSelectedLocations] = useState<any[]>(
|
||||
locations.map(location => ({
|
||||
'aria-label': i18n.translate('xpack.uptime.alerts.locationSelectionItem.ariaLabel', {
|
||||
defaultMessage: 'Location selection item for "{location}"',
|
||||
values: {
|
||||
location,
|
||||
},
|
||||
}),
|
||||
disabled: allLabels,
|
||||
label: location,
|
||||
}))
|
||||
);
|
||||
const [timerangeUnitOptions, setTimerangeUnitOptions] = useState<any[]>([
|
||||
{
|
||||
'aria-label': i18n.translate(
|
||||
'xpack.uptime.alerts.timerangeUnitSelectable.secondsOption.ariaLabel',
|
||||
{
|
||||
defaultMessage: '"Seconds" time range select item',
|
||||
}
|
||||
),
|
||||
'data-test-subj': 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.secondsOption',
|
||||
key: 's',
|
||||
label: i18n.translate('xpack.uptime.alerts.monitorStatus.timerangeOption.seconds', {
|
||||
defaultMessage: 'seconds',
|
||||
}),
|
||||
},
|
||||
{
|
||||
'aria-label': i18n.translate(
|
||||
'xpack.uptime.alerts.timerangeUnitSelectable.minutesOption.ariaLabel',
|
||||
{
|
||||
defaultMessage: '"Minutes" time range select item',
|
||||
}
|
||||
),
|
||||
'data-test-subj': 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.minutesOption',
|
||||
checked: 'on',
|
||||
key: 'm',
|
||||
label: i18n.translate('xpack.uptime.alerts.monitorStatus.timerangeOption.minutes', {
|
||||
defaultMessage: 'minutes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
'aria-label': i18n.translate(
|
||||
'xpack.uptime.alerts.timerangeUnitSelectable.hoursOption.ariaLabel',
|
||||
{
|
||||
defaultMessage: '"Hours" time range select item',
|
||||
}
|
||||
),
|
||||
'data-test-subj': 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.hoursOption',
|
||||
key: 'h',
|
||||
label: i18n.translate('xpack.uptime.alerts.monitorStatus.timerangeOption.hours', {
|
||||
defaultMessage: 'hours',
|
||||
}),
|
||||
},
|
||||
{
|
||||
'aria-label': i18n.translate(
|
||||
'xpack.uptime.alerts.timerangeUnitSelectable.daysOption.ariaLabel',
|
||||
{
|
||||
defaultMessage: '"Days" time range select item',
|
||||
}
|
||||
),
|
||||
'data-test-subj': 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.daysOption',
|
||||
key: 'd',
|
||||
label: i18n.translate('xpack.uptime.alerts.monitorStatus.timerangeOption.days', {
|
||||
defaultMessage: 'days',
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
||||
const { setAlertParams } = props;
|
||||
|
||||
useEffect(() => {
|
||||
setAlertParams('numTimes', numTimes);
|
||||
}, [numTimes, setAlertParams]);
|
||||
|
||||
useEffect(() => {
|
||||
const timerangeUnit = timerangeUnitOptions.find(({ checked }) => checked === 'on')?.key ?? 'm';
|
||||
setAlertParams('timerange', { from: `now-${numMins}${timerangeUnit}`, to: 'now' });
|
||||
}, [numMins, timerangeUnitOptions, setAlertParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (allLabels) {
|
||||
setAlertParams('locations', []);
|
||||
} else {
|
||||
setAlertParams(
|
||||
'locations',
|
||||
selectedLocations.filter(l => l.checked === 'on').map(l => l.label)
|
||||
);
|
||||
}
|
||||
}, [selectedLocations, setAlertParams, allLabels]);
|
||||
const [newFilters, setNewFilters] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setAlertParams('filters', filters);
|
||||
|
@ -239,207 +43,41 @@ export const AlertMonitorStatusComponent: React.FC<AlertMonitorStatusProps> = pr
|
|||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<KueryBar
|
||||
aria-label={i18n.translate('xpack.uptime.alerts.monitorStatus.filterBar.ariaLabel', {
|
||||
defaultMessage: 'Input that allows filtering criteria for the monitor status alert',
|
||||
})}
|
||||
aria-label={labels.ALERT_KUERY_BAR_ARIA}
|
||||
autocomplete={props.autocomplete}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.filterBar"
|
||||
/>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
<AlertExpressionPopover
|
||||
aria-label={i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.numTimesExpression.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Open the popover for down count input',
|
||||
|
||||
<DownNoExpressionSelect filters={filters} setAlertParams={setAlertParams} />
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
|
||||
<TimeExpressionSelect setAlertParams={setAlertParams} />
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
|
||||
<FiltersExpressionsSelect
|
||||
setAlertParams={setAlertParams}
|
||||
newFilters={newFilters}
|
||||
onRemoveFilter={removeFiler => {
|
||||
if (newFilters.includes(removeFiler)) {
|
||||
setNewFilters(newFilters.filter(item => item !== removeFiler));
|
||||
}
|
||||
)}
|
||||
content={
|
||||
<AlertFieldNumber
|
||||
aria-label={i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.numTimesField.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Enter number of down counts required to trigger the alert',
|
||||
}
|
||||
)}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.numTimesField"
|
||||
disabled={false}
|
||||
fieldValue={numTimes}
|
||||
setFieldValue={setNumTimes}
|
||||
/>
|
||||
}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.numTimesExpression"
|
||||
description={
|
||||
filters
|
||||
? i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.numTimesExpression.matchingMonitors.description',
|
||||
{
|
||||
defaultMessage: 'matching monitors are down >',
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.numTimesExpression.anyMonitors.description',
|
||||
{
|
||||
defaultMessage: 'any monitor is down >',
|
||||
}
|
||||
)
|
||||
}
|
||||
id="ping-count"
|
||||
value={`${numTimes} times`}
|
||||
}}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AlertExpressionPopover
|
||||
aria-label={i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.timerangeValueExpression.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Open the popover for time range value field',
|
||||
}
|
||||
)}
|
||||
content={
|
||||
<AlertFieldNumber
|
||||
aria-label={i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.timerangeValueField.ariaLabel',
|
||||
{
|
||||
defaultMessage: `Enter the number of time units for the alert's range`,
|
||||
}
|
||||
)}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeValueField"
|
||||
disabled={false}
|
||||
fieldValue={numMins}
|
||||
setFieldValue={setNumMins}
|
||||
/>
|
||||
}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeValueExpression"
|
||||
description="within"
|
||||
id="timerange"
|
||||
value={`last ${numMins}`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<AlertExpressionPopover
|
||||
aria-label={i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.timerangeUnitExpression.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Open the popover for time range unit select field',
|
||||
}
|
||||
)}
|
||||
content={
|
||||
<>
|
||||
<EuiTitle size="xxs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.alerts.monitorStatus.timerangeSelectionHeader"
|
||||
defaultMessage="Select time range unit"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSelectable
|
||||
aria-label={i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable',
|
||||
{
|
||||
defaultMessage: 'Selectable field for the time range units alerts should use',
|
||||
}
|
||||
)}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable"
|
||||
options={timerangeUnitOptions}
|
||||
onChange={newOptions => {
|
||||
if (newOptions.reduce((acc, { checked }) => acc || checked === 'on', false)) {
|
||||
setTimerangeUnitOptions(newOptions);
|
||||
}
|
||||
}}
|
||||
singleSelection={true}
|
||||
listProps={{
|
||||
showIcons: true,
|
||||
}}
|
||||
>
|
||||
{list => list}
|
||||
</EuiSelectable>
|
||||
</>
|
||||
}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeUnitExpression"
|
||||
description=""
|
||||
id="timerange-unit"
|
||||
value={
|
||||
timerangeUnitOptions.find(({ checked }) => checked === 'on')?.label.toLowerCase() ??
|
||||
''
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xs" />
|
||||
{selectedLocations.length === 0 && (
|
||||
<EuiExpression
|
||||
color="secondary"
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.locationsEmpty"
|
||||
description="in"
|
||||
isActive={false}
|
||||
value="all locations"
|
||||
/>
|
||||
)}
|
||||
{selectedLocations.length > 0 && (
|
||||
<AlertExpressionPopover
|
||||
aria-label={i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.locationsSelectionExpression.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Open the popover to select locations the alert should trigger',
|
||||
}
|
||||
)}
|
||||
content={
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiSwitch
|
||||
aria-label={i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.locationSelectionSwitch.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Select the locations the alert should trigger',
|
||||
}
|
||||
)}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.locationsSelectionSwitch"
|
||||
label="Check all locations"
|
||||
checked={allLabels}
|
||||
onChange={() => {
|
||||
setAllLabels(!allLabels);
|
||||
setSelectedLocations(
|
||||
selectedLocations.map((l: any) => ({
|
||||
'aria-label': i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.locationSelection',
|
||||
{
|
||||
defaultMessage: 'Select the location {location}',
|
||||
values: {
|
||||
location: l,
|
||||
},
|
||||
}
|
||||
),
|
||||
...l,
|
||||
'data-test-subj': `xpack.uptime.alerts.monitorStatus.locationSelection.${l.label}LocationOption`,
|
||||
disabled: !allLabels,
|
||||
}))
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiSelectable
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.locationsSelectionSelectable"
|
||||
options={selectedLocations}
|
||||
onChange={e => setSelectedLocations(e)}
|
||||
>
|
||||
{location => location}
|
||||
</EuiSelectable>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.locationsSelectionExpression"
|
||||
description="from"
|
||||
id="locations"
|
||||
value={
|
||||
selectedLocations.length === 0 || allLabels
|
||||
? 'any location'
|
||||
: selectedLocationsToString(selectedLocations)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<AddFilterButton
|
||||
newFilters={newFilters}
|
||||
onNewFilter={newFilter => {
|
||||
setNewFilters([...newFilters, newFilter]);
|
||||
}}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DownNoExpressionSelect component should renders against props 1`] = `
|
||||
<div
|
||||
class="euiPopover euiPopover--anchorDownLeft"
|
||||
id="ping-count"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Open the popover for down count input"
|
||||
class="euiExpression euiExpression-isClickable euiExpression-isUppercase euiExpression--secondary"
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.numTimesExpression"
|
||||
>
|
||||
<span
|
||||
class="euiExpression__description"
|
||||
>
|
||||
matching monitors are down >
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="euiExpression__value"
|
||||
>
|
||||
5 times
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DownNoExpressionSelect component should shallow renders against props 1`] = `
|
||||
<AlertExpressionPopover
|
||||
aria-label="Open the popover for down count input"
|
||||
content={
|
||||
<AlertFieldNumber
|
||||
aria-label="Enter number of down counts required to trigger the alert"
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.numTimesField"
|
||||
disabled={false}
|
||||
fieldValue={5}
|
||||
setFieldValue={[Function]}
|
||||
/>
|
||||
}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.numTimesExpression"
|
||||
description="matching monitors are down >"
|
||||
id="ping-count"
|
||||
value="5 times"
|
||||
/>
|
||||
`;
|
|
@ -0,0 +1,160 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TimeExpressionSelect component should renders against props 1`] = `
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
class="euiPopover euiPopover--anchorDownLeft"
|
||||
id="timerange"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Open the popover for time range value field"
|
||||
class="euiExpression euiExpression-isClickable euiExpression-isUppercase euiExpression--secondary"
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeValueExpression"
|
||||
>
|
||||
<span
|
||||
class="euiExpression__description"
|
||||
>
|
||||
within
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="euiExpression__value"
|
||||
>
|
||||
last 15
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
class="euiPopover euiPopover--anchorDownLeft"
|
||||
id="timerange-unit"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Open the popover for time range unit select field"
|
||||
class="euiExpression euiExpression-isClickable euiExpression-isUppercase euiExpression--secondary"
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeUnitExpression"
|
||||
>
|
||||
<span
|
||||
class="euiExpression__description"
|
||||
/>
|
||||
|
||||
<span
|
||||
class="euiExpression__value"
|
||||
>
|
||||
minutes
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TimeExpressionSelect component should shallow renders against props 1`] = `
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<AlertExpressionPopover
|
||||
aria-label="Open the popover for time range value field"
|
||||
content={
|
||||
<AlertFieldNumber
|
||||
aria-label="Enter the number of time units for the alert's range"
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeValueField"
|
||||
disabled={false}
|
||||
fieldValue={15}
|
||||
setFieldValue={[Function]}
|
||||
/>
|
||||
}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeValueExpression"
|
||||
description="within"
|
||||
id="timerange"
|
||||
value="last 15"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<AlertExpressionPopover
|
||||
aria-label="Open the popover for time range unit select field"
|
||||
content={
|
||||
<React.Fragment>
|
||||
<EuiTitle
|
||||
size="xxs"
|
||||
>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
defaultMessage="Select time range unit"
|
||||
id="xpack.uptime.alerts.monitorStatus.timerangeSelectionHeader"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSelectable
|
||||
aria-label="Selectable field for the time range units alerts should use"
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable"
|
||||
listProps={
|
||||
Object {
|
||||
"showIcons": true,
|
||||
}
|
||||
}
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"aria-label": "\\"Seconds\\" time range select item",
|
||||
"data-test-subj": "xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.secondsOption",
|
||||
"key": "s",
|
||||
"label": "seconds",
|
||||
},
|
||||
Object {
|
||||
"aria-label": "\\"Minutes\\" time range select item",
|
||||
"checked": "on",
|
||||
"data-test-subj": "xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.minutesOption",
|
||||
"key": "m",
|
||||
"label": "minutes",
|
||||
},
|
||||
Object {
|
||||
"aria-label": "\\"Hours\\" time range select item",
|
||||
"data-test-subj": "xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.hoursOption",
|
||||
"key": "h",
|
||||
"label": "hours",
|
||||
},
|
||||
Object {
|
||||
"aria-label": "\\"Days\\" time range select item",
|
||||
"data-test-subj": "xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.daysOption",
|
||||
"key": "d",
|
||||
"label": "days",
|
||||
},
|
||||
]
|
||||
}
|
||||
searchable={false}
|
||||
singleSelection={true}
|
||||
>
|
||||
[Function]
|
||||
</EuiSelectable>
|
||||
</React.Fragment>
|
||||
}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeUnitExpression"
|
||||
description=""
|
||||
id="timerange-unit"
|
||||
value="minutes"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
`;
|
|
@ -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 React from 'react';
|
||||
import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { DownNoExpressionSelect } from '../down_number_select';
|
||||
|
||||
describe('DownNoExpressionSelect component', () => {
|
||||
const filters =
|
||||
'"{"bool":{"filter":[{"bool":{"should":[{"match":{"observer.geo.name":"US-West"}}],"minimum_should_match":1}},' +
|
||||
'{"bool":{"should":[{"match":{"url.port":443}}],"minimum_should_match":1}}]}}"';
|
||||
|
||||
it('should shallow renders against props', function() {
|
||||
const component = shallowWithIntl(
|
||||
<DownNoExpressionSelect filters={filters} setAlertParams={jest.fn()} />
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should renders against props', function() {
|
||||
const component = renderWithIntl(
|
||||
<DownNoExpressionSelect filters={filters} setAlertParams={jest.fn()} />
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -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 React from 'react';
|
||||
import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { TimeExpressionSelect } from '../time_expression_select';
|
||||
|
||||
describe('TimeExpressionSelect component', () => {
|
||||
it('should shallow renders against props', function() {
|
||||
const component = shallowWithIntl(<TimeExpressionSelect setAlertParams={jest.fn()} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should renders against props', function() {
|
||||
const component = renderWithIntl(<TimeExpressionSelect setAlertParams={jest.fn()} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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, { useEffect, useState } from 'react';
|
||||
import { AlertExpressionPopover } from '../alert_expression_popover';
|
||||
import * as labels from '../translations';
|
||||
import { AlertFieldNumber } from '../alert_field_number';
|
||||
|
||||
interface Props {
|
||||
setAlertParams: (key: string, value: any) => void;
|
||||
filters: string;
|
||||
}
|
||||
|
||||
export const DownNoExpressionSelect: React.FC<Props> = ({ filters, setAlertParams }) => {
|
||||
const [numTimes, setNumTimes] = useState<number>(5);
|
||||
|
||||
useEffect(() => {
|
||||
setAlertParams('numTimes', numTimes);
|
||||
}, [numTimes, setAlertParams]);
|
||||
|
||||
return (
|
||||
<AlertExpressionPopover
|
||||
aria-label={labels.OPEN_THE_POPOVER_DOWN_COUNT}
|
||||
content={
|
||||
<AlertFieldNumber
|
||||
aria-label={labels.ENTER_NUMBER_OF_DOWN_COUNTS}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.numTimesField"
|
||||
disabled={false}
|
||||
fieldValue={numTimes}
|
||||
setFieldValue={setNumTimes}
|
||||
/>
|
||||
}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.numTimesExpression"
|
||||
description={filters ? labels.MATCHING_MONITORS_DOWN : labels.ANY_MONITOR_DOWN}
|
||||
id="ping-count"
|
||||
value={`${numTimes} times`}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* 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, { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { EuiButtonIcon, EuiExpression, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { FilterPopover } from '../../filter_group/filter_popover';
|
||||
import { overviewFiltersSelector } from '../../../../state/selectors';
|
||||
import { useFilterUpdate } from '../../../../hooks/use_filter_update';
|
||||
import { filterLabels } from '../../filter_group/translations';
|
||||
import { alertFilterLabels } from './translations';
|
||||
|
||||
interface Props {
|
||||
newFilters: string[];
|
||||
onRemoveFilter: (val: string) => void;
|
||||
setAlertParams: (key: string, value: any) => void;
|
||||
}
|
||||
|
||||
export const FiltersExpressionsSelect: React.FC<Props> = ({
|
||||
setAlertParams,
|
||||
newFilters,
|
||||
onRemoveFilter,
|
||||
}) => {
|
||||
const { tags, ports, schemes, locations } = useSelector(overviewFiltersSelector);
|
||||
|
||||
const [updatedFieldValues, setUpdatedFieldValues] = useState<{
|
||||
fieldName: string;
|
||||
values: string[];
|
||||
}>({ fieldName: '', values: [] });
|
||||
|
||||
const currentFilters = useFilterUpdate(updatedFieldValues.fieldName, updatedFieldValues.values);
|
||||
|
||||
useEffect(() => {
|
||||
if (updatedFieldValues.fieldName === 'observer.geo.name') {
|
||||
setAlertParams('locations', updatedFieldValues.values);
|
||||
}
|
||||
}, [setAlertParams, updatedFieldValues]);
|
||||
|
||||
useEffect(() => {
|
||||
setAlertParams('locations', []);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const selectedTags = currentFilters.get('tags');
|
||||
const selectedPorts = currentFilters.get('url.port');
|
||||
const selectedScheme = currentFilters.get('monitor.type');
|
||||
const selectedLocation = currentFilters.get('observer.geo.name');
|
||||
|
||||
const getSelectedItems = (fieldName: string) => currentFilters.get(fieldName) || [];
|
||||
|
||||
const onFilterFieldChange = (fieldName: string, values: string[]) => {
|
||||
setUpdatedFieldValues({ fieldName, values });
|
||||
};
|
||||
|
||||
const monitorFilters = [
|
||||
{
|
||||
onFilterFieldChange,
|
||||
loading: false,
|
||||
fieldName: 'url.port',
|
||||
id: 'filter_port',
|
||||
disabled: ports?.length === 0,
|
||||
items: ports?.map((p: number) => p.toString()) ?? [],
|
||||
selectedItems: getSelectedItems('url.port'),
|
||||
title: filterLabels.PORT,
|
||||
description: selectedPorts ? alertFilterLabels.USING_PORT : alertFilterLabels.USING,
|
||||
value: selectedPorts?.join(',') ?? alertFilterLabels.ANY_PORT,
|
||||
},
|
||||
{
|
||||
onFilterFieldChange,
|
||||
loading: false,
|
||||
fieldName: 'tags',
|
||||
id: 'filter_tags',
|
||||
disabled: tags?.length === 0,
|
||||
items: tags ?? [],
|
||||
selectedItems: getSelectedItems('tags'),
|
||||
title: filterLabels.TAGS,
|
||||
description: selectedTags ? alertFilterLabels.WITH_TAG : alertFilterLabels.WITH,
|
||||
value: selectedTags?.join(',') ?? alertFilterLabels.ANY_TAG,
|
||||
},
|
||||
{
|
||||
onFilterFieldChange,
|
||||
loading: false,
|
||||
fieldName: 'monitor.type',
|
||||
id: 'filter_scheme',
|
||||
disabled: schemes?.length === 0,
|
||||
items: schemes ?? [],
|
||||
selectedItems: getSelectedItems('monitor.type'),
|
||||
title: filterLabels.SCHEME,
|
||||
description: selectedScheme ? alertFilterLabels.OF_TYPE : alertFilterLabels.OF,
|
||||
value: selectedScheme?.join(',') ?? alertFilterLabels.ANY_TYPE,
|
||||
},
|
||||
{
|
||||
onFilterFieldChange,
|
||||
loading: false,
|
||||
fieldName: 'observer.geo.name',
|
||||
id: 'filter_location',
|
||||
disabled: locations?.length === 0,
|
||||
items: locations ?? [],
|
||||
selectedItems: getSelectedItems('observer.geo.name'),
|
||||
title: filterLabels.SCHEME,
|
||||
description: selectedLocation ? alertFilterLabels.FROM_LOCATION : alertFilterLabels.FROM,
|
||||
value: selectedLocation?.join(',') ?? alertFilterLabels.ANY_LOCATION,
|
||||
},
|
||||
];
|
||||
|
||||
const [isOpen, setIsOpen] = useState<any>({
|
||||
filter_port: false,
|
||||
filter_tags: false,
|
||||
filter_scheme: false,
|
||||
filter_location: false,
|
||||
});
|
||||
|
||||
const filtersToDisplay = monitorFilters.filter(
|
||||
curr => curr.selectedItems.length > 0 || newFilters?.includes(curr.fieldName)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{filtersToDisplay.map(({ description, value, ...item }) => (
|
||||
<EuiFlexGroup key={item.id}>
|
||||
<EuiFlexItem>
|
||||
<FilterPopover
|
||||
{...item}
|
||||
btnContent={
|
||||
<EuiExpression
|
||||
aria-label={'ariaLabel'}
|
||||
color={'secondary'}
|
||||
data-test-subj={'uptimeCreateStatusAlert.' + item.id}
|
||||
description={description}
|
||||
value={value}
|
||||
onClick={() => setIsOpen({ ...isOpen, [item.id]: !isOpen[item.id] })}
|
||||
/>
|
||||
}
|
||||
forceOpen={isOpen[item.id]}
|
||||
setForceOpen={() => {
|
||||
setIsOpen({ ...isOpen, [item.id]: !isOpen[item.id] });
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
aria-label="Remove filter"
|
||||
iconType="trash"
|
||||
color="danger"
|
||||
onClick={() => {
|
||||
onRemoveFilter(item.fieldName);
|
||||
onFilterFieldChange(item.fieldName, []);
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
</EuiFlexGroup>
|
||||
))}
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { DownNoExpressionSelect } from './down_number_select';
|
||||
export { FiltersExpressionsSelect } from './filters_expression_select';
|
||||
export { TimeExpressionSelect } from './time_expression_select';
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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, { useEffect, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSelectable, EuiTitle } from '@elastic/eui';
|
||||
import { AlertExpressionPopover } from '../alert_expression_popover';
|
||||
import * as labels from '../translations';
|
||||
import { AlertFieldNumber } from '../alert_field_number';
|
||||
import { timeExpLabels } from './translations';
|
||||
|
||||
interface Props {
|
||||
setAlertParams: (key: string, value: any) => void;
|
||||
}
|
||||
|
||||
const TimeRangeOptions = [
|
||||
{
|
||||
'aria-label': labels.SECONDS_TIME_RANGE,
|
||||
'data-test-subj': 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.secondsOption',
|
||||
key: 's',
|
||||
label: labels.SECONDS,
|
||||
},
|
||||
{
|
||||
'aria-label': labels.MINUTES_TIME_RANGE,
|
||||
'data-test-subj': 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.minutesOption',
|
||||
checked: 'on',
|
||||
key: 'm',
|
||||
label: labels.MINUTES,
|
||||
},
|
||||
{
|
||||
'aria-label': labels.HOURS_TIME_RANGE,
|
||||
'data-test-subj': 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.hoursOption',
|
||||
key: 'h',
|
||||
label: labels.HOURS,
|
||||
},
|
||||
{
|
||||
'aria-label': labels.DAYS_TIME_RANGE,
|
||||
'data-test-subj': 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.daysOption',
|
||||
key: 'd',
|
||||
label: labels.DAYS,
|
||||
},
|
||||
];
|
||||
|
||||
export const TimeExpressionSelect: React.FC<Props> = ({ setAlertParams }) => {
|
||||
const [numUnits, setNumUnits] = useState<number>(15);
|
||||
|
||||
const [timerangeUnitOptions, setTimerangeUnitOptions] = useState<any[]>(TimeRangeOptions);
|
||||
|
||||
useEffect(() => {
|
||||
const timerangeUnit = timerangeUnitOptions.find(({ checked }) => checked === 'on')?.key ?? 'm';
|
||||
setAlertParams('timerange', { from: `now-${numUnits}${timerangeUnit}`, to: 'now' });
|
||||
}, [numUnits, timerangeUnitOptions, setAlertParams]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AlertExpressionPopover
|
||||
aria-label={labels.OPEN_THE_POPOVER_TIME_RANGE_VALUE}
|
||||
content={
|
||||
<AlertFieldNumber
|
||||
aria-label={labels.ENTER_NUMBER_OF_TIME_UNITS}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeValueField"
|
||||
disabled={false}
|
||||
fieldValue={numUnits}
|
||||
setFieldValue={setNumUnits}
|
||||
/>
|
||||
}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeValueExpression"
|
||||
description="within"
|
||||
id="timerange"
|
||||
value={`last ${numUnits}`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<AlertExpressionPopover
|
||||
aria-label={timeExpLabels.OPEN_TIME_POPOVER}
|
||||
content={
|
||||
<>
|
||||
<EuiTitle size="xxs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.alerts.monitorStatus.timerangeSelectionHeader"
|
||||
defaultMessage="Select time range unit"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSelectable
|
||||
aria-label={timeExpLabels.SELECT_TIME_RANGE_ARIA}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable"
|
||||
options={timerangeUnitOptions}
|
||||
onChange={newOptions => {
|
||||
if (newOptions.reduce((acc, { checked }) => acc || checked === 'on', false)) {
|
||||
setTimerangeUnitOptions(newOptions);
|
||||
}
|
||||
}}
|
||||
singleSelection={true}
|
||||
listProps={{
|
||||
showIcons: true,
|
||||
}}
|
||||
>
|
||||
{list => list}
|
||||
</EuiSelectable>
|
||||
</>
|
||||
}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeUnitExpression"
|
||||
description=""
|
||||
id="timerange-unit"
|
||||
value={
|
||||
timerangeUnitOptions.find(({ checked }) => checked === 'on')?.label.toLowerCase() ?? ''
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 alertFilterLabels = {
|
||||
USING: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.using', {
|
||||
defaultMessage: 'Using',
|
||||
}),
|
||||
|
||||
USING_PORT: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.usingPort', {
|
||||
defaultMessage: 'Using port',
|
||||
}),
|
||||
|
||||
ANY_PORT: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.anyPort', {
|
||||
defaultMessage: 'any port',
|
||||
}),
|
||||
|
||||
WITH: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.with', {
|
||||
defaultMessage: 'Using',
|
||||
}),
|
||||
|
||||
WITH_TAG: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.withTag', {
|
||||
defaultMessage: 'With tag',
|
||||
}),
|
||||
|
||||
ANY_TAG: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.anyTag', {
|
||||
defaultMessage: 'any tag',
|
||||
}),
|
||||
|
||||
OF: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.of', {
|
||||
defaultMessage: 'Of',
|
||||
}),
|
||||
|
||||
OF_TYPE: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.ofType', {
|
||||
defaultMessage: 'Of type',
|
||||
}),
|
||||
|
||||
ANY_TYPE: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.anyType', {
|
||||
defaultMessage: 'any type',
|
||||
}),
|
||||
|
||||
FROM: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.from', {
|
||||
defaultMessage: 'From',
|
||||
}),
|
||||
|
||||
FROM_LOCATION: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.fromLocation', {
|
||||
defaultMessage: 'From location',
|
||||
}),
|
||||
|
||||
ANY_LOCATION: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.anyLocation', {
|
||||
defaultMessage: 'any location',
|
||||
}),
|
||||
};
|
||||
|
||||
export const timeExpLabels = {
|
||||
OPEN_TIME_POPOVER: i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.timerangeUnitExpression.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Open the popover for time range unit select field',
|
||||
}
|
||||
),
|
||||
SELECT_TIME_RANGE_ARIA: i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable',
|
||||
{
|
||||
defaultMessage: 'Selectable field for the time range units alerts should use',
|
||||
}
|
||||
),
|
||||
};
|
|
@ -6,6 +6,119 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const SECONDS_TIME_RANGE = i18n.translate(
|
||||
'xpack.uptime.alerts.timerangeUnitSelectable.secondsOption.ariaLabel',
|
||||
{
|
||||
defaultMessage: '"Seconds" time range select item',
|
||||
}
|
||||
);
|
||||
|
||||
export const SECONDS = i18n.translate('xpack.uptime.alerts.monitorStatus.timerangeOption.seconds', {
|
||||
defaultMessage: 'seconds',
|
||||
});
|
||||
|
||||
export const MINUTES_TIME_RANGE = i18n.translate(
|
||||
'xpack.uptime.alerts.timerangeUnitSelectable.minutesOption.ariaLabel',
|
||||
{
|
||||
defaultMessage: '"Minutes" time range select item',
|
||||
}
|
||||
);
|
||||
|
||||
export const MINUTES = i18n.translate('xpack.uptime.alerts.monitorStatus.timerangeOption.minutes', {
|
||||
defaultMessage: 'minutes',
|
||||
});
|
||||
|
||||
export const HOURS_TIME_RANGE = i18n.translate(
|
||||
'xpack.uptime.alerts.timerangeUnitSelectable.hoursOption.ariaLabel',
|
||||
{
|
||||
defaultMessage: '"Hours" time range select item',
|
||||
}
|
||||
);
|
||||
|
||||
export const HOURS = i18n.translate('xpack.uptime.alerts.monitorStatus.timerangeOption.hours', {
|
||||
defaultMessage: 'hours',
|
||||
});
|
||||
|
||||
export const DAYS_TIME_RANGE = i18n.translate(
|
||||
'xpack.uptime.alerts.timerangeUnitSelectable.daysOption.ariaLabel',
|
||||
{
|
||||
defaultMessage: '"Days" time range select item',
|
||||
}
|
||||
);
|
||||
|
||||
export const DAYS = i18n.translate('xpack.uptime.alerts.monitorStatus.timerangeOption.days', {
|
||||
defaultMessage: 'days',
|
||||
});
|
||||
|
||||
export const ALERT_KUERY_BAR_ARIA = i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.filterBar.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Input that allows filtering criteria for the monitor status alert',
|
||||
}
|
||||
);
|
||||
|
||||
export const OPEN_THE_POPOVER_DOWN_COUNT = i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.numTimesExpression.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Open the popover for down count input',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENTER_NUMBER_OF_DOWN_COUNTS = i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.numTimesField.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Enter number of down counts required to trigger the alert',
|
||||
}
|
||||
);
|
||||
|
||||
export const MATCHING_MONITORS_DOWN = i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.numTimesExpression.matchingMonitors.description',
|
||||
{
|
||||
defaultMessage: 'matching monitors are down >',
|
||||
}
|
||||
);
|
||||
|
||||
export const ANY_MONITOR_DOWN = i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.numTimesExpression.anyMonitors.description',
|
||||
{
|
||||
defaultMessage: 'any monitor is down >',
|
||||
}
|
||||
);
|
||||
|
||||
export const OPEN_THE_POPOVER_TIME_RANGE_VALUE = i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.timerangeValueExpression.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Open the popover for time range value field',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENTER_NUMBER_OF_TIME_UNITS = i18n.translate(
|
||||
'xpack.uptime.alerts.monitorStatus.timerangeValueField.ariaLabel',
|
||||
{
|
||||
defaultMessage: `Enter the number of time units for the alert's range`,
|
||||
}
|
||||
);
|
||||
|
||||
export const ADD_FILTER = i18n.translate('xpack.uptime.alerts.monitorStatus.addFilter', {
|
||||
defaultMessage: `Add filter`,
|
||||
});
|
||||
|
||||
export const LOCATION = i18n.translate('xpack.uptime.alerts.monitorStatus.addFilter.location', {
|
||||
defaultMessage: `Location`,
|
||||
});
|
||||
|
||||
export const TAG = i18n.translate('xpack.uptime.alerts.monitorStatus.addFilter.tag', {
|
||||
defaultMessage: `Tag`,
|
||||
});
|
||||
|
||||
export const PORT = i18n.translate('xpack.uptime.alerts.monitorStatus.addFilter.port', {
|
||||
defaultMessage: `Port`,
|
||||
});
|
||||
|
||||
export const TYPE = i18n.translate('xpack.uptime.alerts.monitorStatus.addFilter.type', {
|
||||
defaultMessage: `Type`,
|
||||
});
|
||||
|
||||
export const TlsTranslations = {
|
||||
criteriaAriaLabel: i18n.translate('xpack.uptime.alerts.tls.criteriaExpression.ariaLabel', {
|
||||
defaultMessage:
|
||||
|
|
|
@ -21,7 +21,7 @@ exports[`FilterPopover component does not show item list when loading 1`] = `
|
|||
ownFocus={true}
|
||||
panelPaddingSize="m"
|
||||
withTitle={true}
|
||||
zIndex={1000}
|
||||
zIndex={10000}
|
||||
>
|
||||
<EuiPopoverTitle>
|
||||
<EuiFieldSearch
|
||||
|
@ -59,7 +59,7 @@ exports[`FilterPopover component renders without errors for valid props 1`] = `
|
|||
ownFocus={true}
|
||||
panelPaddingSize="m"
|
||||
withTitle={true}
|
||||
zIndex={1000}
|
||||
zIndex={10000}
|
||||
>
|
||||
<EuiPopoverTitle>
|
||||
<EuiFieldSearch
|
||||
|
|
|
@ -4,57 +4,38 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { EuiFilterGroup } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FilterPopoverProps, FilterPopover } from './filter_popover';
|
||||
import { FilterStatusButton } from './filter_status_button';
|
||||
import { OverviewFilters } from '../../../../common/runtime_types/overview_filters';
|
||||
import { filterLabels } from './translations';
|
||||
import { useFilterUpdate } from '../../../hooks/use_filter_update';
|
||||
|
||||
interface PresentationalComponentProps {
|
||||
loading: boolean;
|
||||
overviewFilters: OverviewFilters;
|
||||
currentFilter: string;
|
||||
onFilterUpdate: (filtersKuery: string) => void;
|
||||
}
|
||||
|
||||
export const FilterGroupComponent: React.FC<PresentationalComponentProps> = ({
|
||||
currentFilter,
|
||||
overviewFilters,
|
||||
loading,
|
||||
onFilterUpdate,
|
||||
}) => {
|
||||
const { locations, ports, schemes, tags } = overviewFilters;
|
||||
|
||||
let filterKueries: Map<string, string[]>;
|
||||
try {
|
||||
filterKueries = new Map<string, string[]>(JSON.parse(currentFilter));
|
||||
} catch {
|
||||
filterKueries = new Map<string, string[]>();
|
||||
}
|
||||
const [updatedFieldValues, setUpdatedFieldValues] = useState<{
|
||||
fieldName: string;
|
||||
values: string[];
|
||||
}>({ fieldName: '', values: [] });
|
||||
|
||||
const currentFilters = useFilterUpdate(updatedFieldValues.fieldName, updatedFieldValues.values);
|
||||
|
||||
/**
|
||||
* Handle an added or removed value to filter against for an uptime field.
|
||||
* @param fieldName the name of the field to filter against
|
||||
* @param values the list of values to use when filter a field
|
||||
*/
|
||||
const onFilterFieldChange = (fieldName: string, values: string[]) => {
|
||||
// add new term to filter map, toggle it off if already present
|
||||
const updatedFilterMap = new Map<string, string[]>(filterKueries);
|
||||
updatedFilterMap.set(fieldName, values);
|
||||
Array.from(updatedFilterMap.keys()).forEach(key => {
|
||||
const value = updatedFilterMap.get(key);
|
||||
if (value && value.length === 0) {
|
||||
updatedFilterMap.delete(key);
|
||||
}
|
||||
});
|
||||
|
||||
// store the new set of filters
|
||||
const persistedFilters = Array.from(updatedFilterMap);
|
||||
onFilterUpdate(persistedFilters.length === 0 ? '' : JSON.stringify(persistedFilters));
|
||||
setUpdatedFieldValues({ fieldName, values });
|
||||
};
|
||||
|
||||
const getSelectedItems = (fieldName: string) => filterKueries.get(fieldName) || [];
|
||||
const getSelectedItems = (fieldName: string) => currentFilters.get(fieldName) || [];
|
||||
|
||||
const filterPopoverProps: FilterPopoverProps[] = [
|
||||
{
|
||||
|
@ -64,9 +45,7 @@ export const FilterGroupComponent: React.FC<PresentationalComponentProps> = ({
|
|||
id: 'location',
|
||||
items: locations,
|
||||
selectedItems: getSelectedItems('observer.geo.name'),
|
||||
title: i18n.translate('xpack.uptime.filterBar.options.location.name', {
|
||||
defaultMessage: 'Location',
|
||||
}),
|
||||
title: filterLabels.LOCATION,
|
||||
},
|
||||
{
|
||||
loading,
|
||||
|
@ -76,7 +55,7 @@ export const FilterGroupComponent: React.FC<PresentationalComponentProps> = ({
|
|||
disabled: ports.length === 0,
|
||||
items: ports.map((p: number) => p.toString()),
|
||||
selectedItems: getSelectedItems('url.port'),
|
||||
title: i18n.translate('xpack.uptime.filterBar.options.portLabel', { defaultMessage: 'Port' }),
|
||||
title: filterLabels.PORT,
|
||||
},
|
||||
{
|
||||
loading,
|
||||
|
@ -86,9 +65,7 @@ export const FilterGroupComponent: React.FC<PresentationalComponentProps> = ({
|
|||
disabled: schemes.length === 0,
|
||||
items: schemes,
|
||||
selectedItems: getSelectedItems('monitor.type'),
|
||||
title: i18n.translate('xpack.uptime.filterBar.options.schemeLabel', {
|
||||
defaultMessage: 'Scheme',
|
||||
}),
|
||||
title: filterLabels.SCHEME,
|
||||
},
|
||||
{
|
||||
loading,
|
||||
|
@ -98,9 +75,7 @@ export const FilterGroupComponent: React.FC<PresentationalComponentProps> = ({
|
|||
disabled: tags.length === 0,
|
||||
items: tags,
|
||||
selectedItems: getSelectedItems('tags'),
|
||||
title: i18n.translate('xpack.uptime.filterBar.options.tagsLabel', {
|
||||
defaultMessage: 'Tags',
|
||||
}),
|
||||
title: filterLabels.TAGS,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -5,56 +5,41 @@
|
|||
*/
|
||||
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useUrlParams } from '../../../hooks';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useGetUrlParams } from '../../../hooks';
|
||||
import { parseFiltersMap } from './parse_filter_map';
|
||||
import { AppState } from '../../../state';
|
||||
import { fetchOverviewFilters, GetOverviewFiltersPayload } from '../../../state/actions';
|
||||
import { fetchOverviewFilters } from '../../../state/actions';
|
||||
import { FilterGroupComponent } from './index';
|
||||
import { OverviewFilters } from '../../../../common/runtime_types/overview_filters';
|
||||
import { UptimeRefreshContext } from '../../../contexts';
|
||||
import { filterGroupDataSelector } from '../../../state/selectors';
|
||||
|
||||
interface OwnProps {
|
||||
interface Props {
|
||||
esFilters?: string;
|
||||
}
|
||||
|
||||
interface StoreProps {
|
||||
esKuery: string;
|
||||
lastRefresh: number;
|
||||
loading: boolean;
|
||||
overviewFilters: OverviewFilters;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
loadFilterGroup: typeof fetchOverviewFilters;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StoreProps & DispatchProps;
|
||||
|
||||
export const Container: React.FC<Props> = ({
|
||||
esKuery,
|
||||
esFilters,
|
||||
loading,
|
||||
loadFilterGroup,
|
||||
overviewFilters,
|
||||
}: Props) => {
|
||||
export const FilterGroup: React.FC<Props> = ({ esFilters }: Props) => {
|
||||
const { lastRefresh } = useContext(UptimeRefreshContext);
|
||||
|
||||
const [getUrlParams, updateUrl] = useUrlParams();
|
||||
const { dateRangeStart, dateRangeEnd, statusFilter, filters: urlFilters } = getUrlParams();
|
||||
const { esKuery, filters: overviewFilters, loading } = useSelector(filterGroupDataSelector);
|
||||
|
||||
const { dateRangeStart, dateRangeEnd, statusFilter, filters: urlFilters } = useGetUrlParams();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
const filterSelections = parseFiltersMap(urlFilters);
|
||||
loadFilterGroup({
|
||||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
locations: filterSelections.locations ?? [],
|
||||
ports: filterSelections.ports ?? [],
|
||||
schemes: filterSelections.schemes ?? [],
|
||||
search: esKuery,
|
||||
statusFilter,
|
||||
tags: filterSelections.tags ?? [],
|
||||
});
|
||||
dispatch(
|
||||
fetchOverviewFilters({
|
||||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
locations: filterSelections.locations ?? [],
|
||||
ports: filterSelections.ports ?? [],
|
||||
schemes: filterSelections.schemes ?? [],
|
||||
search: esKuery,
|
||||
statusFilter,
|
||||
tags: filterSelections.tags ?? [],
|
||||
})
|
||||
);
|
||||
}, [
|
||||
lastRefresh,
|
||||
dateRangeStart,
|
||||
|
@ -63,42 +48,8 @@ export const Container: React.FC<Props> = ({
|
|||
esFilters,
|
||||
statusFilter,
|
||||
urlFilters,
|
||||
loadFilterGroup,
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
// update filters in the URL from filter group
|
||||
const onFilterUpdate = (filtersKuery: string) => {
|
||||
if (urlFilters !== filtersKuery) {
|
||||
updateUrl({ filters: filtersKuery, pagination: '' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FilterGroupComponent
|
||||
currentFilter={urlFilters}
|
||||
overviewFilters={overviewFilters}
|
||||
loading={loading}
|
||||
onFilterUpdate={onFilterUpdate}
|
||||
/>
|
||||
);
|
||||
return <FilterGroupComponent overviewFilters={overviewFilters} loading={loading} />;
|
||||
};
|
||||
|
||||
const mapStateToProps = ({
|
||||
overviewFilters: { loading, filters },
|
||||
ui: { esKuery, lastRefresh },
|
||||
}: AppState): StoreProps => ({
|
||||
esKuery,
|
||||
overviewFilters: filters,
|
||||
lastRefresh,
|
||||
loading,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: any): DispatchProps => ({
|
||||
loadFilterGroup: (payload: GetOverviewFiltersPayload) => dispatch(fetchOverviewFilters(payload)),
|
||||
});
|
||||
|
||||
export const FilterGroup = connect<StoreProps, DispatchProps, OwnProps>(
|
||||
// @ts-ignore connect is expecting null | undefined for some reason
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Container);
|
||||
|
|
|
@ -20,6 +20,9 @@ export interface FilterPopoverProps {
|
|||
onFilterFieldChange: (fieldName: string, values: string[]) => void;
|
||||
selectedItems: string[];
|
||||
title: string;
|
||||
btnContent?: JSX.Element;
|
||||
forceOpen?: boolean;
|
||||
setForceOpen?: (val: boolean) => void;
|
||||
}
|
||||
|
||||
const isItemSelected = (selectedItems: string[], item: string): 'on' | undefined =>
|
||||
|
@ -34,6 +37,9 @@ export const FilterPopover = ({
|
|||
onFilterFieldChange,
|
||||
selectedItems,
|
||||
title,
|
||||
btnContent,
|
||||
forceOpen,
|
||||
setForceOpen,
|
||||
}: FilterPopoverProps) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [itemsToDisplay, setItemsToDisplay] = useState<string[]>([]);
|
||||
|
@ -52,28 +58,33 @@ export const FilterPopover = ({
|
|||
return (
|
||||
<EuiPopover
|
||||
button={
|
||||
<UptimeFilterButton
|
||||
isDisabled={disabled}
|
||||
isSelected={tempSelectedItems.length > 0}
|
||||
numFilters={items.length}
|
||||
numActiveFilters={tempSelectedItems.length}
|
||||
onClick={() => {
|
||||
setIsOpen(!isOpen);
|
||||
onFilterFieldChange(fieldName, tempSelectedItems);
|
||||
}}
|
||||
title={title}
|
||||
/>
|
||||
btnContent ?? (
|
||||
<UptimeFilterButton
|
||||
isDisabled={disabled}
|
||||
isSelected={tempSelectedItems.length > 0}
|
||||
numFilters={items.length}
|
||||
numActiveFilters={tempSelectedItems.length}
|
||||
onClick={() => {
|
||||
setIsOpen(!isOpen);
|
||||
onFilterFieldChange(fieldName, tempSelectedItems);
|
||||
}}
|
||||
title={title}
|
||||
/>
|
||||
)
|
||||
}
|
||||
closePopover={() => {
|
||||
setIsOpen(false);
|
||||
onFilterFieldChange(fieldName, tempSelectedItems);
|
||||
if (setForceOpen) {
|
||||
setForceOpen(false);
|
||||
}
|
||||
}}
|
||||
data-test-subj={`filter-popover_${id}`}
|
||||
id={id}
|
||||
isOpen={isOpen}
|
||||
isOpen={isOpen || forceOpen}
|
||||
ownFocus={true}
|
||||
withTitle
|
||||
zIndex={1000}
|
||||
zIndex={10000}
|
||||
>
|
||||
<EuiPopoverTitle>
|
||||
<EuiFieldSearch
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 filterLabels = {
|
||||
LOCATION: i18n.translate('xpack.uptime.filterBar.options.location.name', {
|
||||
defaultMessage: 'Location',
|
||||
}),
|
||||
|
||||
PORT: i18n.translate('xpack.uptime.filterBar.options.portLabel', { defaultMessage: 'Port' }),
|
||||
|
||||
SCHEME: i18n.translate('xpack.uptime.filterBar.options.schemeLabel', {
|
||||
defaultMessage: 'Scheme',
|
||||
}),
|
||||
|
||||
TAGS: i18n.translate('xpack.uptime.filterBar.options.tagsLabel', {
|
||||
defaultMessage: 'Tags',
|
||||
}),
|
||||
};
|
56
x-pack/plugins/uptime/public/hooks/use_filter_update.ts
Normal file
56
x-pack/plugins/uptime/public/hooks/use_filter_update.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { useEffect } from 'react';
|
||||
import { useUrlParams } from './use_url_params';
|
||||
|
||||
/**
|
||||
* Handle an added or removed value to filter against for an uptime field.
|
||||
* @param fieldName the name of the field to filter against
|
||||
* @param values the list of values to use when filter a field
|
||||
*/
|
||||
|
||||
export const useFilterUpdate = (fieldName?: string, values?: string[]) => {
|
||||
const [getUrlParams, updateUrl] = useUrlParams();
|
||||
|
||||
const { filters: currentFilters } = getUrlParams();
|
||||
|
||||
// update filters in the URL from filter group
|
||||
const onFilterUpdate = (filtersKuery: string) => {
|
||||
if (currentFilters !== filtersKuery) {
|
||||
updateUrl({ filters: filtersKuery, pagination: '' });
|
||||
}
|
||||
};
|
||||
|
||||
let filterKueries: Map<string, string[]>;
|
||||
try {
|
||||
filterKueries = new Map<string, string[]>(JSON.parse(currentFilters));
|
||||
} catch {
|
||||
filterKueries = new Map<string, string[]>();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (fieldName) {
|
||||
// add new term to filter map, toggle it off if already present
|
||||
const updatedFilterMap = new Map<string, string[] | undefined>(filterKueries);
|
||||
updatedFilterMap.set(fieldName, values);
|
||||
Array.from(updatedFilterMap.keys()).forEach(key => {
|
||||
const value = updatedFilterMap.get(key);
|
||||
if (value && value.length === 0) {
|
||||
updatedFilterMap.delete(key);
|
||||
}
|
||||
});
|
||||
|
||||
// store the new set of filters
|
||||
const persistedFilters = Array.from(updatedFilterMap);
|
||||
onFilterUpdate(persistedFilters.length === 0 ? '' : JSON.stringify(persistedFilters));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fieldName, values]);
|
||||
|
||||
return filterKueries;
|
||||
};
|
|
@ -174,7 +174,7 @@ describe('monitor status alert type', () => {
|
|||
{{context.downMonitorsWithGeo}}",
|
||||
"iconClass": "uptimeApp",
|
||||
"id": "xpack.uptime.alerts.monitorStatus",
|
||||
"name": "Uptime monitor status",
|
||||
"name": <MonitorStatusTitle />,
|
||||
"validate": [Function],
|
||||
}
|
||||
`);
|
||||
|
|
|
@ -12,6 +12,7 @@ import { AlertTypeModel } from '../../../../triggers_actions_ui/public';
|
|||
import { AlertTypeInitializer } from '.';
|
||||
import { StatusCheckExecutorParamsType } from '../../../common/runtime_types';
|
||||
import { AlertMonitorStatus } from '../../components/overview/alerts/alerts_containers';
|
||||
import { MonitorStatusTitle } from './monitor_status_title';
|
||||
import { CLIENT_ALERT_TYPES } from '../../../common/constants';
|
||||
import { MonitorStatusTranslations } from './translations';
|
||||
|
||||
|
@ -54,13 +55,13 @@ export const validate = (alertParams: any) => {
|
|||
return { errors };
|
||||
};
|
||||
|
||||
const { name, defaultActionMessage } = MonitorStatusTranslations;
|
||||
const { defaultActionMessage } = MonitorStatusTranslations;
|
||||
|
||||
export const initMonitorStatusAlertType: AlertTypeInitializer = ({
|
||||
autocomplete,
|
||||
}): AlertTypeModel => ({
|
||||
id: CLIENT_ALERT_TYPES.MONITOR_STATUS,
|
||||
name,
|
||||
name: <MonitorStatusTitle />,
|
||||
iconClass: 'uptimeApp',
|
||||
alertParamsExpression: params => <AlertMonitorStatus {...params} autocomplete={autocomplete} />,
|
||||
validate,
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { useSelector } from 'react-redux';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiText } from '@elastic/eui';
|
||||
import { snapshotDataSelector } from '../../state/selectors';
|
||||
|
||||
export const MonitorStatusTitle = () => {
|
||||
const { count, loading } = useSelector(snapshotDataSelector);
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.alerts.monitorStatus.title.label"
|
||||
defaultMessage="Uptime monitor status"
|
||||
/>{' '}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} style={{ alignSelf: 'center' }}>
|
||||
{!loading ? (
|
||||
<EuiText size="s" color="subdued">
|
||||
{count.total} monitors
|
||||
</EuiText>
|
||||
) : (
|
||||
<EuiLoadingSpinner size="m" />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -107,3 +107,16 @@ export const monitorListSelector = ({ monitorList, ui: { lastRefresh } }: AppSta
|
|||
monitorList,
|
||||
lastRefresh,
|
||||
});
|
||||
|
||||
export const overviewFiltersSelector = ({ overviewFilters }: AppState) => {
|
||||
return overviewFilters.filters;
|
||||
};
|
||||
|
||||
export const filterGroupDataSelector = ({
|
||||
overviewFilters: { loading, filters },
|
||||
ui: { esKuery },
|
||||
}: AppState) => ({
|
||||
esKuery,
|
||||
filters,
|
||||
loading,
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import { FtrProviderContext } from '../ftr_provider_context';
|
|||
|
||||
export function UptimePageProvider({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const pageObjects = getPageObjects(['common', 'timePicker']);
|
||||
const { alerts, common: commonService, monitor, navigation } = getService('uptime');
|
||||
const { common: commonService, monitor, navigation } = getService('uptime');
|
||||
const retry = getService('retry');
|
||||
|
||||
return new (class UptimePage {
|
||||
|
@ -97,42 +97,9 @@ export function UptimePageProvider({ getPageObjects, getService }: FtrProviderCo
|
|||
return await commonService.getSnapshotCount();
|
||||
}
|
||||
|
||||
public async openAlertFlyoutAndCreateMonitorStatusAlert({
|
||||
alertInterval,
|
||||
alertName,
|
||||
alertNumTimes,
|
||||
alertTags,
|
||||
alertThrottleInterval,
|
||||
alertTimerangeSelection,
|
||||
alertType,
|
||||
filters,
|
||||
}: {
|
||||
alertName: string;
|
||||
alertTags: string[];
|
||||
alertInterval: string;
|
||||
alertThrottleInterval: string;
|
||||
alertNumTimes: string;
|
||||
alertTimerangeSelection: string;
|
||||
alertType?: string;
|
||||
filters?: string;
|
||||
}) {
|
||||
public async setAlertKueryBarText(filters: string) {
|
||||
const { setKueryBarText } = commonService;
|
||||
await alerts.openFlyout();
|
||||
if (alertType) {
|
||||
await alerts.openMonitorStatusAlertType(alertType);
|
||||
}
|
||||
await alerts.setAlertName(alertName);
|
||||
await alerts.setAlertTags(alertTags);
|
||||
await alerts.setAlertInterval(alertInterval);
|
||||
await alerts.setAlertThrottleInterval(alertThrottleInterval);
|
||||
if (filters) {
|
||||
await setKueryBarText('xpack.uptime.alerts.monitorStatus.filterBar', filters);
|
||||
}
|
||||
await alerts.setAlertStatusNumTimes(alertNumTimes);
|
||||
await alerts.setAlertTimerangeSelection(alertTimerangeSelection);
|
||||
await alerts.setMonitorStatusSelectableToHours();
|
||||
await alerts.setLocationsSelectable();
|
||||
await alerts.clickSaveAlertButtion();
|
||||
await setKueryBarText('xpack.uptime.alerts.monitorStatus.filterBar', filters);
|
||||
}
|
||||
|
||||
public async setMonitorListPageSize(size: number): Promise<void> {
|
||||
|
|
|
@ -77,19 +77,37 @@ export function UptimeAlertsProvider({ getService }: FtrProviderContext) {
|
|||
['xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.hoursOption']
|
||||
);
|
||||
},
|
||||
async setLocationsSelectable() {
|
||||
await testSubjects.click(
|
||||
'xpack.uptime.alerts.monitorStatus.locationsSelectionExpression',
|
||||
5000
|
||||
);
|
||||
await testSubjects.click('xpack.uptime.alerts.monitorStatus.locationsSelectionSwitch', 5000);
|
||||
await testSubjects.click(
|
||||
'xpack.uptime.alerts.monitorStatus.locationsSelectionSelectable',
|
||||
5000
|
||||
);
|
||||
async clickAddFilter() {
|
||||
await testSubjects.click('uptimeCreateAlertAddFilter');
|
||||
},
|
||||
async clickAddFilterLocation() {
|
||||
await this.clickAddFilter();
|
||||
await testSubjects.click('uptimeAlertAddFilter.observer.geo.name');
|
||||
},
|
||||
async clickAddFilterPort() {
|
||||
await this.clickAddFilter();
|
||||
await testSubjects.click('uptimeAlertAddFilter.url.port');
|
||||
},
|
||||
async clickAddFilterType() {
|
||||
await this.clickAddFilter();
|
||||
await testSubjects.click('uptimeAlertAddFilter.monitor.type');
|
||||
},
|
||||
async clickLocationExpression(filter: string) {
|
||||
await testSubjects.click('uptimeCreateStatusAlert.filter_location');
|
||||
await testSubjects.click(`filter-popover-item_${filter}`);
|
||||
return browser.pressKeys(browser.keys.ESCAPE);
|
||||
},
|
||||
async clickSaveAlertButtion() {
|
||||
async clickPortExpression(filter: string) {
|
||||
await testSubjects.click('uptimeCreateStatusAlert.filter_port');
|
||||
await testSubjects.click(`filter-popover-item_${filter}`);
|
||||
return browser.pressKeys(browser.keys.ESCAPE);
|
||||
},
|
||||
async clickTypeExpression(filter: string) {
|
||||
await testSubjects.click('uptimeCreateStatusAlert.filter_scheme');
|
||||
await testSubjects.click(`filter-popover-item_${filter}`);
|
||||
return browser.pressKeys(browser.keys.ESCAPE);
|
||||
},
|
||||
async clickSaveAlertButton() {
|
||||
return testSubjects.click('saveAlertButton');
|
||||
},
|
||||
};
|
||||
|
|
|
@ -14,20 +14,67 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
const pageObjects = getPageObjects(['common', 'uptime']);
|
||||
const supertest = getService('supertest');
|
||||
const retry = getService('retry');
|
||||
let alerts: any;
|
||||
|
||||
it('posts an alert, verfies its presence, and deletes the alert', async () => {
|
||||
before(async () => {
|
||||
alerts = getService('uptime').alerts;
|
||||
});
|
||||
|
||||
it('can open alert flyout', async () => {
|
||||
await pageObjects.uptime.goToUptimeOverviewAndLoadData(DEFAULT_DATE_START, DEFAULT_DATE_END);
|
||||
await alerts.openFlyout();
|
||||
});
|
||||
|
||||
await pageObjects.uptime.openAlertFlyoutAndCreateMonitorStatusAlert({
|
||||
alertInterval: '11',
|
||||
alertName: 'uptime-test',
|
||||
alertNumTimes: '3',
|
||||
alertTags: ['uptime', 'another'],
|
||||
alertThrottleInterval: '30',
|
||||
alertTimerangeSelection: '1',
|
||||
filters: 'monitor.id: "0001-up"',
|
||||
});
|
||||
it('can set alert name', async () => {
|
||||
await alerts.setAlertName('uptime-test');
|
||||
});
|
||||
|
||||
it('can set alert tags', async () => {
|
||||
await alerts.setAlertTags(['uptime', 'another']);
|
||||
});
|
||||
|
||||
it('can set alert interval', async () => {
|
||||
await alerts.setAlertInterval('11');
|
||||
});
|
||||
|
||||
it('can set alert throttle interval', async () => {
|
||||
await alerts.setAlertThrottleInterval('30');
|
||||
});
|
||||
|
||||
it('can set alert status number of time', async () => {
|
||||
await alerts.setAlertStatusNumTimes('3');
|
||||
});
|
||||
it('can set alert time range', async () => {
|
||||
await alerts.setAlertTimerangeSelection('1');
|
||||
});
|
||||
it('can set monitor hours', async () => {
|
||||
await alerts.setMonitorStatusSelectableToHours();
|
||||
});
|
||||
|
||||
it('can set kuery bar filters', async () => {
|
||||
await pageObjects.uptime.setAlertKueryBarText('monitor.id: "0001-up"');
|
||||
});
|
||||
|
||||
it('can select location filter', async () => {
|
||||
await alerts.clickAddFilterLocation();
|
||||
await alerts.clickLocationExpression('mpls');
|
||||
});
|
||||
|
||||
it('can select port filter', async () => {
|
||||
await alerts.clickAddFilterPort();
|
||||
await alerts.clickPortExpression('5678');
|
||||
});
|
||||
|
||||
it('can select type/scheme filter', async () => {
|
||||
await alerts.clickAddFilterType();
|
||||
await alerts.clickTypeExpression('http');
|
||||
});
|
||||
|
||||
it('can save alert', async () => {
|
||||
await alerts.clickSaveAlertButton();
|
||||
});
|
||||
|
||||
it('posts an alert, verifies its presence, and deletes the alert', async () => {
|
||||
// The creation of the alert could take some time, so the first few times we query after
|
||||
// the previous line resolves, the API may not be done creating the alert yet, so we
|
||||
// put the fetch code in a retry block with a timeout.
|
||||
|
@ -67,7 +114,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
expect(timerange.to).to.be('now');
|
||||
expect(locations).to.eql(['mpls']);
|
||||
expect(filters).to.eql(
|
||||
'{"bool":{"should":[{"match_phrase":{"monitor.id":"0001-up"}}],"minimum_should_match":1}}'
|
||||
'{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"monitor.id":"0001-up"}}],' +
|
||||
'"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"match":{"observer.geo.name":"mpls"}}],' +
|
||||
'"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"match":{"url.port":5678}}],' +
|
||||
'"minimum_should_match":1}},{"bool":{"should":[{"match":{"monitor.type":"http"}}],"minimum_should_match":1}}]}}]}}]}}'
|
||||
);
|
||||
} finally {
|
||||
await supertest
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue