From bb51156c5c90f17b33f95c55f56a8ac183cd8ea7 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Fri, 30 Oct 2020 14:46:48 -0700 Subject: [PATCH] [Alerting UI] Grouped list of alert types using producers in Types filter of Alerts tab (#81876) * Grouped list of alert types using producers in Types filter of Alerts tab * Added e2e test * fixed deps for test utils --- .../plugins/triggers_actions_ui/kibana.json | 2 +- .../public/application/app.tsx | 2 + .../actions_connectors_list.test.tsx | 16 ++++++ .../components/alerts_list.test.tsx | 11 ++++ .../alerts_list/components/alerts_list.tsx | 41 ++++++++++++-- .../alerts_list/components/type_filter.tsx | 56 ++++++++++++------- .../public/application/test_utils/index.ts | 4 ++ .../triggers_actions_ui/public/plugin.ts | 4 ++ .../apps/triggers_actions_ui/alerts_list.ts | 1 + 9 files changed, 110 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/kibana.json b/x-pack/plugins/triggers_actions_ui/kibana.json index a4446e0a75120..72e1f0be5f7f4 100644 --- a/x-pack/plugins/triggers_actions_ui/kibana.json +++ b/x-pack/plugins/triggers_actions_ui/kibana.json @@ -3,7 +3,7 @@ "version": "kibana", "server": true, "ui": true, - "optionalPlugins": ["home", "alerts", "stackAlerts"], + "optionalPlugins": ["alerts", "stackAlerts", "features", "home"], "requiredPlugins": ["management", "charts", "data", "kibanaReact"], "configPath": ["xpack", "trigger_actions_ui"], "extraPublicDirs": ["public/common", "public/common/constants"], diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index bb9fe65d6bbb8..fc48a8e977c7d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -17,6 +17,7 @@ import { ScopedHistory, } from 'kibana/public'; import { Section, routeToAlertDetails } from './constants'; +import { KibanaFeature } from '../../../features/common'; import { AppContextProvider } from './app_context'; import { ActionTypeRegistryContract, AlertTypeRegistryContract } from '../types'; import { ChartsPluginStart } from '../../../../../src/plugins/charts/public'; @@ -44,6 +45,7 @@ export interface AppDeps { actionTypeRegistry: ActionTypeRegistryContract; alertTypeRegistry: AlertTypeRegistryContract; history: ScopedHistory; + kibanaFeatures: KibanaFeature[]; } export const App = (appDeps: AppDeps) => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index 33b839dc70b31..e946e881def10 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -15,6 +15,7 @@ import { AppContextProvider } from '../../../app_context'; import { chartPluginMock } from '../../../../../../../../src/plugins/charts/public/mocks'; import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; import { alertingPluginMock } from '../../../../../../alerts/public/mocks'; +import { featuresPluginMock } from '../../../../../../features/public/mocks'; jest.mock('../../../lib/action_connector_api', () => ({ loadAllActions: jest.fn(), @@ -49,6 +50,8 @@ describe('actions_connectors_list component empty', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + const deps = { chrome, docLinks, @@ -71,6 +74,7 @@ describe('actions_connectors_list component empty', () => { setBreadcrumbs: jest.fn(), actionTypeRegistry, alertTypeRegistry: {} as any, + kibanaFeatures, }; actionTypeRegistry.has.mockReturnValue(true); @@ -156,6 +160,8 @@ describe('actions_connectors_list component with items', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + const deps = { chrome, docLinks, @@ -182,6 +188,7 @@ describe('actions_connectors_list component with items', () => { }, } as any, alertTypeRegistry: {} as any, + kibanaFeatures, }; wrapper = mountWithIntl( @@ -244,6 +251,8 @@ describe('actions_connectors_list component empty with show only capability', () application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + const deps = { chrome, docLinks, @@ -270,6 +279,7 @@ describe('actions_connectors_list component empty with show only capability', () }, } as any, alertTypeRegistry: {} as any, + kibanaFeatures, }; wrapper = mountWithIntl( @@ -333,6 +343,8 @@ describe('actions_connectors_list with show only capability', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + const deps = { chrome, docLinks, @@ -359,6 +371,7 @@ describe('actions_connectors_list with show only capability', () => { }, } as any, alertTypeRegistry: {} as any, + kibanaFeatures, }; wrapper = mountWithIntl( @@ -434,6 +447,8 @@ describe('actions_connectors_list component with disabled items', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + const deps = { chrome, docLinks, @@ -460,6 +475,7 @@ describe('actions_connectors_list component with disabled items', () => { }, } as any, alertTypeRegistry: {} as any, + kibanaFeatures, }; wrapper = mountWithIntl( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index e6e44d4d21bdf..21dd17b538c63 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -18,6 +18,7 @@ import { chartPluginMock } from '../../../../../../../../src/plugins/charts/publ import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; import { alertingPluginMock } from '../../../../../../alerts/public/mocks'; import { ALERTS_FEATURE_ID } from '../../../../../../alerts/common'; +import { featuresPluginMock } from '../../../../../../features/public/mocks'; jest.mock('../../../lib/action_connector_api', () => ({ loadActionTypes: jest.fn(), @@ -96,6 +97,9 @@ describe('alerts_list component empty', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + const deps = { chrome, docLinks, @@ -111,6 +115,7 @@ describe('alerts_list component empty', () => { setBreadcrumbs: jest.fn(), actionTypeRegistry, alertTypeRegistry, + kibanaFeatures, }; wrapper = mountWithIntl( @@ -265,6 +270,7 @@ describe('alerts_list component with items', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); const deps = { chrome, docLinks, @@ -280,6 +286,7 @@ describe('alerts_list component with items', () => { setBreadcrumbs: jest.fn(), actionTypeRegistry, alertTypeRegistry, + kibanaFeatures, }; alertTypeRegistry.has.mockReturnValue(true); @@ -346,6 +353,7 @@ describe('alerts_list component empty with show only capability', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); const deps = { chrome, docLinks, @@ -365,6 +373,7 @@ describe('alerts_list component empty with show only capability', () => { }, } as any, alertTypeRegistry: {} as any, + kibanaFeatures, }; wrapper = mountWithIntl( @@ -465,6 +474,7 @@ describe('alerts_list with show only capability', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); const deps = { chrome, docLinks, @@ -480,6 +490,7 @@ describe('alerts_list with show only capability', () => { setBreadcrumbs: jest.fn(), actionTypeRegistry, alertTypeRegistry, + kibanaFeatures, }; alertTypeRegistry.has.mockReturnValue(false); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index 9eb1149cf3905..3f39c698597ce 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -7,6 +7,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { i18n } from '@kbn/i18n'; +import { capitalize, sortBy } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useEffect, useState, Fragment } from 'react'; import { @@ -78,6 +79,7 @@ export const AlertsList: React.FunctionComponent = () => { docLinks, charts, dataPlugin, + kibanaFeatures, } = useAppDependencies(); const canExecuteActions = hasExecuteActionsCapability(capabilities); @@ -334,16 +336,43 @@ export const AlertsList: React.FunctionComponent = () => { (alertType) => alertType.authorizedConsumers[ALERTS_FEATURE_ID]?.all ); + const getProducerFeatureName = (producer: string) => { + return kibanaFeatures?.find((featureItem) => featureItem.id === producer)?.name; + }; + + const groupAlertTypesByProducer = () => { + return authorizedAlertTypes.reduce( + ( + result: Record< + string, + Array<{ + value: string; + name: string; + }> + >, + alertType + ) => { + const producer = alertType.producer; + (result[producer] = result[producer] || []).push({ + value: alertType.id, + name: alertType.name, + }); + return result; + }, + {} + ); + }; + const toolsRight = [ setTypesFilter(types)} - options={authorizedAlertTypes - .map((alertType) => ({ - value: alertType.id, - name: alertType.name, - })) - .sort((a, b) => a.name.localeCompare(b.name))} + options={sortBy(Object.entries(groupAlertTypesByProducer())).map( + ([groupName, alertTypesOptions]) => ({ + groupName: getProducerFeatureName(groupName) ?? capitalize(groupName), + subOptions: alertTypesOptions.sort((a, b) => a.name.localeCompare(b.name)), + }) + )} />, ; }>; onChange?: (selectedTags: string[]) => void; } @@ -52,22 +61,29 @@ export const TypeFilter: React.FunctionComponent = ({ } >
- {options.map((item, index) => ( - { - const isPreviouslyChecked = selectedValues.includes(item.value); - if (isPreviouslyChecked) { - setSelectedValues(selectedValues.filter((val) => val !== item.value)); - } else { - setSelectedValues(selectedValues.concat(item.value)); - } - }} - checked={selectedValues.includes(item.value) ? 'on' : undefined} - data-test-subj={`alertType${item.value}FilterOption`} - > - {item.name} - + {options.map((groupItem, groupIndex) => ( + + +

{groupItem.groupName}

+
+ {groupItem.subOptions.map((item, index) => ( + { + const isPreviouslyChecked = selectedValues.includes(item.value); + if (isPreviouslyChecked) { + setSelectedValues(selectedValues.filter((val) => val !== item.value)); + } else { + setSelectedValues(selectedValues.concat(item.value)); + } + }} + checked={selectedValues.includes(item.value) ? 'on' : undefined} + data-test-subj={`alertType${item.value}FilterOption`} + > + {item.name} + + ))} +
))}
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/test_utils/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/test_utils/index.ts index 7b3872246ca50..b5ab53d868cf1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/test_utils/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/test_utils/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { featuresPluginMock } from '../../../../features/public/mocks'; import { chartPluginMock } from '../../../../../../src/plugins/charts/public/mocks'; import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; import { alertingPluginMock } from '../../../../alerts/public/mocks'; @@ -22,6 +23,8 @@ export async function getMockedAppDependencies() { application: { capabilities, navigateToApp }, }, ] = await coreSetupMock.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + return { chrome, docLinks, @@ -37,5 +40,6 @@ export async function getMockedAppDependencies() { setBreadcrumbs: jest.fn(), actionTypeRegistry, alertTypeRegistry, + kibanaFeatures, }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 393ac5bc1b74d..b22be6ef9b2f6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -12,6 +12,7 @@ import { } from 'src/core/public'; import { i18n } from '@kbn/i18n'; +import { FeaturesPluginStart } from '../../features/public'; import { registerBuiltInActionTypes } from './application/components/builtin_action_types'; import { registerBuiltInAlertTypes } from './application/components/builtin_alert_types'; import { ActionTypeModel, AlertTypeModel } from './types'; @@ -52,6 +53,7 @@ interface PluginsStart { charts: ChartsPluginStart; alerts?: AlertingStart; navigateToApp: CoreStart['application']['navigateToApp']; + features: FeaturesPluginStart; } export class Plugin @@ -112,6 +114,7 @@ export class Plugin ]; const { boot } = await import('./application/boot'); + const kibanaFeatures = await pluginsStart.features.getFeatures(); return boot({ dataPlugin: pluginsStart.data, @@ -131,6 +134,7 @@ export class Plugin history: params.history, actionTypeRegistry, alertTypeRegistry, + kibanaFeatures, }); }, }); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts index a69db68c7d35e..f7281a1d93a46 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts @@ -458,6 +458,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const failinfAlert = await createFailingAlert(); await refreshAlertsList(); await testSubjects.click('alertTypeFilterButton'); + expect(await (await testSubjects.find('alertType0Group')).getVisibleText()).to.eql('Alerts'); await testSubjects.click('alertTypetest.failingFilterOption'); await retry.try(async () => {