diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/empty_prompt.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/empty_prompt.tsx new file mode 100644 index 0000000000000..128b5696b23b8 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/empty_prompt.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiEmptyPrompt } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { SetupInstructionsLink } from '../../shared/Links/SetupInstructionsLink'; + +export function EmptyPrompt() { + return ( + + {i18n.translate('xpack.apm.serviceMap.noServicesPromptTitle', { + defaultMessage: 'No services available', + })} + + } + body={ +

+ {i18n.translate('xpack.apm.serviceMap.noServicesPromptDescription', { + defaultMessage: + 'We can’t find any services to map within the currently selected time range and environment. Please try another range or check the environment selected. If you don’t have any services, please use our setup instructions to get started.', + })} +

+ } + actions={} + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx index f504ac1f68ce1..2a5b4ce44ff46 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx @@ -9,16 +9,30 @@ import { CoreStart } from 'kibana/public'; import React, { ReactNode } from 'react'; import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; import { License } from '../../../../../licensing/common/license'; +import { EuiThemeProvider } from '../../../../../observability/public'; +import { FETCH_STATUS } from '../../../../../observability/public/hooks/use_fetcher'; import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; import { LicenseContext } from '../../../context/LicenseContext'; +import * as useFetcherModule from '../../../hooks/useFetcher'; import { ServiceMap } from './'; const KibanaReactContext = createKibanaReactContext({ usageCollection: { reportUiStats: () => {} }, } as Partial); +const activeLicense = new License({ + signature: 'active test signature', + license: { + expiryDateInMillis: 0, + mode: 'platinum', + status: 'active', + type: 'platinum', + uid: '1', + }, +}); + const expiredLicense = new License({ - signature: 'test signature', + signature: 'expired test signature', license: { expiryDateInMillis: 0, mode: 'platinum', @@ -28,26 +42,58 @@ const expiredLicense = new License({ }, }); -function Wrapper({ children }: { children?: ReactNode }) { - return ( - - - {children} - - - ); +function createWrapper(license: License | null) { + return ({ children }: { children?: ReactNode }) => { + return ( + + + + + {children} + + + + + ); + }; } describe('ServiceMap', () => { - describe('with an inactive license', () => { + describe('with no license', () => { + it('renders null', async () => { + expect( + await render(, { + wrapper: createWrapper(null), + }).queryByTestId('ServiceMap') + ).not.toBeInTheDocument(); + }); + }); + + describe('with an expired license', () => { it('renders the license banner', async () => { expect( - ( + await render(, { + wrapper: createWrapper(expiredLicense), + }).findAllByText(/Platinum/) + ).toHaveLength(1); + }); + }); + + describe('with an active license', () => { + describe('with an empty response', () => { + it('renders the empty banner', async () => { + jest.spyOn(useFetcherModule, 'useFetcher').mockReturnValueOnce({ + data: { elements: [] }, + refetch: () => {}, + status: FETCH_STATUS.SUCCESS, + }); + + expect( await render(, { - wrapper: Wrapper, - }).findAllByText(/Platinum/) - ).length - ).toBeGreaterThan(0); + wrapper: createWrapper(activeLicense), + }).findAllByText(/No services available/) + ).toHaveLength(1); + }); }); }); }); diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx index bb450131bdfb8..ce70db486d4d9 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -5,13 +5,13 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import React from 'react'; +import React, { ReactNode } from 'react'; import { useTrackPageview } from '../../../../../observability/public'; import { invalidLicenseMessage, isActivePlatinumLicense, } from '../../../../common/service_map'; -import { useFetcher } from '../../../hooks/useFetcher'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; import { useLicense } from '../../../hooks/useLicense'; import { useTheme } from '../../../hooks/useTheme'; import { useUrlParams } from '../../../hooks/useUrlParams'; @@ -21,6 +21,7 @@ import { Controls } from './Controls'; import { Cytoscape } from './Cytoscape'; import { getCytoscapeDivStyle } from './cytoscapeOptions'; import { EmptyBanner } from './EmptyBanner'; +import { EmptyPrompt } from './empty_prompt'; import { Popover } from './Popover'; import { useRefDimensions } from './useRefDimensions'; @@ -28,12 +29,30 @@ interface ServiceMapProps { serviceName?: string; } +function PromptContainer({ children }: { children: ReactNode }) { + return ( + + + {children} + + + ); +} + export function ServiceMap({ serviceName }: ServiceMapProps) { const theme = useTheme(); const license = useLicense(); const { urlParams } = useUrlParams(); - const { data = { elements: [] } } = useFetcher(() => { + const { data = { elements: [] }, status } = useFetcher(() => { // When we don't have a license or a valid license, don't make the request. if (!license || !isActivePlatinumLicense(license)) { return; @@ -65,8 +84,25 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { return null; } - return isActivePlatinumLicense(license) ? ( + if (!isActivePlatinumLicense(license)) { + return ( + + + + ); + } + + if (status === FETCH_STATUS.SUCCESS && data.elements.length === 0) { + return ( + + + + ); + } + + return (
- ) : ( - - - - - ); }