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:
DeFazio 2020-11-09 11:38:39 -05:00 committed by GitHub
parent 45ddd69ca2
commit 2c05957582
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 318 additions and 70 deletions

View file

@ -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": "コネクターを読み込めません",

View file

@ -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": "无法加载连接器",

View file

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

View file

@ -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);

View file

@ -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('');

View file

@ -0,0 +1,4 @@
.triggersActionsUI__alertTypeNodeHeading {
margin-left: $euiSizeS;
margin-right: $euiSizeS;
}

View file

@ -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');

View file

@ -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} />

View file

@ -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>
);
};

View file

@ -672,6 +672,7 @@ export const AlertsList: React.FunctionComponent = () => {
capabilities,
dataUi: dataPlugin.ui,
dataIndexPatterns: dataPlugin.indexPatterns,
kibanaFeatures,
}}
>
<AlertAdd

View file

@ -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,