diff --git a/x-pack/plugins/actions/server/feature.ts b/x-pack/plugins/actions/server/feature.ts index c06acb676145..321509a7b9de 100644 --- a/x-pack/plugins/actions/server/feature.ts +++ b/x-pack/plugins/actions/server/feature.ts @@ -15,11 +15,17 @@ export const ACTIONS_FEATURE = { icon: 'bell', navLinkId: 'actions', app: [], + management: { + insightsAndAlerting: ['triggersActions'], + }, privileges: { all: { app: [], api: [], catalogue: [], + management: { + insightsAndAlerting: ['triggersActions'], + }, savedObject: { all: [ACTION_SAVED_OBJECT_TYPE, ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE], read: [], @@ -30,6 +36,9 @@ export const ACTIONS_FEATURE = { app: [], api: [], catalogue: [], + management: { + insightsAndAlerting: ['triggersActions'], + }, savedObject: { // action execution requires 'read' over `actions`, but 'all' over `action_task_params` all: [ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE], diff --git a/x-pack/plugins/alerting_builtins/server/feature.ts b/x-pack/plugins/alerting_builtins/server/feature.ts index dd2fe2552ee5..12a19a350f3e 100644 --- a/x-pack/plugins/alerting_builtins/server/feature.ts +++ b/x-pack/plugins/alerting_builtins/server/feature.ts @@ -16,10 +16,16 @@ export const BUILT_IN_ALERTS_FEATURE = { icon: 'bell', navLinkId: 'builtInAlerts', app: [], + management: { + insightsAndAlerting: ['triggersActions'], + }, privileges: { all: { app: [], catalogue: [], + management: { + insightsAndAlerting: ['triggersActions'], + }, alerting: { all: [IndexThreshold], read: [], @@ -29,11 +35,14 @@ export const BUILT_IN_ALERTS_FEATURE = { read: [], }, api: ['actions-read', 'actions-all'], - ui: ['alerting:show', 'actions:show', 'actions:save', 'actions:delete'], + ui: ['actions:save', 'actions:delete'], }, read: { app: [], catalogue: [], + management: { + insightsAndAlerting: ['triggersActions'], + }, alerting: { all: [], read: [IndexThreshold], @@ -43,7 +52,7 @@ export const BUILT_IN_ALERTS_FEATURE = { read: ['action'], }, api: ['actions-read'], - ui: ['alerting:show', 'actions:show'], + ui: [], }, }, }; diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts index 6ca65ac152ee..3abfcba584d7 100644 --- a/x-pack/plugins/alerts/server/plugin.ts +++ b/x-pack/plugins/alerts/server/plugin.ts @@ -129,6 +129,16 @@ export class AlertingPlugin { this.spaces = plugins.spaces?.spacesService; this.security = plugins.security; + core.capabilities.registerProvider(() => { + return { + management: { + insightsAndAlerting: { + triggersActions: true, + }, + }, + }; + }); + this.isESOUsingEphemeralEncryptionKey = plugins.encryptedSavedObjects.usingEphemeralEncryptionKey; 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 80f8e4a3a2c7..1f9352d8405d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -17,8 +17,7 @@ import { ScopedHistory, } from 'kibana/public'; import { Section, routeToAlertDetails } from './constants'; -import { AppContextProvider, useAppDependencies } from './app_context'; -import { hasShowAlertsCapability } from './lib/capabilities'; +import { AppContextProvider } from './app_context'; import { ActionTypeModel, AlertTypeModel } from '../types'; import { TypeRegistry } from './type_registry'; import { ChartsPluginStart } from '../../../../../src/plugins/charts/public'; @@ -63,22 +62,17 @@ export const App = (appDeps: AppDeps) => { }; export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) => { - const { capabilities } = useAppDependencies(); - const canShowAlerts = hasShowAlertsCapability(capabilities); - const DEFAULT_SECTION: Section = canShowAlerts ? 'alerts' : 'connectors'; return ( - {canShowAlerts && ( - - )} - + + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx index eeb8a7771733..ef849dbf1be0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx @@ -45,23 +45,17 @@ export const TriggersActionsUIHome: React.FunctionComponent = []; - if (canShowAlerts) { - tabs.push({ - id: 'alerts', - name: ( - - ), - }); - } + tabs.push({ + id: 'alerts', + name: ( + + ), + }); if (canShowActions) { tabs.push({ @@ -151,17 +145,15 @@ export const TriggersActionsUIHome: React.FunctionComponent )} - {canShowAlerts && ( - ( - - - - )} - /> - )} + ( + + + + )} + /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts index 065a782ee96a..03085b2e8ca2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts @@ -21,11 +21,8 @@ function hasCapability(capabilities: Capabilities, capability: string) { return apps.some((app) => capabilities[app]?.[capability]); } -function createCapabilityCheck(capability: string) { - return (capabilities: Capabilities) => hasCapability(capabilities, capability); -} - -export const hasShowAlertsCapability = createCapabilityCheck('alerting:show'); +export const hasShowAlertsCapability = (capabilities: Capabilities) => + hasCapability(capabilities, 'alerting:show'); export const hasShowActionsCapability = (capabilities: Capabilities) => capabilities?.actions?.show; export const hasSaveActionsCapability = (capabilities: Capabilities) => capabilities?.actions?.save; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index f92d0d4642b3..b6b4e9fd2ad7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -17,6 +17,7 @@ import { EuiBetaBadge, EuiToolTip, EuiButtonIcon, + EuiEmptyPrompt, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -344,12 +345,25 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { ); const noPermissionPrompt = ( -

- -

+ + + + } + body={ +

+ +

+ } + /> ); return ( 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 4056cdaa0235..2b2897a2181b 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 @@ -18,6 +18,7 @@ import { EuiSpacer, EuiLink, EuiLoadingSpinner, + EuiEmptyPrompt, } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; @@ -92,7 +93,7 @@ export const AlertsList: React.FunctionComponent = () => { useEffect(() => { loadAlertsData(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [page, searchText, typesFilter, actionTypesFilter]); + }, [alertTypesState, page, searchText, typesFilter, actionTypesFilter]); useEffect(() => { (async () => { @@ -135,30 +136,33 @@ export const AlertsList: React.FunctionComponent = () => { }, []); async function loadAlertsData() { - setAlertsState({ ...alertsState, isLoading: true }); - try { - const alertsResponse = await loadAlerts({ - http, - page, - searchText, - typesFilter, - actionTypesFilter, - }); - setAlertsState({ - isLoading: false, - data: alertsResponse.data, - totalItemCount: alertsResponse.total, - }); - } catch (e) { - toastNotifications.addDanger({ - title: i18n.translate( - 'xpack.triggersActionsUI.sections.alertsList.unableToLoadAlertsMessage', - { - defaultMessage: 'Unable to load alerts', - } - ), - }); - setAlertsState({ ...alertsState, isLoading: false }); + const hasAnyAuthorizedAlertType = alertTypesState.data.size > 0; + if (hasAnyAuthorizedAlertType) { + setAlertsState({ ...alertsState, isLoading: true }); + try { + const alertsResponse = await loadAlerts({ + http, + page, + searchText, + typesFilter, + actionTypesFilter, + }); + setAlertsState({ + isLoading: false, + data: alertsResponse.data, + totalItemCount: alertsResponse.total, + }); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.unableToLoadAlertsMessage', + { + defaultMessage: 'Unable to load alerts', + } + ), + }); + setAlertsState({ ...alertsState, isLoading: false }); + } } } @@ -247,6 +251,9 @@ export const AlertsList: React.FunctionComponent = () => { ]; const authorizedAlertTypes = [...alertTypesState.data.values()]; + const authorizedToCreateAnyAlerts = authorizedAlertTypes.some( + (alertType) => alertType.authorizedConsumers[ALERTS_FEATURE_ID]?.all + ); const toolsRight = [ { />, ]; - if ( - authorizedAlertTypes.some((alertType) => alertType.authorizedConsumers[ALERTS_FEATURE_ID]?.all) - ) { + if (authorizedToCreateAnyAlerts) { toolsRight.push( { - ) : ( + ) : authorizedToCreateAnyAlerts ? ( setAlertFlyoutVisibility(true)} /> + ) : ( + noPermissionPrompt )} { ); }; +const noPermissionPrompt = ( + + + + } + body={ +

+ +

+ } + /> +); + function filterAlertsById(alerts: Alert[], ids: string[]): Alert[] { return alerts.filter((alert) => ids.includes(alert.id)); } diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index db93e48ab369..c5bc48e0f96e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -4,15 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreStart, PluginInitializerContext, Plugin as CorePlugin } from 'src/core/public'; +import { + CoreStart, + CoreSetup, + PluginInitializerContext, + Plugin as CorePlugin, +} from 'src/core/public'; import { i18n } from '@kbn/i18n'; import { registerBuiltInActionTypes } from './application/components/builtin_action_types'; import { registerBuiltInAlertTypes } from './application/components/builtin_alert_types'; -import { hasShowActionsCapability, hasShowAlertsCapability } from './application/lib/capabilities'; import { ActionTypeModel, AlertTypeModel } from './types'; import { TypeRegistry } from './application/type_registry'; -import { ManagementStart, ManagementSectionId } from '../../../../src/plugins/management/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { boot } from './application/boot'; import { ChartsPluginStart } from '../../../../src/plugins/charts/public'; import { PluginStartContract as AlertingStart } from '../../alerts/public'; @@ -28,17 +32,25 @@ export interface TriggersAndActionsUIPublicPluginStart { alertTypeRegistry: TypeRegistry; } +interface PluginsSetup { + management: ManagementSetup; +} + interface PluginsStart { data: DataPublicPluginStart; charts: ChartsPluginStart; - management: ManagementStart; alerts?: AlertingStart; navigateToApp: CoreStart['application']['navigateToApp']; } export class Plugin implements - CorePlugin { + CorePlugin< + TriggersAndActionsUIPublicPluginSetup, + TriggersAndActionsUIPublicPluginStart, + PluginsSetup, + PluginsStart + > { private actionTypeRegistry: TypeRegistry; private alertTypeRegistry: TypeRegistry; @@ -50,7 +62,10 @@ export class Plugin this.alertTypeRegistry = alertTypeRegistry; } - public setup(): TriggersAndActionsUIPublicPluginSetup { + public setup( + core: CoreSetup, + plugins: PluginsSetup + ): TriggersAndActionsUIPublicPluginSetup { registerBuiltInActionTypes({ actionTypeRegistry: this.actionTypeRegistry, }); @@ -59,50 +74,44 @@ export class Plugin alertTypeRegistry: this.alertTypeRegistry, }); + plugins.management.sections.getSection(ManagementSectionId.InsightsAndAlerting).registerApp({ + id: 'triggersActions', + title: i18n.translate('xpack.triggersActionsUI.managementSection.displayName', { + defaultMessage: 'Alerts and Actions', + }), + order: 0, + mount: async (params) => { + const [coreStart, startPlugins] = await core.getStartServices(); + boot({ + dataPlugin: startPlugins.data, + charts: startPlugins.charts, + alerts: startPlugins.alerts, + element: params.element, + toastNotifications: core.notifications.toasts, + http: core.http, + uiSettings: core.uiSettings, + docLinks: coreStart.docLinks, + chrome: coreStart.chrome, + savedObjects: coreStart.savedObjects.client, + I18nContext: coreStart.i18n.Context, + capabilities: coreStart.application.capabilities, + navigateToApp: coreStart.application.navigateToApp, + setBreadcrumbs: params.setBreadcrumbs, + history: params.history, + actionTypeRegistry: this.actionTypeRegistry, + alertTypeRegistry: this.alertTypeRegistry, + }); + return () => {}; + }, + }); + return { actionTypeRegistry: this.actionTypeRegistry, alertTypeRegistry: this.alertTypeRegistry, }; } - public start(core: CoreStart, plugins: PluginsStart): TriggersAndActionsUIPublicPluginStart { - const { capabilities } = core.application; - - const canShowActions = hasShowActionsCapability(capabilities); - const canShowAlerts = hasShowAlertsCapability(capabilities); - - // Don't register routes when user doesn't have access to the application - if (canShowActions || canShowAlerts) { - plugins.management.sections.getSection(ManagementSectionId.InsightsAndAlerting).registerApp({ - id: 'triggersActions', - title: i18n.translate('xpack.triggersActionsUI.managementSection.displayName', { - defaultMessage: 'Alerts and Actions', - }), - order: 0, - mount: (params) => { - boot({ - dataPlugin: plugins.data, - charts: plugins.charts, - alerts: plugins.alerts, - element: params.element, - toastNotifications: core.notifications.toasts, - http: core.http, - uiSettings: core.uiSettings, - docLinks: core.docLinks, - chrome: core.chrome, - savedObjects: core.savedObjects.client, - I18nContext: core.i18n.Context, - capabilities: core.application.capabilities, - navigateToApp: core.application.navigateToApp, - setBreadcrumbs: params.setBreadcrumbs, - history: params.history, - actionTypeRegistry: this.actionTypeRegistry, - alertTypeRegistry: this.alertTypeRegistry, - }); - return () => {}; - }, - }); - } + public start(): TriggersAndActionsUIPublicPluginStart { return { actionTypeRegistry: this.actionTypeRegistry, alertTypeRegistry: this.alertTypeRegistry,