From ce5a798fddc15c43a6ebee1dccc7efdef1d98ed5 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Fri, 16 Jul 2021 14:26:33 -0500 Subject: [PATCH] Fix ENVIRONMENT_ALL links to exploratory view (#105704) Leave the environment out of the URL for the exploratory view when "All" is selected in the environment dropdown. Move the analyze data button to its own component. Add tests and stories for the the APM service template and analyze data button. Fixes #105467. --- .../analyze_data_button.stories.tsx | 57 +++++++++++ .../analyze_data_button.test.tsx | 77 +++++++++++++++ .../analyze_data_button.tsx | 87 +++++++++++++++++ .../index.tsx} | 97 ++++--------------- x-pack/plugins/observability/public/index.ts | 1 + 5 files changed, 241 insertions(+), 78 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx create mode 100644 x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.test.tsx create mode 100644 x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx rename x-pack/plugins/apm/public/components/routing/templates/{apm_service_template.tsx => apm_service_template/index.tsx} (66%) diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx new file mode 100644 index 000000000000..9c7e6803e445 --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx @@ -0,0 +1,57 @@ +/* + * 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 type { Story, StoryContext } from '@storybook/react'; +import React, { ComponentType } from 'react'; +import { CoreStart } from '../../../../../../../../src/core/public'; +import { createKibanaReactContext } from '../../../../../../../../src/plugins/kibana_react/public'; +import { APMServiceContext } from '../../../../context/apm_service/apm_service_context'; +import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider'; +import { AnalyzeDataButton } from './analyze_data_button'; + +const KibanaContext = createKibanaReactContext(({ + http: { basePath: { get: () => '' } }, +} as unknown) as Partial); + +interface Args { + agentName: string; + environment?: string; + serviceName: string; +} + +export default { + title: 'routing/templates/ApmServiceTemplate/AnalyzeDataButton', + component: AnalyzeDataButton, + decorators: [ + (StoryComponent: ComponentType, { args }: StoryContext) => { + const { agentName, environment, serviceName } = args; + + return ( + + + + + + + + ); + }, + ], +}; + +export const Example: Story = () => { + return ; +}; +Example.args = { + agentName: 'iOS/swift', + environment: 'testEnvironment', + serviceName: 'testServiceName', +}; diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.test.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.test.tsx new file mode 100644 index 000000000000..fdd28fdb378b --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.test.tsx @@ -0,0 +1,77 @@ +/* + * 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 { composeStories } from '@storybook/testing-react'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { + ENVIRONMENT_ALL, + ENVIRONMENT_NOT_DEFINED, +} from '../../../../../common/environment_filter_values'; +import * as stories from './analyze_data_button.stories'; + +const { Example } = composeStories(stories); + +describe('AnalyzeDataButton', () => { + describe('with a non-RUM and non-mobile agent', () => { + it('renders nothing', () => { + render(); + + expect(screen.queryByRole('link')).not.toBeInTheDocument(); + }); + }); + + describe('with a RUM agent', () => { + it('uses a ux dataType', () => { + render(); + + expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual( + 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:ux,isNew:!t,op:average,rdf:(service.environment:!(testEnvironment),service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))' + ); + }); + }); + + describe('with a mobile agent', () => { + it('uses a mobile dataType', () => { + render(); + + expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual( + 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:mobile,isNew:!t,op:average,rdf:(service.environment:!(testEnvironment),service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))' + ); + }); + }); + + describe('with no environment', () => { + it('does not include the environment', () => { + render(); + + expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual( + 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:mobile,isNew:!t,op:average,rdf:(service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))' + ); + }); + }); + + describe('with environment not defined', () => { + it('does not include the environment', () => { + render(); + + expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual( + 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:mobile,isNew:!t,op:average,rdf:(service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))' + ); + }); + }); + + describe('with environment all', () => { + it('uses ALL_VALUES', () => { + render(); + + expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual( + 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:mobile,isNew:!t,op:average,rdf:(service.environment:!(ALL_VALUES),service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))' + ); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx new file mode 100644 index 000000000000..e96334936444 --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx @@ -0,0 +1,87 @@ +/* + * 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 { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { + createExploratoryViewUrl, + SeriesUrl, +} from '../../../../../../observability/public'; +import { ALL_VALUES_SELECTED } from '../../../../../../observability/public'; +import { + isIosAgentName, + isRumAgentName, +} from '../../../../../common/agent_name'; +import { + SERVICE_ENVIRONMENT, + SERVICE_NAME, +} from '../../../../../common/elasticsearch_fieldnames'; +import { + ENVIRONMENT_ALL, + ENVIRONMENT_NOT_DEFINED, +} from '../../../../../common/environment_filter_values'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; + +function getEnvironmentDefinition(environment?: string) { + switch (environment) { + case ENVIRONMENT_ALL.value: + return { [SERVICE_ENVIRONMENT]: [ALL_VALUES_SELECTED] }; + case ENVIRONMENT_NOT_DEFINED.value: + case undefined: + return {}; + default: + return { [SERVICE_ENVIRONMENT]: [environment] }; + } +} + +export function AnalyzeDataButton() { + const { agentName, serviceName } = useApmServiceContext(); + const { services } = useKibana(); + const { urlParams } = useUrlParams(); + const { rangeTo, rangeFrom, environment } = urlParams; + const basepath = services.http?.basePath.get(); + + if (isRumAgentName(agentName) || isIosAgentName(agentName)) { + const href = createExploratoryViewUrl( + { + 'apm-series': { + dataType: isRumAgentName(agentName) ? 'ux' : 'mobile', + time: { from: rangeFrom, to: rangeTo }, + reportType: 'kpi-over-time', + reportDefinitions: { + [SERVICE_NAME]: [serviceName], + ...getEnvironmentDefinition(environment), + }, + operationType: 'average', + isNew: true, + } as SeriesUrl, + }, + basepath + ); + + return ( + + + {i18n.translate('xpack.apm.analyzeDataButton.label', { + defaultMessage: 'Analyze data', + })} + + + ); + } + + return null; +} diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx similarity index 66% rename from x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx rename to x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx index 2e10c853f542..591ccae32f61 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx @@ -5,45 +5,33 @@ * 2.0. */ -import React from 'react'; -import { i18n } from '@kbn/i18n'; import { + EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiPageHeaderProps, EuiTitle, - EuiBetaBadge, - EuiToolTip, - EuiButtonEmpty, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { omit } from 'lodash'; -import { ApmMainTemplate } from './apm_main_template'; -import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { ApmServiceContextProvider } from '../../../context/apm_service/apm_service_context'; -import { enableServiceOverview } from '../../../../common/ui_settings_keys'; +import React from 'react'; import { + isIosAgentName, isJavaAgentName, isRumAgentName, - isIosAgentName, -} from '../../../../common/agent_name'; -import { ServiceIcons } from '../../shared/service_icons'; -import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { ENVIRONMENT_NOT_DEFINED } from '../../../../common/environment_filter_values'; -import { - SERVICE_NAME, - SERVICE_ENVIRONMENT, -} from '../../../../common/elasticsearch_fieldnames'; -import { Correlations } from '../../app/correlations'; -import { SearchBar } from '../../shared/search_bar'; -import { - createExploratoryViewUrl, - SeriesUrl, -} from '../../../../../observability/public'; -import { useApmParams } from '../../../hooks/use_apm_params'; -import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; -import { useApmRouter } from '../../../hooks/use_apm_router'; +} from '../../../../../common/agent_name'; +import { enableServiceOverview } from '../../../../../common/ui_settings_keys'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { ApmServiceContextProvider } from '../../../../context/apm_service/apm_service_context'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { useBreadcrumb } from '../../../../context/breadcrumbs/use_breadcrumb'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useApmRouter } from '../../../../hooks/use_apm_router'; +import { Correlations } from '../../../app/correlations'; +import { SearchBar } from '../../../shared/search_bar'; +import { ServiceIcons } from '../../../shared/service_icons'; +import { ApmMainTemplate } from '../apm_main_template'; +import { AnalyzeDataButton } from './analyze_data_button'; type Tab = NonNullable[0] & { key: @@ -105,7 +93,7 @@ function TemplateWithContext({ -

{serviceName}

+ <>{serviceName}
@@ -115,7 +103,7 @@ function TemplateWithContext({ - + @@ -132,53 +120,6 @@ function TemplateWithContext({ ); } -function AnalyzeDataButton({ serviceName }: { serviceName: string }) { - const { agentName } = useApmServiceContext(); - const { services } = useKibana(); - const { urlParams } = useUrlParams(); - const { rangeTo, rangeFrom, environment } = urlParams; - const basepath = services.http?.basePath.get(); - - if (isRumAgentName(agentName) || isIosAgentName(agentName)) { - const href = createExploratoryViewUrl( - { - 'apm-series': { - dataType: isRumAgentName(agentName) ? 'ux' : 'mobile', - time: { from: rangeFrom, to: rangeTo }, - reportType: 'kpi-over-time', - reportDefinitions: { - [SERVICE_NAME]: [serviceName], - ...(!!environment && ENVIRONMENT_NOT_DEFINED.value !== environment - ? { [SERVICE_ENVIRONMENT]: [environment] } - : {}), - }, - operationType: 'average', - isNew: true, - } as SeriesUrl, - }, - basepath - ); - - return ( - - - {i18n.translate('xpack.apm.analyzeDataButton.label', { - defaultMessage: 'Analyze data', - })} - - - ); - } - - return null; -} - function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) { const { agentName } = useApmServiceContext(); const { core, config } = useApmPluginContext(); diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index 6bafe465fd02..cb390be635e1 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -65,6 +65,7 @@ export { useBreadcrumbs } from './hooks/use_breadcrumbs'; export { useTheme } from './hooks/use_theme'; export { getApmTraceUrl } from './utils/get_apm_trace_url'; export { createExploratoryViewUrl } from './components/shared/exploratory_view/configurations/utils'; +export { ALL_VALUES_SELECTED } from './components/shared/field_value_suggestions/field_value_combobox'; export { FilterValueLabel } from './components/shared/filter_value_label/filter_value_label'; export type { SeriesUrl } from './components/shared/exploratory_view/types';