Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[APM] Make optimised loading of service inventory opt-in #128471

Merged
merged 10 commits into from
Mar 28, 2022
Merged
10 changes: 10 additions & 0 deletions x-pack/plugins/apm/common/service_inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,13 @@ export interface ServiceListItem {
transactionErrorRate?: number | null;
environments?: string[];
}

export enum ServiceInventoryFieldName {
ServiceName = 'serviceName',
HealthStatus = 'healthStatus',
Environments = 'environments',
TransactionType = 'transactionType',
Throughput = 'throughput',
Latency = 'latency',
TransactionErrorRate = 'transactionErrorRate',
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import { ServiceList } from './service_list';
import { MLCallout, shouldDisplayMlCallout } from '../../shared/ml_callout';
import { joinByKey } from '../../../../common/utils/join_by_key';
import { getTimeRangeComparison } from '../../shared/time_comparison/get_time_range_comparison';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { apmServiceInventoryOptimizedSorting } from '../../../../../observability/common';
import { ServiceInventoryFieldName } from '../../../../common/service_inventory';
import { orderServiceItems } from './service_list/order_service_items';

const initialData = {
requestId: '',
Expand Down Expand Up @@ -165,10 +169,36 @@ export function ServiceInventory() {
/>
);

const mainStatisticsItems = mainStatisticsFetch.data?.items ?? [];
const preloadedServices = sortedAndFilteredServicesFetch.data?.services || [];

const displayHealthStatus = [
...mainStatisticsItems,
...preloadedServices,
].some((item) => 'healthStatus' in item);

const tiebreakerField = useKibana().services.uiSettings?.get<boolean>(
apmServiceInventoryOptimizedSorting
)
? ServiceInventoryFieldName.ServiceName
: ServiceInventoryFieldName.Throughput;

const initialSortField = displayHealthStatus
? ServiceInventoryFieldName.HealthStatus
: tiebreakerField;

const initialSortDirection =
initialSortField === ServiceInventoryFieldName.ServiceName ? 'asc' : 'desc';

const items = joinByKey(
[
...(sortedAndFilteredServicesFetch.data?.services ?? []),
...(mainStatisticsFetch.data?.items ?? []),
// only use preloaded services if tiebreaker field is service.name,
// otherwise ignore them to prevent re-sorting of the table
// once the tiebreaking metric comes in
...(tiebreakerField === ServiceInventoryFieldName.ServiceName
? preloadedServices
: []),
...mainStatisticsItems,
],
'serviceName'
);
Expand All @@ -195,6 +225,17 @@ export function ServiceInventory() {
comparisonFetch.status === FETCH_STATUS.LOADING ||
comparisonFetch.status === FETCH_STATUS.NOT_INITIATED
}
displayHealthStatus={displayHealthStatus}
initialSortField={initialSortField}
initialSortDirection={initialSortDirection}
sortFn={(itemsToSort, sortField, sortDirection) => {
return orderServiceItems({
items: itemsToSort,
primarySortField: sortField,
sortDirection,
tiebreakerField,
});
}}
comparisonData={comparisonFetch?.data}
noItemsMessage={noItemsMessage}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { TypeOf } from '@kbn/typed-react-router-config';
import { orderBy } from 'lodash';
import React, { useMemo } from 'react';
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
import { ServiceHealthStatus } from '../../../../../common/service_health_status';
Expand Down Expand Up @@ -45,7 +44,10 @@ import {
getTimeSeriesColor,
} from '../../../shared/charts/helper/get_timeseries_color';
import { HealthBadge } from './health_badge';
import { ServiceListItem } from '../../../../../common/service_inventory';
import {
ServiceInventoryFieldName,
ServiceListItem,
} from '../../../../../common/service_inventory';

type ServicesDetailedStatisticsAPIResponse =
APIReturnType<'GET /internal/apm/services/detailed_statistics'>;
Expand All @@ -54,13 +56,6 @@ function formatString(value?: string | null) {
return value || NOT_AVAILABLE_LABEL;
}

const SERVICE_HEALTH_STATUS_ORDER = [
ServiceHealthStatus.unknown,
ServiceHealthStatus.healthy,
ServiceHealthStatus.warning,
ServiceHealthStatus.critical,
];

export function getServiceColumns({
query,
showTransactionTypeColumn,
Expand All @@ -84,7 +79,7 @@ export function getServiceColumns({
...(showHealthStatusColumn
? [
{
field: 'healthStatus',
field: ServiceInventoryFieldName.HealthStatus,
name: i18n.translate('xpack.apm.servicesTable.healthColumnLabel', {
defaultMessage: 'Health',
}),
Expand All @@ -101,7 +96,7 @@ export function getServiceColumns({
]
: []),
{
field: 'serviceName',
field: ServiceInventoryFieldName.ServiceName,
name: i18n.translate('xpack.apm.servicesTable.nameColumnLabel', {
defaultMessage: 'Name',
}),
Expand All @@ -123,7 +118,7 @@ export function getServiceColumns({
...(showWhenSmallOrGreaterThanLarge
? [
{
field: 'environments',
field: ServiceInventoryFieldName.Environments,
name: i18n.translate(
'xpack.apm.servicesTable.environmentColumnLabel',
{
Expand All @@ -141,7 +136,7 @@ export function getServiceColumns({
...(showTransactionTypeColumn && showWhenSmallOrGreaterThanXL
? [
{
field: 'transactionType',
field: ServiceInventoryFieldName.TransactionType,
name: i18n.translate(
'xpack.apm.servicesTable.transactionColumnLabel',
{ defaultMessage: 'Transaction type' }
Expand All @@ -152,7 +147,7 @@ export function getServiceColumns({
]
: []),
{
field: 'latency',
field: ServiceInventoryFieldName.Latency,
name: i18n.translate('xpack.apm.servicesTable.latencyAvgColumnLabel', {
defaultMessage: 'Latency (avg.)',
}),
Expand All @@ -179,7 +174,7 @@ export function getServiceColumns({
align: RIGHT_ALIGNMENT,
},
{
field: 'throughput',
field: ServiceInventoryFieldName.Throughput,
name: i18n.translate('xpack.apm.servicesTable.throughputColumnLabel', {
defaultMessage: 'Throughput',
}),
Expand Down Expand Up @@ -207,7 +202,7 @@ export function getServiceColumns({
align: RIGHT_ALIGNMENT,
},
{
field: 'transactionErrorRate',
field: ServiceInventoryFieldName.TransactionErrorRate,
name: i18n.translate('xpack.apm.servicesTable.transactionErrorRate', {
defaultMessage: 'Failed transaction rate',
}),
Expand Down Expand Up @@ -246,6 +241,14 @@ interface Props {
noItemsMessage?: React.ReactNode;
isLoading: boolean;
isFailure?: boolean;
displayHealthStatus: boolean;
initialSortField: ServiceInventoryFieldName;
initialSortDirection: 'asc' | 'desc';
sortFn: (
sortItems: ServiceListItem[],
sortField: ServiceInventoryFieldName,
sortDirection: 'asc' | 'desc'
) => ServiceListItem[];
}

export function ServiceList({
Expand All @@ -255,9 +258,12 @@ export function ServiceList({
comparisonData,
isLoading,
isFailure,
displayHealthStatus,
initialSortField,
initialSortDirection,
sortFn,
}: Props) {
const breakpoints = useBreakpoints();
const displayHealthStatus = items.some((item) => 'healthStatus' in item);

const showTransactionTypeColumn = items.some(
({ transactionType }) =>
Expand Down Expand Up @@ -292,9 +298,6 @@ export function ServiceList({
]
);

const initialSortField = displayHealthStatus ? 'healthStatus' : 'serviceName';
const initialSortDirection = displayHealthStatus ? 'desc' : 'asc';

return (
<EuiFlexGroup gutterSize="xs" direction="column" responsive={false}>
<EuiFlexItem>
Expand Down Expand Up @@ -333,49 +336,21 @@ export function ServiceList({
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<ManagedTable
<ManagedTable<ServiceListItem>
isLoading={isLoading}
error={isFailure}
columns={serviceColumns}
items={items}
noItemsMessage={noItemsMessage}
initialSortField={initialSortField}
initialSortDirection={initialSortDirection}
sortFn={(itemsToSort, sortField, sortDirection) => {
// For healthStatus, sort items by healthStatus first, then by name
return sortField === 'healthStatus'
? orderBy(
itemsToSort,
[
(item) => {
return item.healthStatus
? SERVICE_HEALTH_STATUS_ORDER.indexOf(item.healthStatus)
: -1;
},
(item) => item.serviceName.toLowerCase(),
],
[sortDirection, sortDirection === 'asc' ? 'desc' : 'asc']
)
: orderBy(
itemsToSort,
(item) => {
switch (sortField) {
// Use `?? -1` here so `undefined` will appear after/before `0`.
// In the table this will make the "N/A" items always at the
// bottom/top.
case 'latency':
return item.latency ?? -1;
case 'throughput':
return item.throughput ?? -1;
case 'transactionErrorRate':
return item.transactionErrorRate ?? -1;
default:
return item[sortField as keyof typeof item];
}
},
sortDirection
);
}}
sortFn={(itemsToSort, sortField, sortDirection) =>
sortFn(
itemsToSort,
sortField as ServiceInventoryFieldName,
sortDirection
)
}
/>
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Loading