-
Notifications
You must be signed in to change notification settings - Fork 8.5k
[APM] Load list of services from ML/terms enum #126581
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
Changes from all commits
999c2d0
00a6bb3
3fd7549
829e039
c73396e
e034e7b
dfb1314
35488f5
b872fc9
1c7cb29
3d930e7
2b84ff2
618303c
52df3c3
054f979
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| /* | ||
| * 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 { AgentName } from '../typings/es_schemas/ui/fields/agent'; | ||
| import { ServiceHealthStatus } from './service_health_status'; | ||
|
|
||
| export interface ServiceListItem { | ||
| serviceName: string; | ||
| healthStatus?: ServiceHealthStatus; | ||
| transactionType?: string; | ||
| agentName?: AgentName; | ||
| throughput?: number; | ||
| latency?: number | null; | ||
| transactionErrorRate?: number | null; | ||
| environments?: string[]; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -12,21 +12,20 @@ import uuid from 'uuid'; | |||||
| import { useAnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context'; | ||||||
| import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; | ||||||
| import { useLocalStorage } from '../../../hooks/use_local_storage'; | ||||||
| import { useAnyOfApmParams } from '../../../hooks/use_apm_params'; | ||||||
| import { useApmParams } from '../../../hooks/use_apm_params'; | ||||||
| import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; | ||||||
| import { useTimeRange } from '../../../hooks/use_time_range'; | ||||||
| import { SearchBar } from '../../shared/search_bar'; | ||||||
| import { getTimeRangeComparison } from '../../shared/time_comparison/get_time_range_comparison'; | ||||||
| import { ServiceList } from './service_list'; | ||||||
| import { MLCallout, shouldDisplayMlCallout } from '../../shared/ml_callout'; | ||||||
| import { joinByKey } from '../../../../common/utils/join_by_key'; | ||||||
|
|
||||||
| const initialData = { | ||||||
| requestId: '', | ||||||
| mainStatisticsData: { | ||||||
| items: [], | ||||||
| hasHistoricalData: true, | ||||||
| hasLegacyData: false, | ||||||
| }, | ||||||
| items: [], | ||||||
| hasHistoricalData: true, | ||||||
| hasLegacyData: false, | ||||||
| }; | ||||||
|
|
||||||
| function useServicesFetcher() { | ||||||
|
|
@@ -36,7 +35,7 @@ function useServicesFetcher() { | |||||
|
|
||||||
| const { | ||||||
| query: { rangeFrom, rangeTo, environment, kuery }, | ||||||
| } = useAnyOfApmParams('/services/{serviceName}', '/services'); | ||||||
| } = useApmParams('/services'); | ||||||
|
|
||||||
| const { start, end } = useTimeRange({ rangeFrom, rangeTo }); | ||||||
|
|
||||||
|
|
@@ -47,7 +46,23 @@ function useServicesFetcher() { | |||||
| comparisonType, | ||||||
| }); | ||||||
|
|
||||||
| const { data = initialData, status: mainStatisticsStatus } = useFetcher( | ||||||
| const sortedAndFilteredServicesFetch = useFetcher( | ||||||
| (callApmApi) => { | ||||||
| return callApmApi('GET /internal/apm/sorted_and_filtered_services', { | ||||||
| params: { | ||||||
| query: { | ||||||
| start, | ||||||
| end, | ||||||
| environment, | ||||||
| kuery, | ||||||
| }, | ||||||
| }, | ||||||
| }); | ||||||
| }, | ||||||
| [start, end, environment, kuery] | ||||||
| ); | ||||||
|
|
||||||
| const mainStatisticsFetch = useFetcher( | ||||||
| (callApmApi) => { | ||||||
| if (start && end) { | ||||||
| return callApmApi('GET /internal/apm/services', { | ||||||
|
|
@@ -62,17 +77,17 @@ function useServicesFetcher() { | |||||
| }).then((mainStatisticsData) => { | ||||||
| return { | ||||||
| requestId: uuid(), | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know you didn't add this but wouldn't it be better if const mainStatisticsFetch = useFetcher();
// ...
const { requestId, data, status, error } = mainStatisticsFetch; |
||||||
| mainStatisticsData, | ||||||
| ...mainStatisticsData, | ||||||
| }; | ||||||
| }); | ||||||
| } | ||||||
| }, | ||||||
| [environment, kuery, start, end] | ||||||
| ); | ||||||
|
|
||||||
| const { mainStatisticsData, requestId } = data; | ||||||
| const { data: mainStatisticsData = initialData } = mainStatisticsFetch; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Probably just me being old school but I prefer this
Suggested change
|
||||||
|
|
||||||
| const { data: comparisonData } = useFetcher( | ||||||
| const comparisonFetch = useFetcher( | ||||||
| (callApmApi) => { | ||||||
| if (start && end && mainStatisticsData.items.length) { | ||||||
| return callApmApi('GET /internal/apm/services/detailed_statistics', { | ||||||
|
|
@@ -96,20 +111,23 @@ function useServicesFetcher() { | |||||
| }, | ||||||
| // 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 | ||||||
| [requestId, offset], | ||||||
| [mainStatisticsData.requestId, offset], | ||||||
| { preservePreviousData: false } | ||||||
| ); | ||||||
|
|
||||||
| return { | ||||||
| mainStatisticsData, | ||||||
| mainStatisticsStatus, | ||||||
| comparisonData, | ||||||
| sortedAndFilteredServicesFetch, | ||||||
| mainStatisticsFetch, | ||||||
| comparisonFetch, | ||||||
| }; | ||||||
| } | ||||||
|
|
||||||
| export function ServiceInventory() { | ||||||
| const { mainStatisticsData, mainStatisticsStatus, comparisonData } = | ||||||
| useServicesFetcher(); | ||||||
| const { | ||||||
| sortedAndFilteredServicesFetch, | ||||||
| mainStatisticsFetch, | ||||||
| comparisonFetch, | ||||||
| } = useServicesFetcher(); | ||||||
|
|
||||||
| const { anomalyDetectionSetupState } = useAnomalyDetectionJobsContext(); | ||||||
|
|
||||||
|
|
@@ -122,8 +140,13 @@ export function ServiceInventory() { | |||||
| !userHasDismissedCallout && | ||||||
| shouldDisplayMlCallout(anomalyDetectionSetupState); | ||||||
|
|
||||||
| const isLoading = mainStatisticsStatus === FETCH_STATUS.LOADING; | ||||||
| const isFailure = mainStatisticsStatus === FETCH_STATUS.FAILURE; | ||||||
| const isLoading = | ||||||
| sortedAndFilteredServicesFetch.status === FETCH_STATUS.LOADING || | ||||||
| (sortedAndFilteredServicesFetch.status === FETCH_STATUS.SUCCESS && | ||||||
| sortedAndFilteredServicesFetch.data?.services.length === 0 && | ||||||
| mainStatisticsFetch.status === FETCH_STATUS.LOADING); | ||||||
|
|
||||||
| const isFailure = mainStatisticsFetch.status === FETCH_STATUS.FAILURE; | ||||||
| const noItemsMessage = ( | ||||||
| <EuiEmptyPrompt | ||||||
| title={ | ||||||
|
|
@@ -137,6 +160,14 @@ export function ServiceInventory() { | |||||
| /> | ||||||
| ); | ||||||
|
|
||||||
| const items = joinByKey( | ||||||
| [ | ||||||
| ...(sortedAndFilteredServicesFetch.data?.services ?? []), | ||||||
| ...(mainStatisticsFetch.data?.items ?? []), | ||||||
| ], | ||||||
| 'serviceName' | ||||||
| ); | ||||||
|
|
||||||
| return ( | ||||||
| <> | ||||||
| <SearchBar showTimeComparison /> | ||||||
|
|
@@ -154,8 +185,8 @@ export function ServiceInventory() { | |||||
| <ServiceList | ||||||
| isLoading={isLoading} | ||||||
| isFailure={isFailure} | ||||||
| items={mainStatisticsData.items} | ||||||
| comparisonData={comparisonData} | ||||||
| items={items} | ||||||
| comparisonData={comparisonFetch?.data} | ||||||
| noItemsMessage={noItemsMessage} | ||||||
| /> | ||||||
| </EuiFlexItem> | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,7 +17,6 @@ import { i18n } from '@kbn/i18n'; | |||||||||||
| import { TypeOf } from '@kbn/typed-react-router-config'; | ||||||||||||
| import { orderBy } from 'lodash'; | ||||||||||||
| import React, { useMemo } from 'react'; | ||||||||||||
| import { ValuesType } from 'utility-types'; | ||||||||||||
| import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; | ||||||||||||
| import { ServiceHealthStatus } from '../../../../../common/service_health_status'; | ||||||||||||
| import { | ||||||||||||
|
|
@@ -46,14 +45,11 @@ import { | |||||||||||
| getTimeSeriesColor, | ||||||||||||
| } from '../../../shared/charts/helper/get_timeseries_color'; | ||||||||||||
| import { HealthBadge } from './health_badge'; | ||||||||||||
| import { ServiceListItem } from '../../../../../common/service_inventory'; | ||||||||||||
|
|
||||||||||||
| type ServiceListAPIResponse = APIReturnType<'GET /internal/apm/services'>; | ||||||||||||
| type Items = ServiceListAPIResponse['items']; | ||||||||||||
| type ServicesDetailedStatisticsAPIResponse = | ||||||||||||
| APIReturnType<'GET /internal/apm/services/detailed_statistics'>; | ||||||||||||
|
|
||||||||||||
| type ServiceListItem = ValuesType<Items>; | ||||||||||||
|
|
||||||||||||
| function formatString(value?: string | null) { | ||||||||||||
| return value || NOT_AVAILABLE_LABEL; | ||||||||||||
| } | ||||||||||||
|
|
@@ -239,7 +235,7 @@ export function getServiceColumns({ | |||||||||||
| } | ||||||||||||
|
|
||||||||||||
| interface Props { | ||||||||||||
| items: Items; | ||||||||||||
| items: ServiceListItem[]; | ||||||||||||
| comparisonData?: ServicesDetailedStatisticsAPIResponse; | ||||||||||||
| noItemsMessage?: React.ReactNode; | ||||||||||||
| isLoading: boolean; | ||||||||||||
|
|
@@ -287,9 +283,8 @@ export function ServiceList({ | |||||||||||
| ] | ||||||||||||
| ); | ||||||||||||
|
|
||||||||||||
| const initialSortField = displayHealthStatus | ||||||||||||
| ? 'healthStatus' | ||||||||||||
| : 'transactionsPerMinute'; | ||||||||||||
| const initialSortField = displayHealthStatus ? 'healthStatus' : 'serviceName'; | ||||||||||||
| const initialSortDirection = displayHealthStatus ? 'desc' : 'asc'; | ||||||||||||
|
Comment on lines
+286
to
+287
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit
Suggested change
|
||||||||||||
|
|
||||||||||||
| return ( | ||||||||||||
| <EuiFlexGroup gutterSize="xs" direction="column" responsive={false}> | ||||||||||||
|
|
@@ -336,9 +331,9 @@ export function ServiceList({ | |||||||||||
| items={items} | ||||||||||||
| noItemsMessage={noItemsMessage} | ||||||||||||
| initialSortField={initialSortField} | ||||||||||||
| initialSortDirection="desc" | ||||||||||||
| initialSortDirection={initialSortDirection} | ||||||||||||
| sortFn={(itemsToSort, sortField, sortDirection) => { | ||||||||||||
| // For healthStatus, sort items by healthStatus first, then by TPM | ||||||||||||
| // For healthStatus, sort items by healthStatus first, then by name | ||||||||||||
| return sortField === 'healthStatus' | ||||||||||||
| ? orderBy( | ||||||||||||
| itemsToSort, | ||||||||||||
|
|
@@ -348,9 +343,9 @@ export function ServiceList({ | |||||||||||
| ? SERVICE_HEALTH_STATUS_ORDER.indexOf(item.healthStatus) | ||||||||||||
| : -1; | ||||||||||||
| }, | ||||||||||||
| (item) => item.throughput ?? 0, | ||||||||||||
| (item) => item.serviceName.toLowerCase(), | ||||||||||||
| ], | ||||||||||||
| [sortDirection, sortDirection] | ||||||||||||
| [sortDirection, sortDirection === 'asc' ? 'desc' : 'asc'] | ||||||||||||
| ) | ||||||||||||
| : orderBy( | ||||||||||||
| itemsToSort, | ||||||||||||
|
|
||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| import { Logger } from '@kbn/logging'; | ||
| import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; | ||
| import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; | ||
| import { Environment } from '../../../../common/environment_rt'; | ||
| import { ProcessorEvent } from '../../../../common/processor_event'; | ||
| import { joinByKey } from '../../../../common/utils/join_by_key'; | ||
| import { Setup } from '../../../lib/helpers/setup_request'; | ||
| import { getHealthStatuses } from './get_health_statuses'; | ||
|
|
||
| export async function getSortedAndFilteredServices({ | ||
| setup, | ||
| start, | ||
| end, | ||
| environment, | ||
| logger, | ||
| }: { | ||
| setup: Setup; | ||
| start: number; | ||
| end: number; | ||
| environment: Environment; | ||
| logger: Logger; | ||
| }) { | ||
| const { apmEventClient } = setup; | ||
|
|
||
| async function getServicesFromTermsEnum() { | ||
| if (environment !== ENVIRONMENT_ALL.value) { | ||
| return []; | ||
| } | ||
| const response = await apmEventClient.termsEnum( | ||
| 'get_services_from_terms_enum', | ||
| { | ||
| apm: { | ||
| events: [ | ||
| ProcessorEvent.transaction, | ||
| ProcessorEvent.span, | ||
| ProcessorEvent.metric, | ||
| ProcessorEvent.error, | ||
| ], | ||
| }, | ||
| body: { | ||
| size: 500, | ||
| field: SERVICE_NAME, | ||
| }, | ||
| } | ||
| ); | ||
|
|
||
| return response.terms; | ||
| } | ||
|
|
||
| const [servicesWithHealthStatuses, serviceNamesFromTermsEnum] = | ||
| await Promise.all([ | ||
| getHealthStatuses({ | ||
| setup, | ||
| start, | ||
| end, | ||
| environment, | ||
| }).catch((error) => { | ||
| logger.error(error); | ||
| return []; | ||
| }), | ||
| getServicesFromTermsEnum(), | ||
| ]); | ||
|
|
||
| const services = joinByKey( | ||
| [ | ||
| ...servicesWithHealthStatuses, | ||
| ...serviceNamesFromTermsEnum.map((serviceName) => ({ serviceName })), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we do this within
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it does return just a list of service names (ie, strings). this converts it to an object to match what should be finally returned. |
||
| ], | ||
| 'serviceName' | ||
| ); | ||
|
|
||
| return services; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
hasLegacyDatais leftover and should have been removed in #125962.hasHistoricalDataI'm not sure about - I don't see any references to it anywhere. Was that removed accidentally? Or perhaps we replaced that with something else when we introduced the new Observability "no data" screen?tldr: I think both of these should be removed but please double check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hasHistoricalDatawas removed in #111630. It is no longer needed.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, it looks like the message is now simply saying "No services found" when there is no data.
kibana/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx
Lines 127 to 138 in 8b82657
It should instead say something like "No data found for selected period. Please adjust your time range and other possible filters" (because we know there is historical data available)
(fyi @MiriamAparicio)