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.
This commit is contained in:
Nathan L Smith 2021-07-16 14:26:33 -05:00 committed by GitHub
parent 219e1e5ab2
commit ce5a798fdd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 241 additions and 78 deletions

View file

@ -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<CoreStart>);
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 (
<MockUrlParamsContextProvider
params={{ environment, rangeFrom: 'now-15m', rangeTo: 'now' }}
>
<APMServiceContext.Provider
value={{ agentName, alerts: [], transactionTypes: [], serviceName }}
>
<KibanaContext.Provider>
<StoryComponent />
</KibanaContext.Provider>
</APMServiceContext.Provider>
</MockUrlParamsContextProvider>
);
},
],
};
export const Example: Story<Args> = () => {
return <AnalyzeDataButton />;
};
Example.args = {
agentName: 'iOS/swift',
environment: 'testEnvironment',
serviceName: 'testServiceName',
};

View file

@ -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(<Example agentName="java" />);
expect(screen.queryByRole('link')).not.toBeInTheDocument();
});
});
describe('with a RUM agent', () => {
it('uses a ux dataType', () => {
render(<Example agentName="rum-js" />);
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(<Example agentName="iOS/swift" />);
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(<Example environment={undefined} />);
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(<Example environment={ENVIRONMENT_NOT_DEFINED.value} />);
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(<Example environment={ENVIRONMENT_ALL.value} />);
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)))'
);
});
});
});

View file

@ -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 (
<EuiToolTip
position="top"
content={i18n.translate('xpack.apm.analyzeDataButton.tooltip', {
defaultMessage:
'EXPERIMENTAL - Analyze Data allows you to select and filter result data in any dimension, and look for the cause or impact of performance problems',
})}
>
<EuiButtonEmpty href={href} iconType="visBarVerticalStacked">
{i18n.translate('xpack.apm.analyzeDataButton.label', {
defaultMessage: 'Analyze data',
})}
</EuiButtonEmpty>
</EuiToolTip>
);
}
return null;
}

View file

@ -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<EuiPageHeaderProps['tabs']>[0] & {
key:
@ -105,7 +93,7 @@ function TemplateWithContext({
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiTitle size="l">
<h1>{serviceName}</h1>
<>{serviceName}</>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
@ -115,7 +103,7 @@ function TemplateWithContext({
</EuiFlexItem>
<EuiFlexItem grow={false}>
<AnalyzeDataButton serviceName={serviceName} />
<AnalyzeDataButton />
</EuiFlexItem>
<EuiFlexItem grow={false}>
@ -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 (
<EuiToolTip
position="top"
content={i18n.translate('xpack.apm.analyzeDataButton.tooltip', {
defaultMessage:
'EXPERIMENTAL - Analyze Data allows you to select and filter result data in any dimension, and look for the cause or impact of performance problems',
})}
>
<EuiButtonEmpty href={href} iconType="visBarVerticalStacked">
{i18n.translate('xpack.apm.analyzeDataButton.label', {
defaultMessage: 'Analyze data',
})}
</EuiButtonEmpty>
</EuiToolTip>
);
}
return null;
}
function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) {
const { agentName } = useApmServiceContext();
const { core, config } = useApmPluginContext();

View file

@ -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';