mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Update alert type selection layout to rows instead of grid (#73665)
* Update layout to rows for alert types * Fix gutter usage * Update heading, remove icons * Non-working update to the combo box * Add incorrect updates with questions to fix * Fix combo box * Cleanup changes to specific to this module * fixed type checks and made combobox always visible * Added groups by producer * Added get producer name from kibana features names * Added search bar with list of alert types * Added search support functionality * fixed links to alert type * added alert type title * Fixed failing tests * Design updates to list * Remove unsed items in import list * fixed merge issue * Fixed due to comments * fixed tests * Design fixes Co-authored-by: Yuliia Naumenko <yuliia.naumenko@elastic.com>
This commit is contained in:
parent
45ddd69ca2
commit
2c05957582
11 changed files with 318 additions and 70 deletions
|
@ -20148,7 +20148,6 @@
|
|||
"xpack.triggersActionsUI.home.connectorsTabTitle": "コネクター",
|
||||
"xpack.triggersActionsUI.home.sectionDescription": "アラートを使用して条件を検出し、コネクターを使用してアクションを実行します。",
|
||||
"xpack.triggersActionsUI.managementSection.displayName": "アラートとアクション",
|
||||
"xpack.triggersActionsUI.sections.actionAdd.indexAction.indexTextFieldLabel": "タグ (任意)",
|
||||
"xpack.triggersActionsUI.sections.actionConnectorAdd.cancelButtonLabel": "キャンセル",
|
||||
"xpack.triggersActionsUI.sections.actionConnectorAdd.manageLicensePlanBannerLinkTitle": "ライセンスの管理",
|
||||
"xpack.triggersActionsUI.sections.actionConnectorAdd.saveAndTestButtonLabel": "保存してテスト",
|
||||
|
@ -20256,7 +20255,6 @@
|
|||
"xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel": "コネクターを作成する",
|
||||
"xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton": "新規追加",
|
||||
"xpack.triggersActionsUI.sections.alertForm.alertNameLabel": "名前",
|
||||
"xpack.triggersActionsUI.sections.alertForm.changeAlertTypeAriaLabel": "削除",
|
||||
"xpack.triggersActionsUI.sections.alertForm.checkFieldLabel": "確認間隔",
|
||||
"xpack.triggersActionsUI.sections.alertForm.checkWithTooltip": "条件を評価する頻度を定義します。",
|
||||
"xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel": "{actionTypeName}コネクターがありません",
|
||||
|
@ -20273,7 +20271,6 @@
|
|||
"xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知間隔",
|
||||
"xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "アラートがアクティブな間にアクションを繰り返す頻度を定義します。",
|
||||
"xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "アクションタイプを選択してください",
|
||||
"xpack.triggersActionsUI.sections.alertForm.selectAlertTypeTitle": "トリガータイプを選択してください",
|
||||
"xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}",
|
||||
"xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "デフォルトアクショングループの定義がないのでアクションを追加できません",
|
||||
"xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage": "コネクターを読み込めません",
|
||||
|
|
|
@ -20167,7 +20167,6 @@
|
|||
"xpack.triggersActionsUI.home.connectorsTabTitle": "连接器",
|
||||
"xpack.triggersActionsUI.home.sectionDescription": "使用告警检测条件,并使用连接器采取操作。",
|
||||
"xpack.triggersActionsUI.managementSection.displayName": "告警和操作",
|
||||
"xpack.triggersActionsUI.sections.actionAdd.indexAction.indexTextFieldLabel": "标记(可选)",
|
||||
"xpack.triggersActionsUI.sections.actionConnectorAdd.cancelButtonLabel": "取消",
|
||||
"xpack.triggersActionsUI.sections.actionConnectorAdd.manageLicensePlanBannerLinkTitle": "管理许可证",
|
||||
"xpack.triggersActionsUI.sections.actionConnectorAdd.saveAndTestButtonLabel": "保存并测试",
|
||||
|
@ -20276,7 +20275,6 @@
|
|||
"xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel": "创建连接器",
|
||||
"xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton": "新添",
|
||||
"xpack.triggersActionsUI.sections.alertForm.alertNameLabel": "名称",
|
||||
"xpack.triggersActionsUI.sections.alertForm.changeAlertTypeAriaLabel": "删除",
|
||||
"xpack.triggersActionsUI.sections.alertForm.checkFieldLabel": "检查频率",
|
||||
"xpack.triggersActionsUI.sections.alertForm.checkWithTooltip": "定义评估条件的频率。",
|
||||
"xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel": "无 {actionTypeName} 连接器",
|
||||
|
@ -20293,7 +20291,6 @@
|
|||
"xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知频率",
|
||||
"xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "定义告警处于活动状态时重复操作的频率。",
|
||||
"xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "选择操作类型",
|
||||
"xpack.triggersActionsUI.sections.alertForm.selectAlertTypeTitle": "选择触发器类型",
|
||||
"xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}",
|
||||
"xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "无法添加操作,因为未定义默认操作组",
|
||||
"xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage": "无法加载连接器",
|
||||
|
|
|
@ -16,8 +16,8 @@ import {
|
|||
CoreStart,
|
||||
ScopedHistory,
|
||||
} from 'kibana/public';
|
||||
import { Section, routeToAlertDetails } from './constants';
|
||||
import { KibanaFeature } from '../../../features/common';
|
||||
import { Section, routeToAlertDetails } from './constants';
|
||||
import { AppContextProvider } from './app_context';
|
||||
import { ActionTypeRegistryContract, AlertTypeRegistryContract } from '../types';
|
||||
import { ChartsPluginStart } from '../../../../../src/plugins/charts/public';
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
DataPublicPluginStartUi,
|
||||
IndexPatternsContract,
|
||||
} from 'src/plugins/data/public';
|
||||
import { KibanaFeature } from '../../../../features/common';
|
||||
import { AlertTypeRegistryContract, ActionTypeRegistryContract } from '../../types';
|
||||
|
||||
export interface AlertsContextValue<MetaData = Record<string, any>> {
|
||||
|
@ -34,6 +35,7 @@ export interface AlertsContextValue<MetaData = Record<string, any>> {
|
|||
metadata?: MetaData;
|
||||
dataUi?: DataPublicPluginStartUi;
|
||||
dataIndexPatterns?: IndexPatternsContract;
|
||||
kibanaFeatures?: KibanaFeature[];
|
||||
}
|
||||
|
||||
const AlertsContext = createContext<AlertsContextValue>(null as any);
|
||||
|
|
|
@ -182,8 +182,6 @@ describe('alert_add', () => {
|
|||
|
||||
wrapper.find('[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click');
|
||||
|
||||
expect(wrapper.contains('Metadata: some value. Fields: test.')).toBeTruthy();
|
||||
|
||||
expect(wrapper.find('input#alertName').props().value).toBe('');
|
||||
|
||||
expect(wrapper.find('[data-test-subj="tagsComboBox"]').first().text()).toBe('');
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
.triggersActionsUI__alertTypeNodeHeading {
|
||||
margin-left: $euiSizeS;
|
||||
margin-right: $euiSizeS;
|
||||
}
|
|
@ -187,7 +187,7 @@ describe('alert_form', () => {
|
|||
|
||||
it('renders alert type description', async () => {
|
||||
await setup();
|
||||
wrapper.find('[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click');
|
||||
wrapper.find('button[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click');
|
||||
const alertDescription = wrapper.find('[data-test-subj="alertDescription"]');
|
||||
expect(alertDescription.exists()).toBeTruthy();
|
||||
expect(alertDescription.first().text()).toContain('Alert when testing');
|
||||
|
@ -195,7 +195,7 @@ describe('alert_form', () => {
|
|||
|
||||
it('renders alert type documentation link', async () => {
|
||||
await setup();
|
||||
wrapper.find('[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click');
|
||||
wrapper.find('button[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click');
|
||||
const alertDocumentationLink = wrapper.find('[data-test-subj="alertDocumentationLink"]');
|
||||
expect(alertDocumentationLink.exists()).toBeTruthy();
|
||||
expect(alertDocumentationLink.first().prop('href')).toBe('https://localhost.local/docs');
|
||||
|
|
|
@ -9,15 +9,15 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiTextColor,
|
||||
EuiTitle,
|
||||
EuiForm,
|
||||
EuiSpacer,
|
||||
EuiFieldText,
|
||||
EuiFieldSearch,
|
||||
EuiFlexGrid,
|
||||
EuiFormRow,
|
||||
EuiComboBox,
|
||||
EuiKeyPadMenuItem,
|
||||
EuiFieldNumber,
|
||||
EuiSelect,
|
||||
EuiIconTip,
|
||||
|
@ -25,11 +25,16 @@ import {
|
|||
EuiHorizontalRule,
|
||||
EuiLoadingSpinner,
|
||||
EuiEmptyPrompt,
|
||||
EuiListGroupItem,
|
||||
EuiListGroup,
|
||||
EuiLink,
|
||||
EuiText,
|
||||
EuiNotificationBadge,
|
||||
} from '@elastic/eui';
|
||||
import { some, filter, map, fold } from 'fp-ts/lib/Option';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { capitalize } from 'lodash';
|
||||
import { KibanaFeature } from '../../../../../features/public';
|
||||
import {
|
||||
getDurationNumberInItsUnit,
|
||||
getDurationUnitValue,
|
||||
|
@ -37,12 +42,23 @@ import {
|
|||
import { loadAlertTypes } from '../../lib/alert_api';
|
||||
import { actionVariablesFromAlertType } from '../../lib/action_variables';
|
||||
import { AlertReducerAction } from './alert_reducer';
|
||||
import { AlertTypeModel, Alert, IErrorObject, AlertAction, AlertTypeIndex } from '../../../types';
|
||||
import {
|
||||
AlertTypeModel,
|
||||
Alert,
|
||||
IErrorObject,
|
||||
AlertAction,
|
||||
AlertTypeIndex,
|
||||
AlertType,
|
||||
} from '../../../types';
|
||||
import { getTimeOptions } from '../../../common/lib/get_time_options';
|
||||
import { useAlertsContext } from '../../context/alerts_context';
|
||||
import { ActionForm } from '../action_connector_form';
|
||||
import { ALERTS_FEATURE_ID } from '../../../../../alerts/common';
|
||||
import { hasAllPrivilege, hasShowActionsCapability } from '../../lib/capabilities';
|
||||
import { SolutionFilter } from './solution_filter';
|
||||
import './alert_form.scss';
|
||||
|
||||
const ENTER_KEY = 13;
|
||||
|
||||
export function validateBaseProperties(alertObject: Alert) {
|
||||
const validationResult = { errors: {} };
|
||||
|
@ -77,6 +93,10 @@ export function validateBaseProperties(alertObject: Alert) {
|
|||
return validationResult;
|
||||
}
|
||||
|
||||
function getProducerFeatureName(producer: string, kibanaFeatures: KibanaFeature[]) {
|
||||
return kibanaFeatures.find((featureItem) => featureItem.id === producer)?.name;
|
||||
}
|
||||
|
||||
interface AlertFormProps {
|
||||
alert: Alert;
|
||||
dispatch: React.Dispatch<AlertReducerAction>;
|
||||
|
@ -104,12 +124,12 @@ export const AlertForm = ({
|
|||
actionTypeRegistry,
|
||||
docLinks,
|
||||
capabilities,
|
||||
kibanaFeatures,
|
||||
} = alertsContext;
|
||||
const canShowActions = hasShowActionsCapability(capabilities);
|
||||
|
||||
const [alertTypeModel, setAlertTypeModel] = useState<AlertTypeModel | null>(null);
|
||||
|
||||
const [alertTypesIndex, setAlertTypesIndex] = useState<AlertTypeIndex | undefined>(undefined);
|
||||
const [alertInterval, setAlertInterval] = useState<number | undefined>(
|
||||
alert.schedule.interval ? getDurationNumberInItsUnit(alert.schedule.interval) : undefined
|
||||
);
|
||||
|
@ -123,20 +143,53 @@ export const AlertForm = ({
|
|||
alert.throttle ? getDurationUnitValue(alert.throttle) : 'm'
|
||||
);
|
||||
const [defaultActionGroupId, setDefaultActionGroupId] = useState<string | undefined>(undefined);
|
||||
const [alertTypesIndex, setAlertTypesIndex] = useState<AlertTypeIndex | null>(null);
|
||||
|
||||
const [availableAlertTypes, setAvailableAlertTypes] = useState<
|
||||
Array<{ alertTypeModel: AlertTypeModel; alertType: AlertType }>
|
||||
>([]);
|
||||
const [filteredAlertTypes, setFilteredAlertTypes] = useState<
|
||||
Array<{ alertTypeModel: AlertTypeModel; alertType: AlertType }>
|
||||
>([]);
|
||||
const [searchText, setSearchText] = useState<string | undefined>();
|
||||
const [inputText, setInputText] = useState<string | undefined>();
|
||||
const [solutions, setSolutions] = useState<Map<string, string> | undefined>(undefined);
|
||||
const [solutionsFilter, setSolutionFilter] = useState<string[]>([]);
|
||||
|
||||
// load alert types
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const alertTypes = await loadAlertTypes({ http });
|
||||
const alertTypesResult = await loadAlertTypes({ http });
|
||||
const index: AlertTypeIndex = new Map();
|
||||
for (const alertTypeItem of alertTypes) {
|
||||
for (const alertTypeItem of alertTypesResult) {
|
||||
index.set(alertTypeItem.id, alertTypeItem);
|
||||
}
|
||||
if (alert.alertTypeId && index.has(alert.alertTypeId)) {
|
||||
setDefaultActionGroupId(index.get(alert.alertTypeId)!.defaultActionGroupId);
|
||||
}
|
||||
setAlertTypesIndex(index);
|
||||
const availableAlertTypesResult = getAvailableAlertTypes(alertTypesResult);
|
||||
setAvailableAlertTypes(availableAlertTypesResult);
|
||||
|
||||
const solutionsResult = availableAlertTypesResult.reduce(
|
||||
(result: Map<string, string>, alertTypeItem) => {
|
||||
if (!result.has(alertTypeItem.alertType.producer)) {
|
||||
result.set(
|
||||
alertTypeItem.alertType.producer,
|
||||
(kibanaFeatures
|
||||
? getProducerFeatureName(alertTypeItem.alertType.producer, kibanaFeatures)
|
||||
: capitalize(alertTypeItem.alertType.producer)) ??
|
||||
capitalize(alertTypeItem.alertType.producer)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
new Map()
|
||||
);
|
||||
setSolutions(
|
||||
new Map([...solutionsResult.entries()].sort(([, a], [, b]) => a.localeCompare(b)))
|
||||
);
|
||||
} catch (e) {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.translate(
|
||||
|
@ -184,47 +237,143 @@ export const AlertForm = ({
|
|||
[dispatch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const searchValue = searchText ? searchText.trim().toLocaleLowerCase() : null;
|
||||
setFilteredAlertTypes(
|
||||
availableAlertTypes
|
||||
.filter((alertTypeItem) =>
|
||||
solutionsFilter.length > 0
|
||||
? solutionsFilter.find((item) => alertTypeItem.alertType!.producer === item)
|
||||
: alertTypeItem
|
||||
)
|
||||
.filter((alertTypeItem) =>
|
||||
searchValue
|
||||
? alertTypeItem.alertTypeModel.name
|
||||
.toString()
|
||||
.toLocaleLowerCase()
|
||||
.includes(searchValue) ||
|
||||
alertTypeItem.alertType!.producer.toLocaleLowerCase().includes(searchValue) ||
|
||||
alertTypeItem.alertTypeModel.description.toLocaleLowerCase().includes(searchValue)
|
||||
: alertTypeItem
|
||||
)
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [alertTypeRegistry, availableAlertTypes, searchText, JSON.stringify(solutionsFilter)]);
|
||||
|
||||
const getAvailableAlertTypes = (alertTypesResult: AlertType[]) =>
|
||||
alertTypeRegistry
|
||||
.list()
|
||||
.reduce(
|
||||
(
|
||||
arr: Array<{ alertType: AlertType; alertTypeModel: AlertTypeModel }>,
|
||||
alertTypeRegistryItem: AlertTypeModel
|
||||
) => {
|
||||
const alertType = alertTypesResult.find((item) => alertTypeRegistryItem.id === item.id);
|
||||
if (alertType) {
|
||||
arr.push({
|
||||
alertType,
|
||||
alertTypeModel: alertTypeRegistryItem,
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
[]
|
||||
)
|
||||
.filter((item) => item.alertType && hasAllPrivilege(alert, item.alertType))
|
||||
.filter((item) =>
|
||||
alert.consumer === ALERTS_FEATURE_ID
|
||||
? !item.alertTypeModel.requiresAppContext
|
||||
: item.alertType!.producer === alert.consumer
|
||||
);
|
||||
|
||||
const tagsOptions = alert.tags ? alert.tags.map((label: string) => ({ label })) : [];
|
||||
|
||||
const AlertParamsExpressionComponent = alertTypeModel
|
||||
? alertTypeModel.alertParamsExpression
|
||||
: null;
|
||||
|
||||
const alertTypeRegistryList = alertTypesIndex
|
||||
? alertTypeRegistry
|
||||
.list()
|
||||
.filter(
|
||||
(alertTypeRegistryItem: AlertTypeModel) =>
|
||||
alertTypesIndex.has(alertTypeRegistryItem.id) &&
|
||||
hasAllPrivilege(alert, alertTypesIndex.get(alertTypeRegistryItem.id))
|
||||
)
|
||||
.filter((alertTypeRegistryItem: AlertTypeModel) =>
|
||||
alert.consumer === ALERTS_FEATURE_ID
|
||||
? !alertTypeRegistryItem.requiresAppContext
|
||||
: alertTypesIndex.get(alertTypeRegistryItem.id)!.producer === alert.consumer
|
||||
)
|
||||
: [];
|
||||
const alertTypesByProducer = filteredAlertTypes.reduce(
|
||||
(
|
||||
result: Record<string, Array<{ id: string; name: string; alertTypeItem: AlertTypeModel }>>,
|
||||
alertTypeValue
|
||||
) => {
|
||||
const producer = alertTypeValue.alertType.producer;
|
||||
if (producer) {
|
||||
(result[producer] = result[producer] || []).push({
|
||||
name:
|
||||
typeof alertTypeValue.alertTypeModel.name === 'string'
|
||||
? alertTypeValue.alertTypeModel.name
|
||||
: alertTypeValue.alertTypeModel.name.props.defaultMessage,
|
||||
id: alertTypeValue.alertTypeModel.id,
|
||||
alertTypeItem: alertTypeValue.alertTypeModel,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const alertTypeNodes = alertTypeRegistryList.map(function (item, index) {
|
||||
return (
|
||||
<EuiKeyPadMenuItem
|
||||
key={index}
|
||||
data-test-subj={`${item.id}-SelectOption`}
|
||||
label={item.name}
|
||||
onClick={() => {
|
||||
setAlertProperty('alertTypeId', item.id);
|
||||
setActions([]);
|
||||
setAlertTypeModel(item);
|
||||
setAlertProperty('params', {});
|
||||
if (alertTypesIndex && alertTypesIndex.has(item.id)) {
|
||||
setDefaultActionGroupId(alertTypesIndex.get(item.id)!.defaultActionGroupId);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<EuiIcon size="xl" type={item.iconClass} />
|
||||
</EuiKeyPadMenuItem>
|
||||
);
|
||||
});
|
||||
const alertTypeNodes = Object.entries(alertTypesByProducer)
|
||||
.sort(([a], [b]) =>
|
||||
solutions ? solutions.get(a)!.localeCompare(solutions.get(b)!) : a.localeCompare(b)
|
||||
)
|
||||
.map(([solution, items], groupIndex) => (
|
||||
<Fragment key={`group${groupIndex}`}>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
alignItems="center"
|
||||
className="triggersActionsUI__alertTypeNodeHeading"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle
|
||||
data-test-subj={`alertType${groupIndex}Group`}
|
||||
size="xxxs"
|
||||
textTransform="uppercase"
|
||||
>
|
||||
<EuiTextColor color="subdued">
|
||||
{(kibanaFeatures
|
||||
? getProducerFeatureName(solution, kibanaFeatures)
|
||||
: capitalize(solution)) ?? capitalize(solution)}
|
||||
</EuiTextColor>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiNotificationBadge color="subdued">{items.length}</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule size="full" margin="xs" />
|
||||
<EuiListGroup flush={true} gutterSize="m" size="l" maxWidth={false}>
|
||||
{items
|
||||
.sort((a, b) => a.name.toString().localeCompare(b.name.toString()))
|
||||
.map((item, index) => (
|
||||
<Fragment key={index}>
|
||||
<EuiListGroupItem
|
||||
data-test-subj={`${item.id}-SelectOption`}
|
||||
color="primary"
|
||||
label={
|
||||
<span>
|
||||
<strong>{item.name}</strong>
|
||||
<EuiText color="subdued" size="s">
|
||||
<p>{item.alertTypeItem.description}</p>
|
||||
</EuiText>
|
||||
</span>
|
||||
}
|
||||
onClick={() => {
|
||||
setAlertProperty('alertTypeId', item.id);
|
||||
setActions([]);
|
||||
setAlertTypeModel(item.alertTypeItem);
|
||||
setAlertProperty('params', {});
|
||||
if (alertTypesIndex && alertTypesIndex.has(item.id)) {
|
||||
setDefaultActionGroupId(alertTypesIndex.get(item.id)!.defaultActionGroupId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</EuiListGroup>
|
||||
<EuiSpacer />
|
||||
</Fragment>
|
||||
));
|
||||
|
||||
const alertTypeDetails = (
|
||||
<Fragment>
|
||||
|
@ -401,12 +550,9 @@ export const AlertForm = ({
|
|||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.actionAdd.indexAction.indexTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Tags (optional)',
|
||||
}
|
||||
)}
|
||||
label={i18n.translate('xpack.triggersActionsUI.sections.alertForm.tagsFieldLabel', {
|
||||
defaultMessage: 'Tags (optional)',
|
||||
})}
|
||||
>
|
||||
<EuiComboBox
|
||||
noSuggestions
|
||||
|
@ -531,21 +677,52 @@ export const AlertForm = ({
|
|||
<EuiSpacer size="m" />
|
||||
{alertTypeModel ? (
|
||||
<Fragment>{alertTypeDetails}</Fragment>
|
||||
) : alertTypeNodes.length ? (
|
||||
) : availableAlertTypes.length ? (
|
||||
<Fragment>
|
||||
<EuiHorizontalRule />
|
||||
<EuiTitle size="s">
|
||||
<h5 id="alertTypeTitle">
|
||||
<FormattedMessage
|
||||
defaultMessage="Select a trigger type"
|
||||
id="xpack.triggersActionsUI.sections.alertForm.selectAlertTypeTitle"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<EuiTitle size="xxs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.alertTypeSelectLabel"
|
||||
defaultMessage="Select alert type"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiFieldSearch
|
||||
fullWidth
|
||||
data-test-subj="alertSearchField"
|
||||
onChange={(e) => setInputText(e.target.value)}
|
||||
onKeyUp={(e) => {
|
||||
if (e.keyCode === ENTER_KEY) {
|
||||
setSearchText(inputText);
|
||||
}
|
||||
}}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertForm.searchPlaceholderTitle',
|
||||
{ defaultMessage: 'Search' }
|
||||
)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{solutions ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<SolutionFilter
|
||||
key="solution-filter"
|
||||
solutions={solutions}
|
||||
onChange={(selectedSolutions: string[]) => setSolutionFilter(selectedSolutions)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup gutterSize="s" wrap>
|
||||
{alertTypeNodes}
|
||||
</EuiFlexGroup>
|
||||
{alertTypeNodes}
|
||||
</Fragment>
|
||||
) : alertTypesIndex ? (
|
||||
<NoAuthorizedAlertTypes operation={operation} />
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 { EuiFilterGroup, EuiPopover, EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui';
|
||||
|
||||
interface SolutionFilterProps {
|
||||
solutions: Map<string, string>;
|
||||
onChange?: (selectedSolutions: string[]) => void;
|
||||
}
|
||||
|
||||
export const SolutionFilter: React.FunctionComponent<SolutionFilterProps> = ({
|
||||
solutions,
|
||||
onChange,
|
||||
}: SolutionFilterProps) => {
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>([]);
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (onChange) {
|
||||
onChange(selectedValues);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedValues]);
|
||||
|
||||
return (
|
||||
<EuiFilterGroup>
|
||||
<EuiPopover
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={() => setIsPopoverOpen(false)}
|
||||
button={
|
||||
<EuiFilterButton
|
||||
iconType="arrowDown"
|
||||
hasActiveFilters={selectedValues.length > 0}
|
||||
numActiveFilters={selectedValues.length}
|
||||
numFilters={selectedValues.length}
|
||||
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
|
||||
data-test-subj="solutionsFilterButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.solutionFilterLabel"
|
||||
defaultMessage="Filter by solution"
|
||||
/>
|
||||
</EuiFilterButton>
|
||||
}
|
||||
>
|
||||
<div className="euiFilterSelect__items">
|
||||
{[...solutions.entries()].map(([id, title]) => (
|
||||
<EuiFilterSelectItem
|
||||
key={id}
|
||||
onClick={() => {
|
||||
const isPreviouslyChecked = selectedValues.includes(id);
|
||||
if (isPreviouslyChecked) {
|
||||
setSelectedValues(selectedValues.filter((val) => val !== id));
|
||||
} else {
|
||||
setSelectedValues([...selectedValues, id]);
|
||||
}
|
||||
}}
|
||||
checked={selectedValues.includes(id) ? 'on' : undefined}
|
||||
data-test-subj={`solution${id}FilterOption`}
|
||||
>
|
||||
{title}
|
||||
</EuiFilterSelectItem>
|
||||
))}
|
||||
</div>
|
||||
</EuiPopover>
|
||||
</EuiFilterGroup>
|
||||
);
|
||||
};
|
|
@ -672,6 +672,7 @@ export const AlertsList: React.FunctionComponent = () => {
|
|||
capabilities,
|
||||
dataUi: dataPlugin.ui,
|
||||
dataIndexPatterns: dataPlugin.indexPatterns,
|
||||
kibanaFeatures,
|
||||
}}
|
||||
>
|
||||
<AlertAdd
|
||||
|
|
|
@ -115,7 +115,6 @@ export class Plugin
|
|||
|
||||
const { boot } = await import('./application/boot');
|
||||
const kibanaFeatures = await pluginsStart.features.getFeatures();
|
||||
|
||||
return boot({
|
||||
dataPlugin: pluginsStart.data,
|
||||
charts: pluginsStart.charts,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue