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] Add sparklines to the multi-signal view table #187782

Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { v4 as uuidv4 } from 'uuid';
import { ApmDocumentType } from '../../../../../common/document_type';
import { APIReturnType } from '../../../../services/rest/create_call_apm_api';
import { useApmParams } from '../../../../hooks/use_apm_params';
import { useFetcher } from '../../../../hooks/use_fetcher';
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
import { useTimeRange } from '../../../../hooks/use_time_range';
import { EmptyMessage } from '../../../shared/empty_message';
import { SearchBar } from '../../../shared/search_bar/search_bar';
Expand All @@ -22,6 +23,9 @@ import {
MultiSignalServicesTable,
ServiceInventoryFieldName,
} from './table/multi_signal_services_table';
import { ServiceListItem } from '../../../../../common/service_inventory';
import { usePreferredDataSourceAndBucketSize } from '../../../../hooks/use_preferred_data_source_and_bucket_size';
import { useProgressiveFetcher } from '../../../../hooks/use_progressive_fetcher';

type MainStatisticsApiResponse = APIReturnType<'GET /internal/apm/entities/services'>;

Expand Down Expand Up @@ -74,9 +78,72 @@ function useServicesEntitiesMainStatisticsFetcher() {
return { mainStatisticsData: data, mainStatisticsStatus: status };
}

export const MultiSignalInventory = () => {
function useServicesEntitiesDetailedStatisticsFetcher({
mainStatisticsFetch,
services,
}: {
mainStatisticsFetch: ReturnType<typeof useServicesEntitiesMainStatisticsFetcher>;
services: ServiceListItem[];
}) {
const {
query: { rangeFrom, rangeTo, environment, kuery },
} = useApmParams('/services');

const { start, end } = useTimeRange({ rangeFrom, rangeTo });

const dataSourceOptions = usePreferredDataSourceAndBucketSize({
start,
end,
kuery,
type: ApmDocumentType.ServiceTransactionMetric,
numBuckets: 20,
});

const { mainStatisticsData, mainStatisticsStatus } = mainStatisticsFetch;

const timeseriesDataFetch = useProgressiveFetcher(
(callApmApi) => {
const serviceNames = services.map(({ serviceName }) => serviceName);

if (
start &&
end &&
serviceNames.length > 0 &&
mainStatisticsStatus === FETCH_STATUS.SUCCESS &&
dataSourceOptions
) {
return callApmApi('POST /internal/apm/entities/services/detailed_statistics', {
params: {
query: {
environment,
kuery,
start,
end,
documentType: dataSourceOptions.source.documentType,
rollupInterval: dataSourceOptions.source.rollupInterval,
bucketSizeInSeconds: dataSourceOptions.bucketSizeInSeconds,
},
body: {
// Service name is sorted to guarantee the same order every time this API is called so the result can be cached.
serviceNames: JSON.stringify(serviceNames.sort()),
},
},
});
}
},
// only fetches detailed statistics when requestId is invalidated by main statistics api call or offset is changed
// eslint-disable-next-line react-hooks/exhaustive-deps
[mainStatisticsData.requestId, services],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the dependency that causes the API to be unnecessarily called?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is similar to the request we have in the services overview (POST /internal/apm/services/detailed_statistics) It's start, end, environment, kuery, and dataSourceOptions in this case

{ preservePreviousData: false }
);

return { timeseriesDataFetch };
}

export function MultiSignalInventory() {
const [searchQuery, setSearchQuery] = React.useState('');
const { mainStatisticsData, mainStatisticsStatus } = useServicesEntitiesMainStatisticsFetcher();
const mainStatisticsFetch = useServicesEntitiesMainStatisticsFetcher();

const initialSortField = ServiceInventoryFieldName.Throughput;

Expand All @@ -86,6 +153,11 @@ export const MultiSignalInventory = () => {
fieldsToSearch: [ServiceInventoryFieldName.ServiceName],
});

const { timeseriesDataFetch } = useServicesEntitiesDetailedStatisticsFetcher({
mainStatisticsFetch,
services: mainStatisticsData.services,
});

return (
<>
<EuiFlexGroup gutterSize="m">
Expand All @@ -110,6 +182,8 @@ export const MultiSignalInventory = () => {
initialSortField={initialSortField}
initialPageSize={INITIAL_PAGE_SIZE}
initialSortDirection={INITIAL_SORT_DIRECTION}
timeseriesData={timeseriesDataFetch?.data}
timeseriesDataLoading={timeseriesDataFetch.status === FETCH_STATUS.LOADING}
noItemsMessage={
<EmptyMessage
heading={i18n.translate('xpack.apm.servicesTable.notFoundLabel', {
Expand All @@ -122,4 +196,4 @@ export const MultiSignalInventory = () => {
</EuiFlexGroup>
</>
);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,26 @@ import { TruncateWithTooltip } from '../../../../shared/truncate_with_tooltip';
import { ServiceInventoryFieldName } from './multi_signal_services_table';
import { EntityServiceListItem, SignalTypes } from '../../../../../../common/entities/types';
import { isApmSignal } from '../../../../../utils/get_signal_type';
import { APIReturnType } from '../../../../../services/rest/create_call_apm_api';

type ServicesDetailedStatisticsAPIResponse =
APIReturnType<'POST /internal/apm/entities/services/detailed_statistics'>;

export function getServiceColumns({
query,
breakpoints,
link,
timeseriesDataLoading,
timeseriesData,
}: {
query: TypeOf<ApmRoutes, '/services'>['query'];
breakpoints: Breakpoints;
link: any;
timeseriesDataLoading: boolean;
timeseriesData?: ServicesDetailedStatisticsAPIResponse;
}): Array<ITableColumn<EntityServiceListItem>> {
const { isSmall, isLarge } = breakpoints;
const showWhenSmallOrGreaterThanLarge = isSmall || !isLarge;
return [
{
field: ServiceInventoryFieldName.ServiceName,
Expand Down Expand Up @@ -90,17 +101,18 @@ export function getServiceColumns({
sortable: true,
dataType: 'number',
align: RIGHT_ALIGNMENT,
render: (_, { metrics, signalTypes }) => {
render: (_, { metrics, serviceName, signalTypes }) => {
const { currentPeriodColor } = getTimeSeriesColor(ChartType.LATENCY_AVG);

return !isApmSignal(signalTypes) ? (
<NotAvailableApmMetrics />
) : (
<ListMetric
isLoading={false}
isLoading={timeseriesDataLoading}
series={timeseriesData?.currentPeriod?.apm[serviceName]?.latency}
color={currentPeriodColor}
hideSeries
valueLabel={asMillisecondDuration(metrics.latency)}
hideSeries={!showWhenSmallOrGreaterThanLarge}
/>
);
},
Expand All @@ -113,17 +125,18 @@ export function getServiceColumns({
sortable: true,
dataType: 'number',
align: RIGHT_ALIGNMENT,
render: (_, { metrics, signalTypes }) => {
render: (_, { metrics, serviceName, signalTypes }) => {
const { currentPeriodColor } = getTimeSeriesColor(ChartType.THROUGHPUT);

return !isApmSignal(signalTypes) ? (
<NotAvailableApmMetrics />
) : (
<ListMetric
isLoading={false}
color={currentPeriodColor}
hideSeries
valueLabel={asTransactionRate(metrics.throughput)}
isLoading={timeseriesDataLoading}
series={timeseriesData?.currentPeriod?.apm[serviceName]?.throughput}
hideSeries={!showWhenSmallOrGreaterThanLarge}
/>
);
},
Expand All @@ -136,17 +149,18 @@ export function getServiceColumns({
sortable: true,
dataType: 'number',
align: RIGHT_ALIGNMENT,
render: (_, { metrics, signalTypes }) => {
render: (_, { metrics, serviceName, signalTypes }) => {
const { currentPeriodColor } = getTimeSeriesColor(ChartType.FAILED_TRANSACTION_RATE);

return !isApmSignal(signalTypes) ? (
<NotAvailableApmMetrics />
) : (
<ListMetric
isLoading={false}
color={currentPeriodColor}
hideSeries
valueLabel={asPercent(metrics.failedTransactionRate, 1)}
isLoading={timeseriesDataLoading}
series={timeseriesData?.currentPeriod?.apm[serviceName]?.transactionErrorRate}
hideSeries={!showWhenSmallOrGreaterThanLarge}
/>
);
},
Expand All @@ -159,15 +173,16 @@ export function getServiceColumns({
sortable: true,
dataType: 'number',
align: RIGHT_ALIGNMENT,
render: (_, { metrics }) => {
render: (_, { metrics, serviceName }) => {
const { currentPeriodColor } = getTimeSeriesColor(ChartType.LOG_RATE);

return (
<ListMetric
isLoading={false}
color={currentPeriodColor}
hideSeries
series={timeseriesData?.currentPeriod?.logRate[serviceName] ?? []}
valueLabel={asDecimalOrInteger(metrics.logRatePerMinute)}
hideSeries={!showWhenSmallOrGreaterThanLarge}
/>
);
},
Expand All @@ -180,15 +195,16 @@ export function getServiceColumns({
sortable: true,
dataType: 'number',
align: RIGHT_ALIGNMENT,
render: (_, { metrics }) => {
render: (_, { metrics, serviceName }) => {
const { currentPeriodColor } = getTimeSeriesColor(ChartType.LOG_ERROR_RATE);

return (
<ListMetric
isLoading={false}
color={currentPeriodColor}
hideSeries
series={timeseriesData?.currentPeriod?.logErrorRate[serviceName] ?? []}
valueLabel={asPercent(metrics.logErrorRate, 1)}
hideSeries={!showWhenSmallOrGreaterThanLarge}
/>
);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { ManagedTable } from '../../../../shared/managed_table';
import { getServiceColumns } from './get_service_columns';

type MainStatisticsApiResponse = APIReturnType<'GET /internal/apm/entities/services'>;
type ServicesDetailedStatisticsAPIResponse =
APIReturnType<'POST /internal/apm/entities/services/detailed_statistics'>;

export enum ServiceInventoryFieldName {
ServiceName = 'serviceName',
Expand All @@ -35,6 +37,8 @@ interface Props {
initialSortDirection: 'asc' | 'desc';
noItemsMessage: React.ReactNode;
data: MainStatisticsApiResponse['services'];
timeseriesDataLoading: boolean;
timeseriesData?: ServicesDetailedStatisticsAPIResponse;
}

export function MultiSignalServicesTable({
Expand All @@ -44,6 +48,8 @@ export function MultiSignalServicesTable({
initialPageSize,
initialSortDirection,
noItemsMessage,
timeseriesDataLoading,
timeseriesData,
}: Props) {
const breakpoints = useBreakpoints();
const { query } = useApmParams('/services');
Expand All @@ -55,8 +61,10 @@ export function MultiSignalServicesTable({
query: omit(query, 'page', 'pageSize', 'sortDirection', 'sortField'),
breakpoints,
link,
timeseriesDataLoading,
timeseriesData,
});
}, [query, link, breakpoints]);
}, [query, breakpoints, link, timeseriesDataLoading, timeseriesData]);

return (
<EuiFlexGroup gutterSize="xs" direction="column" responsive={false}>
Expand Down
Loading