diff --git a/x-pack/plugins/observability_solution/inventory/.storybook/get_mock_inventory_context.tsx b/x-pack/plugins/observability_solution/inventory/.storybook/get_mock_inventory_context.tsx index da59f29c578429..6739e21abe2802 100644 --- a/x-pack/plugins/observability_solution/inventory/.storybook/get_mock_inventory_context.tsx +++ b/x-pack/plugins/observability_solution/inventory/.storybook/get_mock_inventory_context.tsx @@ -6,6 +6,7 @@ */ import { coreMock } from '@kbn/core/public/mocks'; +import { EntityManagerPublicPluginStart } from '@kbn/entityManager-plugin/public'; import type { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; import type { InferencePublicStart } from '@kbn/inference-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; @@ -17,6 +18,7 @@ export function getMockInventoryContext(): InventoryKibanaContext { return { ...coreStart, + entityManager: {} as unknown as EntityManagerPublicPluginStart, observabilityShared: {} as unknown as ObservabilitySharedPluginStart, inference: {} as unknown as InferencePublicStart, share: {} as unknown as SharePluginStart, diff --git a/x-pack/plugins/observability_solution/inventory/public/assets/entities_inventory_dark.png b/x-pack/plugins/observability_solution/inventory/public/assets/entities_inventory_dark.png new file mode 100644 index 00000000000000..991abf611a0052 Binary files /dev/null and b/x-pack/plugins/observability_solution/inventory/public/assets/entities_inventory_dark.png differ diff --git a/x-pack/plugins/observability_solution/inventory/public/assets/entities_inventory_light.png b/x-pack/plugins/observability_solution/inventory/public/assets/entities_inventory_light.png new file mode 100644 index 00000000000000..f313d3df548424 Binary files /dev/null and b/x-pack/plugins/observability_solution/inventory/public/assets/entities_inventory_light.png differ diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entity_enablement/enable_entity_model_button.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entity_enablement/enable_entity_model_button.tsx new file mode 100644 index 00000000000000..6f13c33585bca5 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/entity_enablement/enable_entity_model_button.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useState } from 'react'; +import { EntityManagerUnauthorizedError } from '@kbn/entityManager-plugin/public'; +import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public'; +import { useKibana } from '../../hooks/use_kibana'; +import { Unauthorized } from './unauthorized_modal'; + +export function EnableEntityModelButton({ onSuccess }: { onSuccess: () => void }) { + const { + services: { entityManager, notifications }, + } = useKibana(); + + const [isLoading, setIsLoading] = useState(false); + const [showModal, setModalVisible] = useState(false); + + const handleEnablement = async () => { + setIsLoading(true); + try { + const response = await entityManager.entityClient.enableManagedEntityDiscovery(); + + if (response.success) { + setIsLoading(false); + onSuccess(); + } else { + throw new Error(response.message); + } + } catch (error) { + setIsLoading(false); + + if (error instanceof EntityManagerUnauthorizedError) { + setModalVisible(true); + return; + } + + const err = error as Error | IHttpFetchError; + notifications.toasts.addDanger({ + title: i18n.translate('xpack.inventory.eemEnablement.errorTitle', { + defaultMessage: 'Error while enabling the new entity model', + }), + text: 'response' in err ? err.body?.message ?? err.response?.statusText : err.message, + }); + } + }; + + return ( + <> + + {i18n.translate('xpack.inventory.noData.card.button', { + defaultMessage: 'Enable', + })} + + {showModal ? setModalVisible(false)} /> : null} + + ); +} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entity_enablement/unauthorized_modal.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entity_enablement/unauthorized_modal.tsx new file mode 100644 index 00000000000000..790240e66e4338 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/entity_enablement/unauthorized_modal.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButton, + EuiConfirmModal, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiImage, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, + useEuiTheme, + COLOR_MODES_STANDARD, +} from '@elastic/eui'; +import inventoryLight from '../../assets/entities_inventory_light.png'; +import inventoryDark from '../../assets/entities_inventory_dark.png'; + +export function Unauthorized({ onClose }: { onClose: () => void }) { + const { colorMode } = useEuiTheme(); + + return ( + + {i18n.translate('xpack.inventory.unauthorised.button', { + defaultMessage: 'OK', + })} + + } + cancelButtonText={ + + {i18n.translate('xpack.inventory.unauthorized.linkLabel', { + defaultMessage: 'Learn more', + })} + + } + defaultFocusedButton="confirm" + > + + + + + + + +

+ {i18n.translate('xpack.inventory.unauthorised.title', { + defaultMessage: 'Insufficient permissions', + })} +

+
+
+
+
+ + + +

+ {i18n.translate('xpack.inventory.unauthorised.body', { + defaultMessage: + "You don't have permissions to turn on the Elastic Entity Model. Please ask your administrator to enable this for you so you can see everything you have in one place.", + })} +

+
+
+ + + + +
+ ); +} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entity_enablement/welcome_modal.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entity_enablement/welcome_modal.tsx new file mode 100644 index 00000000000000..18adc9f87c43e8 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/entity_enablement/welcome_modal.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButton, + EuiConfirmModal, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiImage, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, + useEuiTheme, + COLOR_MODES_STANDARD, +} from '@elastic/eui'; +import inventoryLight from '../../assets/entities_inventory_light.png'; +import inventoryDark from '../../assets/entities_inventory_dark.png'; + +export function Welcome({ onClose, onConfirm }: { onClose: () => void; onConfirm: () => void }) { + const { colorMode } = useEuiTheme(); + + return ( + + {i18n.translate('xpack.inventory.welcome.button', { + defaultMessage: 'OK', + })} + + } + cancelButtonText={ + + {i18n.translate('xpack.inventory.welcome.linkLabel', { + defaultMessage: 'Learn more', + })} + + } + defaultFocusedButton="confirm" + > + + + + + + + +

+ {i18n.translate('xpack.inventory.welcome.title', { + defaultMessage: 'See everything you have in one place!', + })} +

+
+
+
+
+ + + +

+ {i18n.translate('xpack.inventory.welcome.body', { + defaultMessage: + 'The inventory will show all of your observed entities in one place so you can detect and resolve problems with them faster.', + })} +

+
+
+ + + + +
+ ); +} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/index.tsx index 7c5d94fe26449b..77f47cd8235b38 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/index.tsx @@ -7,6 +7,9 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useKibana } from '../../hooks/use_kibana'; +import { getEntityManagerEnablement } from './no_data_config'; +import { useEntityManager } from '../../hooks/use_entity_manager'; +import { Welcome } from '../entity_enablement/welcome_modal'; export function InventoryPageTemplate({ children }: { children: React.ReactNode }) { const { @@ -14,9 +17,26 @@ export function InventoryPageTemplate({ children }: { children: React.ReactNode } = useKibana(); const { PageTemplate: ObservabilityPageTemplate } = observabilityShared.navigation; + const { + isEntityManagerEnabled, + isEnablementLoading, + refresh, + showWelcomedModal, + toggleWelcomedModal, + } = useEntityManager(); + + const handleSuccess = () => { + refresh(); + toggleWelcomedModal(); + }; return ( {children} + {showWelcomedModal ? ( + + ) : null} ); } diff --git a/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/no_data_config.tsx b/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/no_data_config.tsx new file mode 100644 index 00000000000000..3b12e11d2ba7cf --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/no_data_config.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiLink } from '@elastic/eui'; +import { EnableEntityModelButton } from '../entity_enablement/enable_entity_model_button'; + +export function getEntityManagerEnablement({ + enabled, + loading, + onSuccess, +}: { + enabled: boolean; + loading: boolean; + onSuccess: () => void; +}) { + if (enabled || loading) { + return; + } + + return { + solution: i18n.translate('xpack.inventory.noData.solutionName', { + defaultMessage: 'Observability', + }), + action: { + elasticAgent: { + description: ( + + {i18n.translate('xpack.inventory.noData.card.description.link', { + defaultMessage: 'Elastic Entity Model', + })} + + ), + }} + /> + ), + button: , + onClick: (event: React.MouseEvent) => { + event.preventDefault(); + }, + }, + }, + pageTitle: i18n.translate('xpack.inventory.noData.page.title', { + defaultMessage: 'See everything you have in one place!', + }), + pageDescription: i18n.translate('xpack.inventory.noData.page.description', { + defaultMessage: + 'The inventory will show all of your observed entities in one place so you can detect and resolve problems with them faster!', + }), + }; +} diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_entity_manager.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_entity_manager.ts new file mode 100644 index 00000000000000..1082017e1ad7a9 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_entity_manager.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useAbortableAsync } from '@kbn/observability-utils/hooks/use_abortable_async'; +import { useState } from 'react'; +import { useKibana } from './use_kibana'; + +export const useEntityManager = () => { + const { + services: { entityManager }, + } = useKibana(); + + const [showWelcomedModal, setWelcomedModal] = useState(false); + + const { + value = { enabled: false }, + loading, + refresh, + } = useAbortableAsync( + ({ signal }) => { + return entityManager.entityClient.isManagedEntityDiscoveryEnabled(); + }, + [entityManager] + ); + + return { + isEntityManagerEnabled: value.enabled, + isEnablementLoading: loading, + refresh, + showWelcomedModal, + toggleWelcomedModal: () => setWelcomedModal((state) => !state), + }; +}; diff --git a/x-pack/plugins/observability_solution/inventory/public/types.ts b/x-pack/plugins/observability_solution/inventory/public/types.ts index 88a3188c45a578..47bc622048ed51 100644 --- a/x-pack/plugins/observability_solution/inventory/public/types.ts +++ b/x-pack/plugins/observability_solution/inventory/public/types.ts @@ -8,6 +8,10 @@ import type { ObservabilitySharedPluginStart, ObservabilitySharedPluginSetup, } from '@kbn/observability-shared-plugin/public'; +import { + EntityManagerPublicPluginSetup, + EntityManagerPublicPluginStart, +} from '@kbn/entityManager-plugin/public'; import type { InferencePublicStart, InferencePublicSetup } from '@kbn/inference-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; @@ -18,11 +22,13 @@ export interface ConfigSchema {} export interface InventorySetupDependencies { observabilityShared: ObservabilitySharedPluginSetup; inference: InferencePublicSetup; + entityManager: EntityManagerPublicPluginSetup; } export interface InventoryStartDependencies { observabilityShared: ObservabilitySharedPluginStart; inference: InferencePublicStart; + entityManager: EntityManagerPublicPluginStart; share: SharePluginStart; }