[APM] Service overview: Add throughput chart (#84439)
This commit is contained in:
parent
923a525923
commit
a2b71f8bf9
|
@ -4,43 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
getFirstTransactionType,
|
||||
isJavaAgentName,
|
||||
isRumAgentName,
|
||||
} from './agent_name';
|
||||
import { isJavaAgentName, isRumAgentName } from './agent_name';
|
||||
|
||||
describe('agent name helpers', () => {
|
||||
describe('getFirstTransactionType', () => {
|
||||
describe('with no transaction types', () => {
|
||||
expect(getFirstTransactionType([])).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('with a non-rum agent', () => {
|
||||
it('returns "request"', () => {
|
||||
expect(getFirstTransactionType(['worker', 'request'], 'java')).toEqual(
|
||||
'request'
|
||||
);
|
||||
});
|
||||
|
||||
describe('with no request types', () => {
|
||||
it('returns the first type', () => {
|
||||
expect(
|
||||
getFirstTransactionType(['worker', 'shirker'], 'java')
|
||||
).toEqual('worker');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a rum agent', () => {
|
||||
it('returns "page-load"', () => {
|
||||
expect(
|
||||
getFirstTransactionType(['http-request', 'page-load'], 'js-base')
|
||||
).toEqual('page-load');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isJavaAgentName', () => {
|
||||
describe('when the agent name is java', () => {
|
||||
it('returns true', () => {
|
||||
|
|
|
@ -5,10 +5,6 @@
|
|||
*/
|
||||
|
||||
import { AgentName } from '../typings/es_schemas/ui/fields/agent';
|
||||
import {
|
||||
TRANSACTION_PAGE_LOAD,
|
||||
TRANSACTION_REQUEST,
|
||||
} from './transaction_types';
|
||||
|
||||
/*
|
||||
* Agent names can be any string. This list only defines the official agents
|
||||
|
@ -50,26 +46,6 @@ export const RUM_AGENT_NAMES: AgentName[] = [
|
|||
'opentelemetry/webjs',
|
||||
];
|
||||
|
||||
function getDefaultTransactionTypeForAgentName(agentName?: string) {
|
||||
return isRumAgentName(agentName)
|
||||
? TRANSACTION_PAGE_LOAD
|
||||
: TRANSACTION_REQUEST;
|
||||
}
|
||||
|
||||
export function getFirstTransactionType(
|
||||
transactionTypes: string[],
|
||||
agentName?: string
|
||||
) {
|
||||
const defaultTransactionType = getDefaultTransactionTypeForAgentName(
|
||||
agentName
|
||||
);
|
||||
|
||||
return (
|
||||
transactionTypes.find((type) => type === defaultTransactionType) ??
|
||||
transactionTypes[0]
|
||||
);
|
||||
}
|
||||
|
||||
export function isJavaAgentName(
|
||||
agentName: string | undefined
|
||||
): agentName is 'java' {
|
||||
|
|
|
@ -11,7 +11,6 @@ import React from 'react';
|
|||
import { ForLastExpression } from '../../../../../triggers_actions_ui/public';
|
||||
import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types';
|
||||
import { useEnvironments } from '../../../hooks/useEnvironments';
|
||||
import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes';
|
||||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
import { ServiceAlertTrigger } from '../ServiceAlertTrigger';
|
||||
import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression';
|
||||
|
@ -22,6 +21,7 @@ import {
|
|||
TransactionTypeField,
|
||||
IsAboveField,
|
||||
} from '../fields';
|
||||
import { useApmService } from '../../../hooks/use_apm_service';
|
||||
|
||||
interface AlertParams {
|
||||
windowSize: number;
|
||||
|
@ -63,7 +63,7 @@ interface Props {
|
|||
export function TransactionDurationAlertTrigger(props: Props) {
|
||||
const { setAlertParams, alertParams, setAlertProperty } = props;
|
||||
const { urlParams } = useUrlParams();
|
||||
const transactionTypes = useServiceTransactionTypes(urlParams);
|
||||
const { transactionTypes } = useApmService();
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const { start, end, transactionType } = urlParams;
|
||||
const { environmentOptions } = useEnvironments({ serviceName, start, end });
|
||||
|
|
|
@ -10,7 +10,6 @@ import React from 'react';
|
|||
import { ANOMALY_SEVERITY } from '../../../../../ml/common';
|
||||
import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types';
|
||||
import { useEnvironments } from '../../../hooks/useEnvironments';
|
||||
import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes';
|
||||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
import { ServiceAlertTrigger } from '../ServiceAlertTrigger';
|
||||
import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression';
|
||||
|
@ -24,6 +23,7 @@ import {
|
|||
ServiceField,
|
||||
TransactionTypeField,
|
||||
} from '../fields';
|
||||
import { useApmService } from '../../../hooks/use_apm_service';
|
||||
|
||||
interface Params {
|
||||
windowSize: number;
|
||||
|
@ -47,7 +47,7 @@ interface Props {
|
|||
export function TransactionDurationAnomalyAlertTrigger(props: Props) {
|
||||
const { setAlertParams, alertParams, setAlertProperty } = props;
|
||||
const { urlParams } = useUrlParams();
|
||||
const transactionTypes = useServiceTransactionTypes(urlParams);
|
||||
const { transactionTypes } = useApmService();
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const { start, end, transactionType } = urlParams;
|
||||
const { environmentOptions } = useEnvironments({ serviceName, start, end });
|
||||
|
|
|
@ -8,7 +8,6 @@ import React from 'react';
|
|||
import { ForLastExpression } from '../../../../../triggers_actions_ui/public';
|
||||
import { ALERT_TYPES_CONFIG, AlertType } from '../../../../common/alert_types';
|
||||
import { useEnvironments } from '../../../hooks/useEnvironments';
|
||||
import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes';
|
||||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
import { ServiceAlertTrigger } from '../ServiceAlertTrigger';
|
||||
|
||||
|
@ -19,6 +18,7 @@ import {
|
|||
EnvironmentField,
|
||||
IsAboveField,
|
||||
} from '../fields';
|
||||
import { useApmService } from '../../../hooks/use_apm_service';
|
||||
|
||||
interface AlertParams {
|
||||
windowSize: number;
|
||||
|
@ -38,7 +38,7 @@ interface Props {
|
|||
export function TransactionErrorRateAlertTrigger(props: Props) {
|
||||
const { setAlertParams, alertParams, setAlertProperty } = props;
|
||||
const { urlParams } = useUrlParams();
|
||||
const transactionTypes = useServiceTransactionTypes(urlParams);
|
||||
const { transactionTypes } = useApmService();
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const { start, end, transactionType } = urlParams;
|
||||
const { environmentOptions } = useEnvironments({ serviceName, start, end });
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
import { ApmServiceContextProvider } from '../../../../context/apm_service_context';
|
||||
import { UNIDENTIFIED_SERVICE_NODES_LABEL } from '../../../../../common/i18n';
|
||||
import { SERVICE_NODE_NAME_MISSING } from '../../../../../common/service_nodes';
|
||||
import { APMRouteDefinition } from '../../../../application/routes';
|
||||
|
@ -227,19 +228,19 @@ export const routes: APMRouteDefinition[] = [
|
|||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.overviewTitle', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
component: ServiceDetailsOverview,
|
||||
component: withApmServiceContext(ServiceDetailsOverview),
|
||||
} as APMRouteDefinition<{ serviceName: string }>,
|
||||
// errors
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/errors/:groupId',
|
||||
component: ErrorGroupDetails,
|
||||
component: withApmServiceContext(ErrorGroupDetails),
|
||||
breadcrumb: ({ match }) => match.params.groupId,
|
||||
} as APMRouteDefinition<{ groupId: string; serviceName: string }>,
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/errors',
|
||||
component: ServiceDetailsErrors,
|
||||
component: withApmServiceContext(ServiceDetailsErrors),
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.errorsTitle', {
|
||||
defaultMessage: 'Errors',
|
||||
}),
|
||||
|
@ -248,7 +249,7 @@ export const routes: APMRouteDefinition[] = [
|
|||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/transactions',
|
||||
component: ServiceDetailsTransactions,
|
||||
component: withApmServiceContext(ServiceDetailsTransactions),
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.transactionsTitle', {
|
||||
defaultMessage: 'Transactions',
|
||||
}),
|
||||
|
@ -257,7 +258,7 @@ export const routes: APMRouteDefinition[] = [
|
|||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/metrics',
|
||||
component: ServiceDetailsMetrics,
|
||||
component: withApmServiceContext(ServiceDetailsMetrics),
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.metricsTitle', {
|
||||
defaultMessage: 'Metrics',
|
||||
}),
|
||||
|
@ -266,7 +267,7 @@ export const routes: APMRouteDefinition[] = [
|
|||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/nodes',
|
||||
component: ServiceDetailsNodes,
|
||||
component: withApmServiceContext(ServiceDetailsNodes),
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.nodesTitle', {
|
||||
defaultMessage: 'JVMs',
|
||||
}),
|
||||
|
@ -275,7 +276,7 @@ export const routes: APMRouteDefinition[] = [
|
|||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/nodes/:serviceNodeName/metrics',
|
||||
component: ServiceNodeMetrics,
|
||||
component: withApmServiceContext(ServiceNodeMetrics),
|
||||
breadcrumb: ({ match }) => {
|
||||
const { serviceNodeName } = match.params;
|
||||
|
||||
|
@ -289,12 +290,20 @@ export const routes: APMRouteDefinition[] = [
|
|||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/transactions/view',
|
||||
component: TransactionDetails,
|
||||
component: withApmServiceContext(TransactionDetails),
|
||||
breadcrumb: ({ location }) => {
|
||||
const query = toQuery(location.search);
|
||||
return query.transactionName as string;
|
||||
},
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/service-map',
|
||||
component: withApmServiceContext(ServiceDetailsServiceMap),
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', {
|
||||
defaultMessage: 'Service Map',
|
||||
}),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/link-to/trace/:traceId',
|
||||
|
@ -309,14 +318,6 @@ export const routes: APMRouteDefinition[] = [
|
|||
defaultMessage: 'Service Map',
|
||||
}),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/service-map',
|
||||
component: ServiceDetailsServiceMap,
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', {
|
||||
defaultMessage: 'Service Map',
|
||||
}),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/customize-ui',
|
||||
|
@ -337,3 +338,13 @@ export const routes: APMRouteDefinition[] = [
|
|||
),
|
||||
},
|
||||
];
|
||||
|
||||
function withApmServiceContext(WrappedComponent: React.ComponentType<any>) {
|
||||
return (props: any) => {
|
||||
return (
|
||||
<ApmServiceContextProvider>
|
||||
<WrappedComponent {...props} />
|
||||
</ApmServiceContextProvider>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n';
|
|||
import React, { ReactNode } from 'react';
|
||||
import { isJavaAgentName, isRumAgentName } from '../../../../common/agent_name';
|
||||
import { enableServiceOverview } from '../../../../common/ui_settings_keys';
|
||||
import { useAgentName } from '../../../hooks/useAgentName';
|
||||
import { useApmPluginContext } from '../../../hooks/useApmPluginContext';
|
||||
import { useErrorOverviewHref } from '../../shared/Links/apm/ErrorOverviewLink';
|
||||
import { useMetricOverviewHref } from '../../shared/Links/apm/MetricOverviewLink';
|
||||
|
@ -24,6 +23,7 @@ import { ServiceMetrics } from '../service_metrics';
|
|||
import { ServiceNodeOverview } from '../ServiceNodeOverview';
|
||||
import { ServiceOverview } from '../service_overview';
|
||||
import { TransactionOverview } from '../transaction_overview';
|
||||
import { useApmService } from '../../../hooks/use_apm_service';
|
||||
|
||||
interface Tab {
|
||||
key: string;
|
||||
|
@ -44,7 +44,7 @@ interface Props {
|
|||
}
|
||||
|
||||
export function ServiceDetailTabs({ serviceName, tab }: Props) {
|
||||
const { agentName } = useAgentName();
|
||||
const { agentName } = useApmService();
|
||||
const { uiSettings } = useApmPluginContext().core;
|
||||
|
||||
const overviewTab = {
|
||||
|
|
|
@ -23,10 +23,10 @@ import { RouteComponentProps } from 'react-router-dom';
|
|||
import styled from 'styled-components';
|
||||
import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes';
|
||||
import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event_context';
|
||||
import { useAgentName } from '../../../hooks/useAgentName';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher';
|
||||
import { useServiceMetricCharts } from '../../../hooks/useServiceMetricCharts';
|
||||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
import { useApmService } from '../../../hooks/use_apm_service';
|
||||
import { px, truncate, unit } from '../../../style/variables';
|
||||
import { ApmHeader } from '../../shared/ApmHeader';
|
||||
import { MetricsChart } from '../../shared/charts/metrics_chart';
|
||||
|
@ -58,7 +58,7 @@ type ServiceNodeMetricsProps = RouteComponentProps<{
|
|||
export function ServiceNodeMetrics({ match }: ServiceNodeMetricsProps) {
|
||||
const { urlParams, uiFilters } = useUrlParams();
|
||||
const { serviceName, serviceNodeName } = match.params;
|
||||
const { agentName } = useAgentName();
|
||||
const { agentName } = useApmService();
|
||||
const { data } = useServiceMetricCharts(
|
||||
urlParams,
|
||||
agentName,
|
||||
|
|
|
@ -21,6 +21,7 @@ import { TransactionErrorRateChart } from '../../shared/charts/transaction_error
|
|||
import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
import { ServiceOverviewErrorsTable } from './service_overview_errors_table';
|
||||
import { ServiceOverviewThroughputChart } from './service_overview_throughput_chart';
|
||||
import { ServiceOverviewTransactionsTable } from './service_overview_transactions_table';
|
||||
import { TableLinkFlexItem } from './table_link_flex_item';
|
||||
|
||||
|
@ -64,18 +65,7 @@ export function ServiceOverview({
|
|||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={4}>
|
||||
<EuiPanel>
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
{i18n.translate(
|
||||
'xpack.apm.serviceOverview.trafficChartTitle',
|
||||
{
|
||||
defaultMessage: 'Traffic',
|
||||
}
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiPanel>
|
||||
<ServiceOverviewThroughputChart height={chartHeight} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={6}>
|
||||
<EuiPanel>
|
||||
|
|
|
@ -72,6 +72,7 @@ describe('ServiceOverview', () => {
|
|||
sort: { direction: 'desc', field: 'test field' },
|
||||
},
|
||||
totalItemCount: 0,
|
||||
throughput: [],
|
||||
},
|
||||
refetch: () => {},
|
||||
status: FETCH_STATUS.SUCCESS,
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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 { EuiPanel, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { asTransactionRate } from '../../../../common/utils/formatters';
|
||||
import { useFetcher } from '../../../hooks/useFetcher';
|
||||
import { useTheme } from '../../../hooks/useTheme';
|
||||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
import { useApmService } from '../../../hooks/use_apm_service';
|
||||
import { callApmApi } from '../../../services/rest/createCallApmApi';
|
||||
import { TimeseriesChart } from '../../shared/charts/timeseries_chart';
|
||||
|
||||
export function ServiceOverviewThroughputChart({
|
||||
height,
|
||||
}: {
|
||||
height?: number;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const { urlParams, uiFilters } = useUrlParams();
|
||||
const { transactionType } = useApmService();
|
||||
const { start, end } = urlParams;
|
||||
|
||||
const { data, status } = useFetcher(() => {
|
||||
if (serviceName && transactionType && start && end) {
|
||||
return callApmApi({
|
||||
endpoint: 'GET /api/apm/services/{serviceName}/throughput',
|
||||
params: {
|
||||
path: {
|
||||
serviceName,
|
||||
},
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
transactionType,
|
||||
uiFilters: JSON.stringify(uiFilters),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [serviceName, start, end, uiFilters, transactionType]);
|
||||
|
||||
return (
|
||||
<EuiPanel>
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
{i18n.translate('xpack.apm.serviceOverview.throughtputChartTitle', {
|
||||
defaultMessage: 'Traffic',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<TimeseriesChart
|
||||
id="throughput"
|
||||
height={height}
|
||||
showAnnotations={false}
|
||||
fetchStatus={status}
|
||||
timeseries={[
|
||||
{
|
||||
data: data?.throughput ?? [],
|
||||
type: 'linemark',
|
||||
color: theme.eui.euiColorVis0,
|
||||
title: i18n.translate(
|
||||
'xpack.apm.serviceOverview.throughputChart.currentPeriodLabel',
|
||||
{
|
||||
defaultMessage: 'Current period',
|
||||
}
|
||||
),
|
||||
},
|
||||
]}
|
||||
yLabelFormat={asTransactionRate}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
|
@ -24,11 +24,9 @@ import { useTrackPageview } from '../../../../../observability/public';
|
|||
import { Projection } from '../../../../common/projections';
|
||||
import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types';
|
||||
import { IUrlParams } from '../../../context/UrlParamsContext/types';
|
||||
import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes';
|
||||
import { useTransactionCharts } from '../../../hooks/useTransactionCharts';
|
||||
import { useTransactionList } from '../../../hooks/useTransactionList';
|
||||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
import { useTransactionType } from '../../../hooks/use_transaction_type';
|
||||
import { TransactionCharts } from '../../shared/charts/transaction_charts';
|
||||
import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink';
|
||||
import { fromQuery, toQuery } from '../../shared/Links/url_helpers';
|
||||
|
@ -39,6 +37,7 @@ import { Correlations } from '../Correlations';
|
|||
import { TransactionList } from './TransactionList';
|
||||
import { useRedirect } from './useRedirect';
|
||||
import { UserExperienceCallout } from './user_experience_callout';
|
||||
import { useApmService } from '../../../hooks/use_apm_service';
|
||||
|
||||
function getRedirectLocation({
|
||||
location,
|
||||
|
@ -69,8 +68,7 @@ interface TransactionOverviewProps {
|
|||
export function TransactionOverview({ serviceName }: TransactionOverviewProps) {
|
||||
const location = useLocation();
|
||||
const { urlParams } = useUrlParams();
|
||||
const transactionType = useTransactionType();
|
||||
const serviceTransactionTypes = useServiceTransactionTypes(urlParams);
|
||||
const { transactionType, transactionTypes } = useApmService();
|
||||
|
||||
// redirect to first transaction type
|
||||
useRedirect(getRedirectLocation({ location, transactionType, urlParams }));
|
||||
|
@ -122,9 +120,7 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) {
|
|||
<EuiFlexItem grow={1}>
|
||||
<Correlations />
|
||||
<LocalUIFilters {...localFiltersConfig}>
|
||||
<TransactionTypeFilter
|
||||
transactionTypes={serviceTransactionTypes}
|
||||
/>
|
||||
<TransactionTypeFilter transactionTypes={transactionTypes} />
|
||||
<EuiSpacer size="m" />
|
||||
<EuiHorizontalRule margin="none" />
|
||||
</LocalUIFilters>
|
||||
|
|
|
@ -11,10 +11,12 @@ import React from 'react';
|
|||
import { Router } from 'react-router-dom';
|
||||
import { createKibanaReactContext } from 'src/plugins/kibana_react/public';
|
||||
import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext';
|
||||
import { ApmServiceContextProvider } from '../../../context/apm_service_context';
|
||||
import { UrlParamsProvider } from '../../../context/UrlParamsContext';
|
||||
import { IUrlParams } from '../../../context/UrlParamsContext/types';
|
||||
import * as useFetcherHook from '../../../hooks/useFetcher';
|
||||
import * as useServiceTransactionTypesHook from '../../../hooks/useServiceTransactionTypes';
|
||||
import * as useServiceTransactionTypesHook from '../../../hooks/use_service_transaction_types';
|
||||
import * as useServiceAgentNameHook from '../../../hooks/use_service_agent_name';
|
||||
import {
|
||||
disableConsoleWarning,
|
||||
renderWithTheme,
|
||||
|
@ -37,19 +39,23 @@ function setup({
|
|||
urlParams: IUrlParams;
|
||||
serviceTransactionTypes: string[];
|
||||
}) {
|
||||
const defaultLocation = {
|
||||
history.replace({
|
||||
pathname: '/services/foo/transactions',
|
||||
search: fromQuery(urlParams),
|
||||
} as any;
|
||||
|
||||
history.replace({
|
||||
...defaultLocation,
|
||||
});
|
||||
|
||||
// mock transaction types
|
||||
jest
|
||||
.spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypes')
|
||||
.mockReturnValue(serviceTransactionTypes);
|
||||
|
||||
// mock agent
|
||||
jest.spyOn(useServiceAgentNameHook, 'useServiceAgentName').mockReturnValue({
|
||||
agentName: 'nodejs',
|
||||
error: undefined,
|
||||
status: useFetcherHook.FETCH_STATUS.SUCCESS,
|
||||
});
|
||||
|
||||
jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any);
|
||||
|
||||
return renderWithTheme(
|
||||
|
@ -57,7 +63,9 @@ function setup({
|
|||
<MockApmPluginContextWrapper>
|
||||
<Router history={history}>
|
||||
<UrlParamsProvider>
|
||||
<TransactionOverview serviceName="opbeans-python" />
|
||||
<ApmServiceContextProvider>
|
||||
<TransactionOverview serviceName="opbeans-python" />
|
||||
</ApmServiceContextProvider>
|
||||
</UrlParamsProvider>
|
||||
</Router>
|
||||
</MockApmPluginContextWrapper>
|
||||
|
@ -80,7 +88,7 @@ describe('TransactionOverview', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('when no transaction type is given', () => {
|
||||
describe('when no transaction type is given in urlParams', () => {
|
||||
it('should redirect to first type', () => {
|
||||
setup({
|
||||
serviceTransactionTypes: ['firstType', 'secondType'],
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 { getTransactionType } from './apm_service_context';
|
||||
|
||||
describe('getTransactionType', () => {
|
||||
describe('with transaction type in url', () => {
|
||||
it('returns the transaction type in the url ', () => {
|
||||
expect(
|
||||
getTransactionType({
|
||||
transactionTypes: ['worker', 'request'],
|
||||
urlParams: { transactionType: 'custom' },
|
||||
agentName: 'nodejs',
|
||||
})
|
||||
).toBe('custom');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with no transaction types', () => {
|
||||
it('returns undefined', () => {
|
||||
expect(
|
||||
getTransactionType({
|
||||
transactionTypes: [],
|
||||
urlParams: {},
|
||||
})
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a non-rum agent', () => {
|
||||
describe('with default transaction type', () => {
|
||||
it('returns "request"', () => {
|
||||
expect(
|
||||
getTransactionType({
|
||||
transactionTypes: ['worker', 'request'],
|
||||
urlParams: {},
|
||||
agentName: 'nodejs',
|
||||
})
|
||||
).toEqual('request');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with no default transaction type', () => {
|
||||
it('returns the first type', () => {
|
||||
expect(
|
||||
getTransactionType({
|
||||
transactionTypes: ['worker', 'custom'],
|
||||
urlParams: {},
|
||||
agentName: 'nodejs',
|
||||
})
|
||||
).toEqual('worker');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a rum agent', () => {
|
||||
it('returns "page-load"', () => {
|
||||
expect(
|
||||
getTransactionType({
|
||||
transactionTypes: ['http-request', 'page-load'],
|
||||
urlParams: {},
|
||||
agentName: 'js-base',
|
||||
})
|
||||
).toEqual('page-load');
|
||||
});
|
||||
});
|
||||
});
|
72
x-pack/plugins/apm/public/context/apm_service_context.tsx
Normal file
72
x-pack/plugins/apm/public/context/apm_service_context.tsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 React, { createContext, ReactNode } from 'react';
|
||||
import { isRumAgentName } from '../../common/agent_name';
|
||||
import {
|
||||
TRANSACTION_PAGE_LOAD,
|
||||
TRANSACTION_REQUEST,
|
||||
} from '../../common/transaction_types';
|
||||
import { useServiceTransactionTypes } from '../hooks/use_service_transaction_types';
|
||||
import { useUrlParams } from '../hooks/useUrlParams';
|
||||
import { useServiceAgentName } from '../hooks/use_service_agent_name';
|
||||
import { IUrlParams } from './UrlParamsContext/types';
|
||||
|
||||
export const APMServiceContext = createContext<{
|
||||
agentName?: string;
|
||||
transactionType?: string;
|
||||
transactionTypes: string[];
|
||||
}>({ transactionTypes: [] });
|
||||
|
||||
export function ApmServiceContextProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const { urlParams } = useUrlParams();
|
||||
const { agentName } = useServiceAgentName();
|
||||
const transactionTypes = useServiceTransactionTypes();
|
||||
const transactionType = getTransactionType({
|
||||
urlParams,
|
||||
transactionTypes,
|
||||
agentName,
|
||||
});
|
||||
|
||||
return (
|
||||
<APMServiceContext.Provider
|
||||
value={{ agentName, transactionType, transactionTypes }}
|
||||
children={children}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function getTransactionType({
|
||||
urlParams,
|
||||
transactionTypes,
|
||||
agentName,
|
||||
}: {
|
||||
urlParams: IUrlParams;
|
||||
transactionTypes: string[];
|
||||
agentName?: string;
|
||||
}) {
|
||||
if (urlParams.transactionType) {
|
||||
return urlParams.transactionType;
|
||||
}
|
||||
|
||||
if (!agentName || transactionTypes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The default transaction type is "page-load" for RUM agents and "request" for all others
|
||||
const defaultTransactionType = isRumAgentName(agentName)
|
||||
? TRANSACTION_PAGE_LOAD
|
||||
: TRANSACTION_REQUEST;
|
||||
|
||||
// If the default transaction type is not in transactionTypes the first in the list is returned
|
||||
return transactionTypes.includes(defaultTransactionType)
|
||||
? defaultTransactionType
|
||||
: transactionTypes[0];
|
||||
}
|
|
@ -4,12 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useRef } from 'react';
|
||||
import { useContext } from 'react';
|
||||
import { APMServiceContext } from '../context/apm_service_context';
|
||||
|
||||
let uniqueId = 0;
|
||||
const getUniqueId = () => uniqueId++;
|
||||
|
||||
export function useComponentId() {
|
||||
const idRef = useRef(getUniqueId());
|
||||
return idRef.current;
|
||||
export function useApmService() {
|
||||
return useContext(APMServiceContext);
|
||||
}
|
|
@ -3,16 +3,16 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { useUrlParams } from './useUrlParams';
|
||||
|
||||
export function useAgentName() {
|
||||
export function useServiceAgentName() {
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const { urlParams } = useUrlParams();
|
||||
const { start, end } = urlParams;
|
||||
|
||||
const { data: agentName, error, status } = useFetcher(
|
||||
const { data, error, status } = useFetcher(
|
||||
(callApmApi) => {
|
||||
if (serviceName && start && end) {
|
||||
return callApmApi({
|
||||
|
@ -21,15 +21,11 @@ export function useAgentName() {
|
|||
path: { serviceName },
|
||||
query: { start, end },
|
||||
},
|
||||
}).then((res) => res.agentName);
|
||||
});
|
||||
}
|
||||
},
|
||||
[serviceName, start, end]
|
||||
);
|
||||
|
||||
return {
|
||||
agentName,
|
||||
status,
|
||||
error,
|
||||
};
|
||||
return { agentName: data?.agentName, status, error };
|
||||
}
|
|
@ -5,13 +5,14 @@
|
|||
*/
|
||||
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { IUrlParams } from '../context/UrlParamsContext/types';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { useUrlParams } from './useUrlParams';
|
||||
|
||||
const INITIAL_DATA = { transactionTypes: [] };
|
||||
|
||||
export function useServiceTransactionTypes(urlParams: IUrlParams) {
|
||||
export function useServiceTransactionTypes() {
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const { urlParams } = useUrlParams();
|
||||
const { start, end } = urlParams;
|
||||
const { data = INITIAL_DATA } = useFetcher(
|
||||
(callApmApi) => {
|
|
@ -7,13 +7,13 @@
|
|||
import { useParams } from 'react-router-dom';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { useUrlParams } from './useUrlParams';
|
||||
import { useTransactionType } from './use_transaction_type';
|
||||
import { useApmService } from './use_apm_service';
|
||||
|
||||
export function useTransactionBreakdown() {
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const { urlParams, uiFilters } = useUrlParams();
|
||||
const { start, end, transactionName } = urlParams;
|
||||
const transactionType = useTransactionType();
|
||||
const { transactionType } = useApmService();
|
||||
|
||||
const { data = { timeseries: undefined }, error, status } = useFetcher(
|
||||
(callApmApi) => {
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* 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 { getFirstTransactionType } from '../../common/agent_name';
|
||||
import { useAgentName } from './useAgentName';
|
||||
import { useServiceTransactionTypes } from './useServiceTransactionTypes';
|
||||
import { useUrlParams } from './useUrlParams';
|
||||
|
||||
/**
|
||||
* Get either the transaction type from the URL parameters, "request"
|
||||
* (for non-RUM agents), "page-load" (for RUM agents) if this service uses them,
|
||||
* or the first available transaction type.
|
||||
*/
|
||||
export function useTransactionType() {
|
||||
const { agentName } = useAgentName();
|
||||
const { urlParams } = useUrlParams();
|
||||
const transactionTypeFromUrlParams = urlParams.transactionType;
|
||||
const transactionTypes = useServiceTransactionTypes(urlParams);
|
||||
const firstTransactionType = getFirstTransactionType(
|
||||
transactionTypes,
|
||||
agentName
|
||||
);
|
||||
|
||||
return transactionTypeFromUrlParams ?? firstTransactionType;
|
||||
}
|
84
x-pack/plugins/apm/server/lib/services/get_throughput.ts
Normal file
84
x-pack/plugins/apm/server/lib/services/get_throughput.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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 { ESFilter } from '../../../../../typings/elasticsearch';
|
||||
import { PromiseReturnType } from '../../../../observability/typings/common';
|
||||
import {
|
||||
SERVICE_NAME,
|
||||
TRANSACTION_TYPE,
|
||||
} from '../../../common/elasticsearch_fieldnames';
|
||||
import { rangeFilter } from '../../../common/utils/range_filter';
|
||||
import {
|
||||
getDocumentTypeFilterForAggregatedTransactions,
|
||||
getProcessorEventForAggregatedTransactions,
|
||||
} from '../helpers/aggregated_transactions';
|
||||
import { getBucketSize } from '../helpers/get_bucket_size';
|
||||
import { Setup, SetupTimeRange } from '../helpers/setup_request';
|
||||
|
||||
interface Options {
|
||||
searchAggregatedTransactions: boolean;
|
||||
serviceName: string;
|
||||
setup: Setup & SetupTimeRange;
|
||||
transactionType: string;
|
||||
}
|
||||
|
||||
type ESResponse = PromiseReturnType<typeof fetcher>;
|
||||
|
||||
function transform(response: ESResponse) {
|
||||
const buckets = response.aggregations?.throughput?.buckets ?? [];
|
||||
return buckets.map(({ key: x, doc_count: y }) => ({ x, y }));
|
||||
}
|
||||
|
||||
async function fetcher({
|
||||
searchAggregatedTransactions,
|
||||
serviceName,
|
||||
setup,
|
||||
transactionType,
|
||||
}: Options) {
|
||||
const { start, end, apmEventClient } = setup;
|
||||
const { intervalString } = getBucketSize({ start, end });
|
||||
const filter: ESFilter[] = [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [TRANSACTION_TYPE]: transactionType } },
|
||||
{ range: rangeFilter(start, end) },
|
||||
...getDocumentTypeFilterForAggregatedTransactions(
|
||||
searchAggregatedTransactions
|
||||
),
|
||||
...setup.esFilter,
|
||||
];
|
||||
|
||||
const params = {
|
||||
apm: {
|
||||
events: [
|
||||
getProcessorEventForAggregatedTransactions(
|
||||
searchAggregatedTransactions
|
||||
),
|
||||
],
|
||||
},
|
||||
body: {
|
||||
size: 0,
|
||||
query: { bool: { filter } },
|
||||
aggs: {
|
||||
throughput: {
|
||||
date_histogram: {
|
||||
field: '@timestamp',
|
||||
fixed_interval: intervalString,
|
||||
min_doc_count: 0,
|
||||
extended_bounds: { min: start, max: end },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return apmEventClient.search(params);
|
||||
}
|
||||
|
||||
export async function getThroughput(options: Options) {
|
||||
return {
|
||||
throughput: transform(await fetcher(options)),
|
||||
};
|
||||
}
|
|
@ -22,6 +22,7 @@ import {
|
|||
serviceAnnotationsRoute,
|
||||
serviceAnnotationsCreateRoute,
|
||||
serviceErrorGroupsRoute,
|
||||
serviceThroughputRoute,
|
||||
serviceTransactionGroupsRoute,
|
||||
} from './services';
|
||||
import {
|
||||
|
@ -117,6 +118,7 @@ const createApmApi = () => {
|
|||
.add(serviceAnnotationsRoute)
|
||||
.add(serviceAnnotationsCreateRoute)
|
||||
.add(serviceErrorGroupsRoute)
|
||||
.add(serviceThroughputRoute)
|
||||
.add(serviceTransactionGroupsRoute)
|
||||
|
||||
// Agent configuration
|
||||
|
|
|
@ -20,6 +20,7 @@ import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_trans
|
|||
import { getServiceErrorGroups } from '../lib/services/get_service_error_groups';
|
||||
import { toNumberRt } from '../../common/runtime_types/to_number_rt';
|
||||
import { getServiceTransactionGroups } from '../lib/services/get_service_transaction_groups';
|
||||
import { getThroughput } from '../lib/services/get_throughput';
|
||||
|
||||
export const servicesRoute = createRoute({
|
||||
endpoint: 'GET /api/apm/services',
|
||||
|
@ -246,6 +247,36 @@ export const serviceErrorGroupsRoute = createRoute({
|
|||
},
|
||||
});
|
||||
|
||||
export const serviceThroughputRoute = createRoute({
|
||||
endpoint: 'GET /api/apm/services/{serviceName}/throughput',
|
||||
params: t.type({
|
||||
path: t.type({
|
||||
serviceName: t.string,
|
||||
}),
|
||||
query: t.intersection([
|
||||
t.type({ transactionType: t.string }),
|
||||
uiFiltersRt,
|
||||
rangeRt,
|
||||
]),
|
||||
}),
|
||||
options: { tags: ['access:apm'] },
|
||||
handler: async ({ context, request }) => {
|
||||
const setup = await setupRequest(context, request);
|
||||
const { serviceName } = context.params.path;
|
||||
const { transactionType } = context.params.query;
|
||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||
setup
|
||||
);
|
||||
|
||||
return getThroughput({
|
||||
searchAggregatedTransactions,
|
||||
serviceName,
|
||||
setup,
|
||||
transactionType,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const serviceTransactionGroupsRoute = createRoute({
|
||||
endpoint: 'GET /api/apm/services/{serviceName}/overview_transaction_groups',
|
||||
params: t.type({
|
||||
|
|
|
@ -16,9 +16,10 @@ export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderCont
|
|||
});
|
||||
|
||||
describe('Services', function () {
|
||||
loadTestFile(require.resolve('./services/annotations'));
|
||||
loadTestFile(require.resolve('./services/top_services'));
|
||||
loadTestFile(require.resolve('./services/agent_name'));
|
||||
loadTestFile(require.resolve('./services/annotations'));
|
||||
loadTestFile(require.resolve('./services/throughput'));
|
||||
loadTestFile(require.resolve('./services/top_services'));
|
||||
loadTestFile(require.resolve('./services/transaction_types'));
|
||||
});
|
||||
|
||||
|
|
250
x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap
generated
Normal file
250
x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap
generated
Normal file
|
@ -0,0 +1,250 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Throughput when data is loaded returns the service throughput has the correct throughput 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"x": 1601389800000,
|
||||
"y": 6,
|
||||
},
|
||||
Object {
|
||||
"x": 1601389830000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601389860000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601389890000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601389920000,
|
||||
"y": 3,
|
||||
},
|
||||
Object {
|
||||
"x": 1601389950000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1601389980000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390010000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390040000,
|
||||
"y": 3,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390070000,
|
||||
"y": 2,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390100000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390130000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390160000,
|
||||
"y": 7,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390190000,
|
||||
"y": 3,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390220000,
|
||||
"y": 2,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390250000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390280000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390310000,
|
||||
"y": 8,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390340000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390370000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390400000,
|
||||
"y": 3,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390430000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390460000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390490000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390520000,
|
||||
"y": 4,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390550000,
|
||||
"y": 3,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390580000,
|
||||
"y": 2,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390610000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390640000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390670000,
|
||||
"y": 2,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390700000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390730000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390760000,
|
||||
"y": 4,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390790000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390820000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390850000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390880000,
|
||||
"y": 6,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390910000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390940000,
|
||||
"y": 3,
|
||||
},
|
||||
Object {
|
||||
"x": 1601390970000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391000000,
|
||||
"y": 4,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391030000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391060000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391090000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391120000,
|
||||
"y": 2,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391150000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391180000,
|
||||
"y": 2,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391210000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391240000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391270000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391300000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391330000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391360000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391390000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391420000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391450000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391480000,
|
||||
"y": 10,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391510000,
|
||||
"y": 3,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391540000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391570000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1601391600000,
|
||||
"y": 0,
|
||||
},
|
||||
]
|
||||
`;
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import qs from 'querystring';
|
||||
import { first, last } from 'lodash';
|
||||
import archives_metadata from '../../../common/archives_metadata';
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const esArchiver = getService('esArchiver');
|
||||
|
||||
const archiveName = 'apm_8.0.0';
|
||||
const metadata = archives_metadata[archiveName];
|
||||
|
||||
describe('Throughput', () => {
|
||||
describe('when data is not loaded', () => {
|
||||
it('handles the empty state', async () => {
|
||||
const response = await supertest.get(
|
||||
`/api/apm/services/opbeans-java/throughput?${qs.stringify({
|
||||
start: metadata.start,
|
||||
end: metadata.end,
|
||||
uiFilters: encodeURIComponent('{}'),
|
||||
transactionType: 'request',
|
||||
})}`
|
||||
);
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.throughput.length).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
before(() => esArchiver.load(archiveName));
|
||||
after(() => esArchiver.unload(archiveName));
|
||||
|
||||
describe('returns the service throughput', () => {
|
||||
let throughputResponse: {
|
||||
throughput: Array<{ x: number; y: number | null }>;
|
||||
};
|
||||
before(async () => {
|
||||
const response = await supertest.get(
|
||||
`/api/apm/services/opbeans-java/throughput?${qs.stringify({
|
||||
start: metadata.start,
|
||||
end: metadata.end,
|
||||
uiFilters: encodeURIComponent('{}'),
|
||||
transactionType: 'request',
|
||||
})}`
|
||||
);
|
||||
throughputResponse = response.body;
|
||||
});
|
||||
|
||||
it('returns some data', () => {
|
||||
expect(throughputResponse.throughput.length).to.be.greaterThan(0);
|
||||
|
||||
const nonNullDataPoints = throughputResponse.throughput.filter(({ y }) => y !== null);
|
||||
|
||||
expect(nonNullDataPoints.length).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('has the correct start date', () => {
|
||||
expectSnapshot(
|
||||
new Date(first(throughputResponse.throughput)?.x ?? NaN).toISOString()
|
||||
).toMatchInline(`"2020-09-29T14:30:00.000Z"`);
|
||||
});
|
||||
|
||||
it('has the correct end date', () => {
|
||||
expectSnapshot(
|
||||
new Date(last(throughputResponse.throughput)?.x ?? NaN).toISOString()
|
||||
).toMatchInline(`"2020-09-29T15:00:00.000Z"`);
|
||||
});
|
||||
|
||||
it('has the correct number of buckets', () => {
|
||||
expectSnapshot(throughputResponse.throughput.length).toMatchInline(`61`);
|
||||
});
|
||||
|
||||
it('has the correct throughput', () => {
|
||||
expectSnapshot(throughputResponse.throughput).toMatch();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue