Skip to content

Commit

Permalink
[APM][ECO] Promote new experience when no apm data found (#188867)
Browse files Browse the repository at this point in the history
closes elastic/observability-dev#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>
  • Loading branch information
kpatticha and kibanamachine committed Jul 23, 2024
1 parent 2cc0332 commit 5d9d92b
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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'];
Expand Down Expand Up @@ -76,7 +79,8 @@ export function ApmMainTemplate({
entityCentricExperience,
false
);
const { isEntityCentricExperienceViewEnabled } = useEntityManagerEnablementContext();
const { isEntityCentricExperienceViewEnabled, serviceInventoryViewLocalStorageSetting } =
useEntityManagerEnablementContext();

const ObservabilityPageTemplate = observabilityShared.navigation.PageTemplate;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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} />
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -37,6 +37,7 @@ export function EntityEnablement() {
} = useKibana<ApmPluginStartDeps>();

const {
isEntityManagerEnabled,
isEnablementPending,
refetch,
setServiceInventoryViewLocalStorageSetting,
Expand All @@ -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();
Expand Down Expand Up @@ -82,66 +88,70 @@ 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}
>
{isEntityCentricExperienceViewEnabled
? 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.',
})}
</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',
{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',
})}
</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}>
Expand All @@ -158,6 +168,7 @@ export function EntityEnablement() {
<Unauthorized
isUnauthorizedModalVisible={isUnauthorizedModalVisible}
onClose={() => setsIsUnauthorizedModalVisible(false)}
label={label}
/>
</EuiFlexGroup>
);
Expand Down
Loading

0 comments on commit 5d9d92b

Please sign in to comment.