Skip to content

Commit

Permalink
[Alerting UI] Grouped list of alert types using producers in Types fi…
Browse files Browse the repository at this point in the history
…lter of Alerts tab (elastic#81876) (elastic#82205)

* Grouped list of alert types using producers in Types filter of Alerts tab

* Added e2e test

* fixed deps for test utils
  • Loading branch information
YulNaumenko authored Oct 31, 2020
1 parent f8a0026 commit d9c8724
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 27 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/triggers_actions_ui/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/triggers_actions_ui/public/application/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -44,6 +45,7 @@ export interface AppDeps {
actionTypeRegistry: ActionTypeRegistryContract;
alertTypeRegistry: AlertTypeRegistryContract;
history: ScopedHistory;
kibanaFeatures: KibanaFeature[];
}

export const App = (appDeps: AppDeps) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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,
Expand All @@ -71,6 +74,7 @@ describe('actions_connectors_list component empty', () => {
setBreadcrumbs: jest.fn(),
actionTypeRegistry,
alertTypeRegistry: {} as any,
kibanaFeatures,
};
actionTypeRegistry.has.mockReturnValue(true);

Expand Down Expand Up @@ -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,
Expand All @@ -182,6 +188,7 @@ describe('actions_connectors_list component with items', () => {
},
} as any,
alertTypeRegistry: {} as any,
kibanaFeatures,
};

wrapper = mountWithIntl(
Expand Down Expand Up @@ -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,
Expand All @@ -270,6 +279,7 @@ describe('actions_connectors_list component empty with show only capability', ()
},
} as any,
alertTypeRegistry: {} as any,
kibanaFeatures,
};

wrapper = mountWithIntl(
Expand Down Expand Up @@ -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,
Expand All @@ -359,6 +371,7 @@ describe('actions_connectors_list with show only capability', () => {
},
} as any,
alertTypeRegistry: {} as any,
kibanaFeatures,
};

wrapper = mountWithIntl(
Expand Down Expand Up @@ -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,
Expand All @@ -460,6 +475,7 @@ describe('actions_connectors_list component with disabled items', () => {
},
} as any,
alertTypeRegistry: {} as any,
kibanaFeatures,
};

wrapper = mountWithIntl(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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,
Expand All @@ -111,6 +115,7 @@ describe('alerts_list component empty', () => {
setBreadcrumbs: jest.fn(),
actionTypeRegistry,
alertTypeRegistry,
kibanaFeatures,
};

wrapper = mountWithIntl(
Expand Down Expand Up @@ -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,
Expand All @@ -280,6 +286,7 @@ describe('alerts_list component with items', () => {
setBreadcrumbs: jest.fn(),
actionTypeRegistry,
alertTypeRegistry,
kibanaFeatures,
};

alertTypeRegistry.has.mockReturnValue(true);
Expand Down Expand Up @@ -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,
Expand All @@ -365,6 +373,7 @@ describe('alerts_list component empty with show only capability', () => {
},
} as any,
alertTypeRegistry: {} as any,
kibanaFeatures,
};

wrapper = mountWithIntl(
Expand Down Expand Up @@ -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,
Expand All @@ -480,6 +490,7 @@ describe('alerts_list with show only capability', () => {
setBreadcrumbs: jest.fn(),
actionTypeRegistry,
alertTypeRegistry,
kibanaFeatures,
};

alertTypeRegistry.has.mockReturnValue(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -78,6 +79,7 @@ export const AlertsList: React.FunctionComponent = () => {
docLinks,
charts,
dataPlugin,
kibanaFeatures,
} = useAppDependencies();
const canExecuteActions = hasExecuteActionsCapability(capabilities);

Expand Down Expand Up @@ -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 = [
<TypeFilter
key="type-filter"
onChange={(types: string[]) => 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)),
})
)}
/>,
<ActionTypeFilter
key="action-type-filter"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useEffect, useState } from 'react';
import React, { Fragment, useEffect, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiFilterGroup, EuiPopover, EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui';
import {
EuiFilterGroup,
EuiPopover,
EuiFilterButton,
EuiFilterSelectItem,
EuiTitle,
} from '@elastic/eui';

interface TypeFilterProps {
options: Array<{
value: string;
name: string;
groupName: string;
subOptions: Array<{
value: string;
name: string;
}>;
}>;
onChange?: (selectedTags: string[]) => void;
}
Expand Down Expand Up @@ -52,22 +61,29 @@ export const TypeFilter: React.FunctionComponent<TypeFilterProps> = ({
}
>
<div className="euiFilterSelect__items">
{options.map((item, index) => (
<EuiFilterSelectItem
key={index}
onClick={() => {
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}
</EuiFilterSelectItem>
{options.map((groupItem, groupIndex) => (
<Fragment key={`group${groupIndex}`}>
<EuiTitle data-test-subj={`alertType${groupIndex}Group`} size="xxs">
<h3>{groupItem.groupName}</h3>
</EuiTitle>
{groupItem.subOptions.map((item, index) => (
<EuiFilterSelectItem
key={index}
onClick={() => {
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}
</EuiFilterSelectItem>
))}
</Fragment>
))}
</div>
</EuiPopover>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -22,6 +23,8 @@ export async function getMockedAppDependencies() {
application: { capabilities, navigateToApp },
},
] = await coreSetupMock.getStartServices();
const kibanaFeatures = await featuresPluginMock.createStart().getFeatures();

return {
chrome,
docLinks,
Expand All @@ -37,5 +40,6 @@ export async function getMockedAppDependencies() {
setBreadcrumbs: jest.fn(),
actionTypeRegistry,
alertTypeRegistry,
kibanaFeatures,
};
}
4 changes: 4 additions & 0 deletions x-pack/plugins/triggers_actions_ui/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -52,6 +53,7 @@ interface PluginsStart {
charts: ChartsPluginStart;
alerts?: AlertingStart;
navigateToApp: CoreStart['application']['navigateToApp'];
features: FeaturesPluginStart;
}

export class Plugin
Expand Down Expand Up @@ -112,6 +114,7 @@ export class Plugin
];

const { boot } = await import('./application/boot');
const kibanaFeatures = await pluginsStart.features.getFeatures();

return boot({
dataPlugin: pluginsStart.data,
Expand All @@ -131,6 +134,7 @@ export class Plugin
history: params.history,
actionTypeRegistry,
alertTypeRegistry,
kibanaFeatures,
});
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,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 () => {
Expand Down

0 comments on commit d9c8724

Please sign in to comment.