mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[APM] Display comparison for mobile stats (#149097)
## Summary related to https://github.com/elastic/kibana/issues/146854 1. Show the comparison for the mobile stats 2. Display a badge "comparison not supported" (when the comparison is enabled )for the components that don't support comparison 3. display "coming soon" text for the metrics that are not available yet Addressing feedback - Replace the badge with a tooltip with an icon - Always display the previous state for metrics when loading and add the spinner - Update Crash rate to Crash rate (Crash per minute) - Remove fallback to transaction events badge ## Before <img width="1420" alt="image" src="https://user-images.githubusercontent.com/3369346/213138845-3eab0bf5-a24e-4ec0-87fb-d8eacc029a2f.png"> ## After  Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
43247bdc0f
commit
026d347305
9 changed files with 152 additions and 74 deletions
|
@ -356,7 +356,9 @@ const scenario: Scenario<ApmFields> = async ({ scenarioOpts, logger }) => {
|
|||
.span({
|
||||
spanName: 'onCreate',
|
||||
spanType: 'app',
|
||||
spanSubtype: 'internal',
|
||||
spanSubtype: 'external',
|
||||
'service.target.type': 'http',
|
||||
'span.destination.service.resource': 'external',
|
||||
})
|
||||
.duration(50)
|
||||
.success()
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
EuiSpacer,
|
||||
EuiTitle,
|
||||
EuiCallOut,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -38,7 +39,6 @@ import { MostUsedChart } from './most_used_chart';
|
|||
import { LatencyMap } from './latency_map';
|
||||
import { FailedTransactionRateChart } from '../../../shared/charts/failed_transaction_rate_chart';
|
||||
import { ServiceOverviewDependenciesTable } from '../../service_overview/service_overview_dependencies_table';
|
||||
import { AggregatedTransactionsBadge } from '../../../shared/aggregated_transactions_badge';
|
||||
import { LatencyChart } from '../../../shared/charts/latency_chart';
|
||||
import { useFiltersForEmbeddableCharts } from '../../../../hooks/use_filters_for_embeddable_charts';
|
||||
import { getKueryWithMobileFilters } from '../../../../../common/utils/get_kuery_with_mobile_filters';
|
||||
|
@ -50,7 +50,7 @@ import { MobileStats } from './stats';
|
|||
export const chartHeight = 288;
|
||||
|
||||
export function MobileServiceOverview() {
|
||||
const { serviceName, fallbackToTransactions } = useApmServiceContext();
|
||||
const { serviceName } = useApmServiceContext();
|
||||
const router = useApmRouter();
|
||||
const embeddableFilters = useFiltersForEmbeddableCharts();
|
||||
|
||||
|
@ -65,6 +65,7 @@ export function MobileServiceOverview() {
|
|||
osVersion,
|
||||
appVersion,
|
||||
netConnectionType,
|
||||
comparisonEnabled,
|
||||
},
|
||||
} = useApmParams('/mobile-services/{serviceName}/overview');
|
||||
|
||||
|
@ -142,11 +143,6 @@ export function MobileServiceOverview() {
|
|||
</EuiCallOut>
|
||||
<EuiSpacer size="s" />
|
||||
</EuiFlexItem>
|
||||
{fallbackToTransactions && (
|
||||
<EuiFlexItem>
|
||||
<AggregatedTransactionsBadge />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<MobileStats
|
||||
start={start}
|
||||
|
@ -163,24 +159,46 @@ export function MobileServiceOverview() {
|
|||
end={end}
|
||||
kuery={kueryWithMobileFilters}
|
||||
filters={embeddableFilters}
|
||||
comparisonEnabled={comparisonEnabled}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={7}>
|
||||
<EuiPanel hasBorder={true}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
{i18n.translate(
|
||||
'xpack.apm.serviceOverview.mostUsedTitle',
|
||||
{
|
||||
defaultMessage: 'Most used',
|
||||
}
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
alignItems="center"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
{i18n.translate(
|
||||
'xpack.apm.serviceOverview.mostUsedTitle',
|
||||
{
|
||||
defaultMessage: 'Top 5 most used',
|
||||
}
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{comparisonEnabled && (
|
||||
<EuiIconTip
|
||||
content={i18n.translate(
|
||||
'xpack.apm.comparison.not.support',
|
||||
{
|
||||
defaultMessage: 'Comparison is not supported',
|
||||
}
|
||||
)}
|
||||
size="m"
|
||||
type="alert"
|
||||
color="warning"
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexGroup direction={rowDirection} gutterSize="s">
|
||||
{/* Device */}
|
||||
<EuiFlexItem>
|
||||
|
|
|
@ -6,7 +6,13 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiTitle, EuiSpacer } from '@elastic/eui';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import { EmbeddedMap } from './embedded_map';
|
||||
|
@ -16,21 +22,39 @@ export function LatencyMap({
|
|||
end,
|
||||
kuery,
|
||||
filters,
|
||||
comparisonEnabled,
|
||||
}: {
|
||||
start: string;
|
||||
end: string;
|
||||
kuery?: string;
|
||||
filters: Filter[];
|
||||
comparisonEnabled: boolean;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<EuiTitle size="xs">
|
||||
<h3>
|
||||
{i18n.translate('xpack.apm.serviceOverview.embeddedMap.title', {
|
||||
defaultMessage: 'Average latency per country',
|
||||
})}
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
{i18n.translate('xpack.apm.serviceOverview.embeddedMap.title', {
|
||||
defaultMessage: 'Average latency per country',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{comparisonEnabled && (
|
||||
<EuiIconTip
|
||||
content={i18n.translate('xpack.apm.comparison.not.support', {
|
||||
defaultMessage: 'Comparison is not supported',
|
||||
})}
|
||||
size="m"
|
||||
type="alert"
|
||||
color="warning"
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
<EmbeddedMap start={start} end={end} kuery={kuery} filters={filters} />
|
||||
</>
|
||||
|
|
|
@ -6,30 +6,24 @@
|
|||
*/
|
||||
import { MetricDatum, MetricTrendShape } from '@elastic/charts';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLoadingSpinner,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTheme } from '@kbn/observability-plugin/public';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useAnyOfApmParams } from '../../../../../hooks/use_apm_params';
|
||||
import { useFetcher, FETCH_STATUS } from '../../../../../hooks/use_fetcher';
|
||||
import { MetricItem } from './metric_item';
|
||||
import { usePreviousPeriodLabel } from '../../../../../hooks/use_previous_period_text';
|
||||
|
||||
const valueFormatter = (value: number, suffix = '') => {
|
||||
return `${value} ${suffix}`;
|
||||
};
|
||||
|
||||
const getIcon =
|
||||
(type: string) =>
|
||||
({
|
||||
width = 20,
|
||||
height = 20,
|
||||
color,
|
||||
}: {
|
||||
width: number;
|
||||
height: number;
|
||||
color: string;
|
||||
}) =>
|
||||
<EuiIcon type={type} width={width} height={height} fill={color} />;
|
||||
|
||||
export function MobileStats({
|
||||
start,
|
||||
end,
|
||||
|
@ -43,9 +37,11 @@ export function MobileStats({
|
|||
|
||||
const {
|
||||
path: { serviceName },
|
||||
query: { environment, transactionType },
|
||||
query: { environment, transactionType, offset, comparisonEnabled },
|
||||
} = useAnyOfApmParams('/mobile-services/{serviceName}/overview');
|
||||
|
||||
const previousPeriodLabel = usePreviousPeriodLabel();
|
||||
|
||||
const { data, status } = useFetcher(
|
||||
(callApmApi) => {
|
||||
return callApmApi(
|
||||
|
@ -59,31 +55,71 @@ export function MobileStats({
|
|||
environment,
|
||||
kuery,
|
||||
transactionType,
|
||||
offset,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
[start, end, environment, kuery, serviceName, transactionType]
|
||||
[start, end, environment, kuery, serviceName, transactionType, offset]
|
||||
);
|
||||
|
||||
const getComparisonValueFormatter = useCallback(
|
||||
(value) => {
|
||||
return (
|
||||
<span>
|
||||
{value && comparisonEnabled
|
||||
? `${previousPeriodLabel}: ${value}`
|
||||
: null}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
[comparisonEnabled, previousPeriodLabel]
|
||||
);
|
||||
|
||||
const getIcon = useCallback(
|
||||
(type: string) =>
|
||||
({
|
||||
width = 20,
|
||||
height = 20,
|
||||
color,
|
||||
}: {
|
||||
width: number;
|
||||
height: number;
|
||||
color: string;
|
||||
}) => {
|
||||
return status === FETCH_STATUS.LOADING ? (
|
||||
<EuiLoadingSpinner size="m" />
|
||||
) : (
|
||||
<EuiIcon type={type} width={width} height={height} fill={color} />
|
||||
);
|
||||
},
|
||||
[status]
|
||||
);
|
||||
|
||||
const metrics: MetricDatum[] = [
|
||||
{
|
||||
color: euiTheme.eui.euiColorLightestShade,
|
||||
color: euiTheme.eui.euiColorDisabled,
|
||||
title: i18n.translate('xpack.apm.mobile.metrics.crash.rate', {
|
||||
defaultMessage: 'Crash Rate',
|
||||
defaultMessage: 'Crash Rate (Crash per minute)',
|
||||
}),
|
||||
subtitle: i18n.translate('xpack.apm.mobile.coming.soon', {
|
||||
defaultMessage: 'Coming Soon',
|
||||
}),
|
||||
icon: getIcon('bug'),
|
||||
value: 'N/A',
|
||||
valueFormatter: (value: number) => valueFormatter(value, 'cpm'),
|
||||
valueFormatter: (value: number) => valueFormatter(value),
|
||||
trend: [],
|
||||
trendShape: MetricTrendShape.Area,
|
||||
},
|
||||
{
|
||||
color: euiTheme.eui.euiColorLightestShade,
|
||||
color: euiTheme.eui.euiColorDisabled,
|
||||
title: i18n.translate('xpack.apm.mobile.metrics.load.time', {
|
||||
defaultMessage: 'Slowest App load time',
|
||||
}),
|
||||
subtitle: i18n.translate('xpack.apm.mobile.coming.soon', {
|
||||
defaultMessage: 'Coming Soon',
|
||||
}),
|
||||
icon: getIcon('visGauge'),
|
||||
value: 'N/A',
|
||||
valueFormatter: (value: number) => valueFormatter(value, 's'),
|
||||
|
@ -99,6 +135,7 @@ export function MobileStats({
|
|||
value: data?.currentPeriod?.sessions?.value ?? NaN,
|
||||
valueFormatter: (value: number) => valueFormatter(value),
|
||||
trend: data?.currentPeriod?.sessions?.timeseries,
|
||||
extra: getComparisonValueFormatter(data?.previousPeriod.sessions?.value),
|
||||
trendShape: MetricTrendShape.Area,
|
||||
},
|
||||
{
|
||||
|
@ -108,8 +145,9 @@ export function MobileStats({
|
|||
}),
|
||||
icon: getIcon('kubernetesPod'),
|
||||
value: data?.currentPeriod?.requests?.value ?? NaN,
|
||||
extra: getComparisonValueFormatter(data?.previousPeriod.requests?.value),
|
||||
valueFormatter: (value: number) => valueFormatter(value),
|
||||
trend: data?.currentPeriod?.requests?.timeseries ?? [],
|
||||
trend: data?.currentPeriod?.requests?.timeseries,
|
||||
trendShape: MetricTrendShape.Area,
|
||||
},
|
||||
];
|
||||
|
@ -121,7 +159,8 @@ export function MobileStats({
|
|||
<MetricItem
|
||||
id={key}
|
||||
data={[metric]}
|
||||
isLoading={status === FETCH_STATUS.LOADING}
|
||||
hasData={!isEmpty(data)}
|
||||
status={status}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
|
|
|
@ -7,15 +7,18 @@
|
|||
import React from 'react';
|
||||
import { Chart, Metric, MetricDatum } from '@elastic/charts';
|
||||
import { EuiLoadingContent, EuiPanel } from '@elastic/eui';
|
||||
import { FETCH_STATUS, isPending } from '../../../../../hooks/use_fetcher';
|
||||
|
||||
export function MetricItem({
|
||||
data,
|
||||
id,
|
||||
isLoading,
|
||||
status,
|
||||
hasData,
|
||||
}: {
|
||||
data: MetricDatum[];
|
||||
id: number;
|
||||
isLoading: boolean;
|
||||
status: FETCH_STATUS;
|
||||
hasData: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
|
@ -23,11 +26,11 @@ export function MetricItem({
|
|||
resize: 'none',
|
||||
padding: '0px',
|
||||
overflow: 'auto',
|
||||
height: '100px',
|
||||
height: '120px',
|
||||
borderRadius: '6px',
|
||||
}}
|
||||
>
|
||||
{isLoading ? (
|
||||
{!hasData && isPending(status) ? (
|
||||
<EuiPanel hasBorder={true}>
|
||||
<EuiLoadingContent lines={3} />
|
||||
</EuiPanel>
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiPanel,
|
||||
|
@ -17,7 +16,6 @@ import { useHistory } from 'react-router-dom';
|
|||
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
|
||||
import { useApmParams } from '../../../../hooks/use_apm_params';
|
||||
import { useTimeRange } from '../../../../hooks/use_time_range';
|
||||
import { AggregatedTransactionsBadge } from '../../../shared/aggregated_transactions_badge';
|
||||
import { TransactionsTable } from '../../../shared/transactions_table';
|
||||
import { replace } from '../../../shared/links/url_helpers';
|
||||
import { getKueryWithMobileFilters } from '../../../../../common/utils/get_kuery_with_mobile_filters';
|
||||
|
@ -51,7 +49,7 @@ export function MobileTransactionOverview() {
|
|||
|
||||
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
|
||||
|
||||
const { transactionType, fallbackToTransactions } = useApmServiceContext();
|
||||
const { transactionType } = useApmServiceContext();
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
|
@ -65,16 +63,6 @@ export function MobileTransactionOverview() {
|
|||
<EuiFlexItem>
|
||||
<EuiHorizontalRule />
|
||||
</EuiFlexItem>
|
||||
{fallbackToTransactions && (
|
||||
<>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<AggregatedTransactionsBadge />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
<MobileTransactionCharts
|
||||
transactionType={transactionType}
|
||||
serviceName={serviceName}
|
||||
|
|
|
@ -67,6 +67,7 @@ const mobileStatsRoute = createApmServerRoute({
|
|||
kueryRt,
|
||||
rangeRt,
|
||||
environmentRt,
|
||||
offsetRt,
|
||||
t.partial({
|
||||
transactionType: t.string,
|
||||
}),
|
||||
|
@ -77,7 +78,7 @@ const mobileStatsRoute = createApmServerRoute({
|
|||
const apmEventClient = await getApmEventClient(resources);
|
||||
const { params } = resources;
|
||||
const { serviceName } = params.path;
|
||||
const { kuery, environment, start, end } = params.query;
|
||||
const { kuery, environment, start, end, offset } = params.query;
|
||||
|
||||
const stats = await getMobileStatsPeriods({
|
||||
kuery,
|
||||
|
@ -86,6 +87,7 @@ const mobileStatsRoute = createApmServerRoute({
|
|||
end,
|
||||
serviceName,
|
||||
apmEventClient,
|
||||
offset,
|
||||
});
|
||||
|
||||
return stats;
|
||||
|
|
|
@ -101,7 +101,9 @@ export async function generateMobileData({
|
|||
.span({
|
||||
spanName: 'onCreate',
|
||||
spanType: 'app',
|
||||
spanSubtype: 'internal',
|
||||
spanSubtype: 'external',
|
||||
'service.target.type': 'http',
|
||||
'span.destination.service.resource': 'external',
|
||||
})
|
||||
.duration(50)
|
||||
.success()
|
||||
|
|
|
@ -74,9 +74,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
const response = await getHttpRequestsChart({ serviceName: 'synth-android', offset: '1d' });
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(
|
||||
response.body.currentPeriod.timeseries.some((item) => item.y === 0 && item.x)
|
||||
).to.eql(true);
|
||||
expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql(
|
||||
true
|
||||
);
|
||||
expect(response.body.previousPeriod.timeseries[0].y).to.eql(0);
|
||||
});
|
||||
|
||||
|
@ -88,7 +88,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
response.body.currentPeriod.timeseries.some((item) => item.y === 0 && item.x)
|
||||
).to.eql(true);
|
||||
|
||||
expect(response.body.currentPeriod.timeseries[0].y).to.eql(0);
|
||||
expect(response.body.currentPeriod.timeseries[0].y).to.eql(1);
|
||||
expect(response.body.previousPeriod.timeseries).to.eql([]);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue