From 4841cfa73eb24482d1d5db71f385dd2071e3e651 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 27 Oct 2020 22:15:51 -0700 Subject: [PATCH] Grouped list of alert types using producers in Types filter of Alerts tab --- .../plugins/triggers_actions_ui/kibana.json | 2 +- .../public/application/app.tsx | 2 + .../components/alerts_list.test.tsx | 11 ++++ .../alerts_list/components/alerts_list.tsx | 41 ++++++++++++-- .../alerts_list/components/type_filter.tsx | 56 ++++++++++++------- .../triggers_actions_ui/public/plugin.ts | 4 ++ 6 files changed, 89 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/kibana.json b/x-pack/plugins/triggers_actions_ui/kibana.json index c9187096821d8..c49699265fa95 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": ["alerts", "stackAlerts"], + "optionalPlugins": ["alerts", "stackAlerts", "features"], "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 c53dc0c105084..abfb68f7083d4 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 { ActionTypeModel, AlertTypeModel } from '../types'; import { TypeRegistry } from './type_registry'; @@ -45,6 +46,7 @@ export interface AppDeps { actionTypeRegistry: TypeRegistry; alertTypeRegistry: TypeRegistry; history: ScopedHistory; + kibanaFeatures: KibanaFeature[]; } export const App = (appDeps: AppDeps) => { 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 86b9afd9565f8..db448b6cd7e09 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: actionTypeRegistry as any, alertTypeRegistry: alertTypeRegistry as any, + 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: actionTypeRegistry as any, alertTypeRegistry: alertTypeRegistry as any, + 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: actionTypeRegistry as any, alertTypeRegistry: alertTypeRegistry as any, + 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/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 874a380f56b5f..eeec9327f1388 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'; @@ -47,6 +48,7 @@ interface PluginsStart { charts: ChartsPluginStart; alerts?: AlertingStart; navigateToApp: CoreStart['application']['navigateToApp']; + features: FeaturesPluginStart; } export class Plugin @@ -87,6 +89,7 @@ export class Plugin ]; const { boot } = await import('./application/boot'); + const kibanaFeatures = await pluginsStart.features.getFeatures(); return boot({ dataPlugin: pluginsStart.data, @@ -106,6 +109,7 @@ export class Plugin history: params.history, actionTypeRegistry, alertTypeRegistry, + kibanaFeatures, }); }, });