mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[APM][ECO] Promote new experience when no apm data found (#188867)
closes https://github.com/elastic/observability-dev/issues/3737 ## Summary - When FF is disabled it shows the existing no data page - The no data config is very limited in the template, thus we had to go against the guidelines and create a custom no data page for the new experience. - The user needs to have permissions to enable EEM, othewise same modal appears with slightly different copy Additionally, the PR includes - Small refactoring in the enablement component in order to share it - Add short link that was misseed https://github.com/user-attachments/assets/5d3bbe83-682a-47a1-a9af-770f1ca42876 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
2cc03329e4
commit
5d9d92b88e
6 changed files with 232 additions and 56 deletions
|
@ -11,6 +11,7 @@ import { entityCentricExperience } from '@kbn/observability-plugin/common';
|
|||
import { ObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public';
|
||||
import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template';
|
||||
import React, { useContext } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { FeatureFeedbackButton } from '@kbn/observability-shared-plugin/public';
|
||||
import { useEntityManagerEnablementContext } from '../../../context/entity_manager_context/use_entity_manager_enablement_context';
|
||||
|
@ -26,6 +27,8 @@ import { ApmEnvironmentFilter } from '../../shared/environment_filter';
|
|||
import { getNoDataConfig } from './no_data_config';
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { EntityEnablement } from '../../shared/entity_enablement';
|
||||
import { CustomNoDataTemplate } from './custom_no_data_template';
|
||||
import { ServiceInventoryView } from '../../../context/entity_manager_context/entity_manager_context';
|
||||
|
||||
// Paths that must skip the no data screen
|
||||
const bypassNoDataScreenPaths = ['/settings', '/diagnostics'];
|
||||
|
@ -76,7 +79,8 @@ export function ApmMainTemplate({
|
|||
entityCentricExperience,
|
||||
false
|
||||
);
|
||||
const { isEntityCentricExperienceViewEnabled } = useEntityManagerEnablementContext();
|
||||
const { isEntityCentricExperienceViewEnabled, serviceInventoryViewLocalStorageSetting } =
|
||||
useEntityManagerEnablementContext();
|
||||
|
||||
const ObservabilityPageTemplate = observabilityShared.navigation.PageTemplate;
|
||||
|
||||
|
@ -114,6 +118,10 @@ export function ApmMainTemplate({
|
|||
|
||||
const hasApmData = !!data?.hasData;
|
||||
const hasApmIntegrations = !!fleetApmPoliciesData?.hasApmPolicies;
|
||||
const showCustomEmptyState =
|
||||
!hasApmData &&
|
||||
isEntityCentricExperienceSettingEnabled &&
|
||||
serviceInventoryViewLocalStorageSetting === ServiceInventoryView.classic;
|
||||
|
||||
const noDataConfig = getNoDataConfig({
|
||||
basePath,
|
||||
|
@ -160,9 +168,16 @@ export function ApmMainTemplate({
|
|||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
const pageTemplate = (
|
||||
const pageTemplate = showCustomEmptyState ? (
|
||||
<CustomNoDataTemplate isPageDataLoaded={isLoading === false} noDataConfig={noDataConfig} />
|
||||
) : (
|
||||
<ObservabilityPageTemplate
|
||||
noDataConfig={shouldBypassNoDataScreen ? undefined : noDataConfig}
|
||||
noDataConfig={
|
||||
shouldBypassNoDataScreen ||
|
||||
serviceInventoryViewLocalStorageSetting === ServiceInventoryView.entity
|
||||
? undefined
|
||||
: noDataConfig
|
||||
}
|
||||
isPageDataLoaded={isLoading === false}
|
||||
pageHeader={{
|
||||
rightSideItems,
|
||||
|
@ -173,7 +188,15 @@ export function ApmMainTemplate({
|
|||
{isEntityCentricExperienceSettingEnabled &&
|
||||
showEnablementCallout &&
|
||||
selectedNavButton === 'allServices' ? (
|
||||
<EntityEnablement />
|
||||
<EntityEnablement
|
||||
label={i18n.translate('xpack.apm.eemEnablement.tryItButton.', {
|
||||
defaultMessage: 'Try our new experience!',
|
||||
})}
|
||||
tooltip={i18n.translate('xpack.apm.entityEnablement.content', {
|
||||
defaultMessage:
|
||||
'Our new experience combines both APM-instrumented services with services detected from logs in a single service inventory.',
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
{showServiceGroupsNav && selectedNavButton && (
|
||||
<ServiceGroupsButtonGroup selectedNavButton={selectedNavButton} />
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiTextColor,
|
||||
EuiText,
|
||||
EuiButton,
|
||||
EuiPageTemplate,
|
||||
EuiCard,
|
||||
EuiImage,
|
||||
EuiScreenReaderOnly,
|
||||
} from '@elastic/eui';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { KibanaSolutionAvatar } from '@kbn/shared-ux-avatar-solution';
|
||||
import { NoDataConfig } from '@kbn/shared-ux-page-no-data-config-types';
|
||||
import { ApmPluginStartDeps } from '../../../plugin';
|
||||
import { EntityEnablement } from '../../shared/entity_enablement';
|
||||
|
||||
export function CustomNoDataTemplate({
|
||||
isPageDataLoaded,
|
||||
noDataConfig,
|
||||
}: {
|
||||
isPageDataLoaded: boolean;
|
||||
noDataConfig?: NoDataConfig;
|
||||
}) {
|
||||
const { services } = useKibana<ApmPluginStartDeps>();
|
||||
const { http, observabilityShared } = services;
|
||||
const basePath = http?.basePath.get();
|
||||
|
||||
const ObservabilityPageTemplate = observabilityShared.navigation.PageTemplate;
|
||||
const imageUrl = `${basePath}/plugins/kibanaReact/assets/elastic_agent_card.svg`;
|
||||
|
||||
return (
|
||||
<ObservabilityPageTemplate isPageDataLoaded={isPageDataLoaded} paddingSize="none">
|
||||
<EuiPageTemplate panelled={false} offset={0} restrictWidth="960px">
|
||||
<EuiPageTemplate.Section alignment="center" component="div" grow>
|
||||
<EuiText textAlign="center">
|
||||
<KibanaSolutionAvatar name="observability" iconType="logoObservability" size="xxl" />
|
||||
<EuiSpacer size="l" />
|
||||
<h1>
|
||||
{i18n.translate('xpack.apm.customEmtpyState.title', {
|
||||
defaultMessage: 'Detect and resolve problems with your application',
|
||||
})}
|
||||
</h1>
|
||||
<EuiTextColor color="subdued">
|
||||
<p>
|
||||
{i18n.translate('xpack.apm.customEmtpyState.description', {
|
||||
defaultMessage:
|
||||
'Start collecting data for your applications and services so you can detect and resolve problems faster.',
|
||||
})}
|
||||
</p>
|
||||
</EuiTextColor>
|
||||
</EuiText>
|
||||
<EuiSpacer size="xxl" />
|
||||
<EuiCard
|
||||
css={{ maxWidth: 400, marginInline: 'auto' }}
|
||||
paddingSize="l"
|
||||
title={
|
||||
<EuiScreenReaderOnly>
|
||||
<span>
|
||||
{i18n.translate('xpack.apm.customEmtpyState.title.reader', {
|
||||
defaultMessage: 'Add APM data',
|
||||
})}
|
||||
</span>
|
||||
</EuiScreenReaderOnly>
|
||||
}
|
||||
description={i18n.translate('xpack.apm.customEmtpyState.card.description', {
|
||||
defaultMessage:
|
||||
'Use APM agents to collect APM data. We make it easy with agents for many popular languages.',
|
||||
})}
|
||||
footer={
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="apmCustomNoDataTemplateAddApmAgentButton"
|
||||
color="primary"
|
||||
fill
|
||||
href={noDataConfig?.action.elasticAgent.href}
|
||||
>
|
||||
{noDataConfig?.action.elasticAgent.title}
|
||||
</EuiButton>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<EntityEnablement
|
||||
label={i18n.translate('xpack.apm.customEmtpyState.card.link', {
|
||||
defaultMessage: 'Try creating services from logs',
|
||||
})}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
image={
|
||||
<EuiImage
|
||||
size="fullWidth"
|
||||
style={{
|
||||
width: 'max(100%, 360px)',
|
||||
height: 240,
|
||||
objectFit: 'cover',
|
||||
background: 'aliceblue',
|
||||
}}
|
||||
url={imageUrl}
|
||||
alt={i18n.translate('xpack.apm.customEmtpyState.img.alt', {
|
||||
defaultMessage: 'Image of the Elastic Agent card',
|
||||
})}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiPageTemplate.Section>
|
||||
</EuiPageTemplate>
|
||||
</ObservabilityPageTemplate>
|
||||
);
|
||||
}
|
|
@ -28,7 +28,7 @@ import { FeedbackModal } from './feedback_modal';
|
|||
import { ServiceInventoryView } from '../../../context/entity_manager_context/entity_manager_context';
|
||||
import { Unauthorized } from './unauthorized_modal';
|
||||
|
||||
export function EntityEnablement() {
|
||||
export function EntityEnablement({ label, tooltip }: { label: string; tooltip?: string }) {
|
||||
const [isFeedbackModalVisible, setsIsFeedbackModalVisible] = useState(false);
|
||||
const [isUnauthorizedModalVisible, setsIsUnauthorizedModalVisible] = useState(false);
|
||||
|
||||
|
@ -37,6 +37,7 @@ export function EntityEnablement() {
|
|||
} = useKibana<ApmPluginStartDeps>();
|
||||
|
||||
const {
|
||||
isEntityManagerEnabled,
|
||||
isEnablementPending,
|
||||
refetch,
|
||||
setServiceInventoryViewLocalStorageSetting,
|
||||
|
@ -52,6 +53,11 @@ export function EntityEnablement() {
|
|||
};
|
||||
|
||||
const handleEnablement = async () => {
|
||||
if (isEntityManagerEnabled) {
|
||||
setServiceInventoryViewLocalStorageSetting(ServiceInventoryView.entity);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await entityManager.entityClient.enableManagedEntityDiscovery();
|
||||
|
@ -82,11 +88,15 @@ export function EntityEnablement() {
|
|||
) : (
|
||||
<EuiFlexGroup direction="row" alignItems="center" gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
{isLoading ? <EuiLoadingSpinner size="m" /> : <TechnicalPreviewBadge icon="beaker" />}
|
||||
{isLoading ? (
|
||||
<EuiLoadingSpinner size="m" />
|
||||
) : (
|
||||
<TechnicalPreviewBadge icon="beaker" style={{ verticalAlign: 'middle' }} />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
disabled={isEntityCentricExperienceViewEnabled}
|
||||
disabled={isEntityCentricExperienceViewEnabled || isLoading}
|
||||
data-test-subj="tryOutEEMLink"
|
||||
onClick={handleEnablement}
|
||||
>
|
||||
|
@ -94,54 +104,54 @@ export function EntityEnablement() {
|
|||
? i18n.translate('xpack.apm.eemEnablement.enabled.', {
|
||||
defaultMessage: 'Viewing our new experience',
|
||||
})
|
||||
: i18n.translate('xpack.apm.eemEnablement.tryItButton.', {
|
||||
defaultMessage: 'Try our new experience!',
|
||||
})}
|
||||
: label}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
onClick={togglePopover}
|
||||
data-test-subj="apmEntityEnablementWithFooterButton"
|
||||
iconType="iInCircle"
|
||||
size="xs"
|
||||
aria-label={i18n.translate('xpack.apm.entityEnablement.euiButtonIcon.arial', {
|
||||
defaultMessage: 'click to find more for the new ui experience',
|
||||
})}
|
||||
/>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={togglePopover}
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<div style={{ width: '300px' }}>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
{i18n.translate('xpack.apm.entityEnablement.content', {
|
||||
defaultMessage:
|
||||
'Our new experience combines both APM-instrumented services with services detected from logs in a single service inventory.',
|
||||
{tooltip && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
onClick={togglePopover}
|
||||
data-test-subj="apmEntityEnablementWithFooterButton"
|
||||
iconType="iInCircle"
|
||||
size="xs"
|
||||
aria-label={i18n.translate('xpack.apm.entityEnablement.euiButtonIcon.arial', {
|
||||
defaultMessage: 'click to find more for the new ui experience',
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
</div>
|
||||
<EuiPopoverFooter>
|
||||
<EuiTextColor color="subdued">
|
||||
<EuiLink
|
||||
data-test-subj="apmEntityEnablementLink"
|
||||
href="https://ela.st/new-experience-services"
|
||||
external
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.translate('xpack.apm.entityEnablement.footer', {
|
||||
defaultMessage: 'Learn more',
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiTextColor>
|
||||
</EuiPopoverFooter>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
/>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={togglePopover}
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<div style={{ width: '300px' }}>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
{i18n.translate('xpack.apm.entityEnablement.content', {
|
||||
defaultMessage:
|
||||
'Our new experience combines both APM-instrumented services with services detected from logs in a single service inventory.',
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
</div>
|
||||
<EuiPopoverFooter>
|
||||
<EuiTextColor color="subdued">
|
||||
<EuiLink
|
||||
data-test-subj="apmEntityEnablementLink"
|
||||
href="https://ela.st/new-experience-services"
|
||||
external
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.translate('xpack.apm.entityEnablement.footer', {
|
||||
defaultMessage: 'Learn more',
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiTextColor>
|
||||
</EuiPopoverFooter>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{isEntityCentricExperienceViewEnabled && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink data-test-subj="restoreClassicView" onClick={handleRestoreView}>
|
||||
|
@ -158,6 +168,7 @@ export function EntityEnablement() {
|
|||
<Unauthorized
|
||||
isUnauthorizedModalVisible={isUnauthorizedModalVisible}
|
||||
onClose={() => setsIsUnauthorizedModalVisible(false)}
|
||||
label={label}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiImage,
|
||||
EuiLink,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
|
@ -32,9 +33,11 @@ import { useKibanaUrl } from '../../../hooks/use_kibana_url';
|
|||
export function Unauthorized({
|
||||
isUnauthorizedModalVisible = false,
|
||||
onClose,
|
||||
label,
|
||||
}: {
|
||||
isUnauthorizedModalVisible?: boolean;
|
||||
onClose: () => void;
|
||||
label: string;
|
||||
}) {
|
||||
const servicesInventory = useKibanaUrl('/plugins/apm/assets/services_inventory.png');
|
||||
|
||||
|
@ -54,6 +57,18 @@ export function Unauthorized({
|
|||
})}
|
||||
</EuiButton>
|
||||
}
|
||||
cancelButtonText={
|
||||
<EuiLink
|
||||
target="_blank"
|
||||
data-test-subj="apmUnauthorizedLinkExternal"
|
||||
href="https://ela.st/new-experience-services"
|
||||
external
|
||||
>
|
||||
{i18n.translate('xpack.apm.unauthorized.linkLinkLabel', {
|
||||
defaultMessage: 'See how to enable EEM',
|
||||
})}
|
||||
</EuiLink>
|
||||
}
|
||||
defaultFocusedButton="confirm"
|
||||
>
|
||||
<EuiPanel hasShadow={false}>
|
||||
|
@ -83,7 +98,8 @@ export function Unauthorized({
|
|||
<p>
|
||||
{i18n.translate('xpack.apm.unauthorised.body', {
|
||||
defaultMessage:
|
||||
'To see services detected from logs and APM-instrumented services in our new service inventory, please ask an administrator to visit this page and try our new experience. ',
|
||||
'To see services detected from logs and APM-instrumented services in our new service inventory, please ask an administrator to visit this page and {label}. ',
|
||||
values: { label: label.toLowerCase() },
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
|
|
|
@ -123,7 +123,9 @@
|
|||
"@kbn/test-jest-helpers",
|
||||
"@kbn/security-plugin-types-common",
|
||||
"@kbn/entityManager-plugin",
|
||||
"@kbn/react-hooks"
|
||||
"@kbn/react-hooks",
|
||||
"@kbn/shared-ux-avatar-solution",
|
||||
"@kbn/shared-ux-page-no-data-config-types"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiSideNavItemType, EuiPageSectionProps } from '@elastic/eui';
|
||||
import { EuiSideNavItemType, EuiPageSectionProps, EuiEmptyPromptProps } from '@elastic/eui';
|
||||
import { _EuiPageBottomBarProps } from '@elastic/eui/src/components/page_template/bottom_bar/page_bottom_bar';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useMemo } from 'react';
|
||||
|
@ -39,7 +39,7 @@ export type WrappedPageTemplateProps = Pick<
|
|||
bottomBar?: React.ReactNode;
|
||||
bottomBarProps?: _EuiPageBottomBarProps;
|
||||
topSearchBar?: React.ReactNode;
|
||||
};
|
||||
} & Pick<EuiEmptyPromptProps, 'paddingSize'>;
|
||||
|
||||
export interface NavigationEntry {
|
||||
// the label of the menu entry, should be translated
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue