mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[APM][ECO] Removing Entities inventory from APM (#195116)](https://github.com/elastic/kibana/pull/195116) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Cauê Marcondes","email":"55978943+cauemarcondes@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-10-07T10:46:47Z","message":"[APM][ECO] Removing Entities inventory from APM (#195116)\n\ncloses https://github.com/elastic/kibana/issues/194114\r\n\r\nWe will no longer show the entity inventory page on the APM UI. So, I'm\r\nremoving all its code.","sha":"c44b7de7a23fbd5b11b31cc8d39c5baffb6c8d6f","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-infra_services","v8.16.0"],"title":"[APM][ECO] Removing Entities inventory from APM","number":195116,"url":"https://github.com/elastic/kibana/pull/195116","mergeCommit":{"message":"[APM][ECO] Removing Entities inventory from APM (#195116)\n\ncloses https://github.com/elastic/kibana/issues/194114\r\n\r\nWe will no longer show the entity inventory page on the APM UI. So, I'm\r\nremoving all its code.","sha":"c44b7de7a23fbd5b11b31cc8d39c5baffb6c8d6f"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/195116","number":195116,"mergeCommit":{"message":"[APM][ECO] Removing Entities inventory from APM (#195116)\n\ncloses https://github.com/elastic/kibana/issues/194114\r\n\r\nWe will no longer show the entity inventory page on the APM UI. So, I'm\r\nremoving all its code.","sha":"c44b7de7a23fbd5b11b31cc8d39c5baffb6c8d6f"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com>
This commit is contained in:
parent
078f98c8b0
commit
aac149aff3
46 changed files with 431 additions and 1684 deletions
|
@ -30,8 +30,7 @@
|
|||
"lens",
|
||||
"maps",
|
||||
"uiActions",
|
||||
"logsDataAccess",
|
||||
"entityManager"
|
||||
"logsDataAccess"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"actions",
|
||||
|
|
|
@ -1,36 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { AnalyticsServiceSetup } from '@kbn/core/public';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { ServiceInventoryView } from '../context/entity_manager_context/entity_manager_context';
|
||||
|
||||
export const SERVICE_INVENTORY_STORAGE_KEY = 'apm.service.inventory.view';
|
||||
|
||||
export let serviceInventoryViewType$: BehaviorSubject<{ serviceInventoryViewType: string }>;
|
||||
|
||||
export function registerServiceInventoryViewTypeContext(analytics: AnalyticsServiceSetup) {
|
||||
const serviceInventoryLocalStorageValue = window.localStorage.getItem(
|
||||
SERVICE_INVENTORY_STORAGE_KEY
|
||||
);
|
||||
serviceInventoryViewType$ = new BehaviorSubject({
|
||||
serviceInventoryViewType:
|
||||
serviceInventoryLocalStorageValue === null
|
||||
? ServiceInventoryView.classic
|
||||
: JSON.parse(serviceInventoryLocalStorageValue),
|
||||
});
|
||||
analytics.registerContextProvider({
|
||||
name: 'serviceInventoryViewType',
|
||||
context$: serviceInventoryViewType$,
|
||||
schema: {
|
||||
serviceInventoryViewType: {
|
||||
type: 'keyword',
|
||||
_meta: { description: 'The APM service inventory view type' },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
|
@ -11,8 +11,6 @@ import { EntityLink } from '.';
|
|||
import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context';
|
||||
import type { ServiceEntitySummary } from '../../../../context/apm_service/use_service_entity_summary_fetcher';
|
||||
import * as useServiceEntitySummary from '../../../../context/apm_service/use_service_entity_summary_fetcher';
|
||||
import type { EntityManagerEnablementContextValue } from '../../../../context/entity_manager_context/entity_manager_context';
|
||||
import * as useEntityManagerEnablementContext from '../../../../context/entity_manager_context/use_entity_manager_enablement_context';
|
||||
import * as useFetcher from '../../../../hooks/use_fetcher';
|
||||
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
|
||||
import { fromQuery } from '../../../shared/links/url_helpers';
|
||||
|
@ -20,6 +18,7 @@ import { APIReturnType } from '../../../../services/rest/create_call_apm_api';
|
|||
import { Redirect } from 'react-router-dom';
|
||||
import { ApmPluginContextValue } from '../../../../context/apm_plugin/apm_plugin_context';
|
||||
import { ApmThemeProvider } from '../../../routing/app_root';
|
||||
import * as useEntityCentricExperienceSetting from '../../../../hooks/use_entity_centric_experience_setting';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'), // Keep other functionality intact
|
||||
|
@ -29,12 +28,12 @@ jest.mock('react-router-dom', () => ({
|
|||
export type HasApmData = APIReturnType<'GET /internal/apm/has_data'>;
|
||||
|
||||
const renderEntityLink = ({
|
||||
entityManagerMockReturnValue,
|
||||
isEntityCentricExperienceEnabled = true,
|
||||
serviceEntitySummaryMockReturnValue,
|
||||
hasApmDataFetcherMockReturnValue,
|
||||
query = {},
|
||||
}: {
|
||||
entityManagerMockReturnValue: Partial<EntityManagerEnablementContextValue>;
|
||||
isEntityCentricExperienceEnabled?: boolean;
|
||||
serviceEntitySummaryMockReturnValue: ReturnType<
|
||||
typeof useServiceEntitySummary.useServiceEntitySummaryFetcher
|
||||
>;
|
||||
|
@ -45,10 +44,8 @@ const renderEntityLink = ({
|
|||
};
|
||||
}) => {
|
||||
jest
|
||||
.spyOn(useEntityManagerEnablementContext, 'useEntityManagerEnablementContext')
|
||||
.mockReturnValue(
|
||||
entityManagerMockReturnValue as unknown as EntityManagerEnablementContextValue
|
||||
);
|
||||
.spyOn(useEntityCentricExperienceSetting, 'useEntityCentricExperienceSetting')
|
||||
.mockReturnValue({ isEntityCentricExperienceEnabled });
|
||||
|
||||
jest
|
||||
.spyOn(useServiceEntitySummary, 'useServiceEntitySummaryFetcher')
|
||||
|
@ -101,30 +98,9 @@ describe('Entity link', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders a loading spinner while fetching data', () => {
|
||||
renderEntityLink({
|
||||
entityManagerMockReturnValue: {
|
||||
isEntityCentricExperienceViewEnabled: undefined,
|
||||
isEnablementPending: true,
|
||||
},
|
||||
serviceEntitySummaryMockReturnValue: {
|
||||
serviceEntitySummary: undefined,
|
||||
serviceEntitySummaryStatus: FETCH_STATUS.LOADING,
|
||||
},
|
||||
hasApmDataFetcherMockReturnValue: {
|
||||
data: undefined,
|
||||
status: FETCH_STATUS.LOADING,
|
||||
},
|
||||
});
|
||||
expect(screen.queryByTestId('apmEntityLinkLoadingSpinner')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders EEM callout when EEM is enabled but service is not found on EEM indices', () => {
|
||||
renderEntityLink({
|
||||
entityManagerMockReturnValue: {
|
||||
isEntityCentricExperienceViewEnabled: true,
|
||||
isEnablementPending: false,
|
||||
},
|
||||
isEntityCentricExperienceEnabled: true,
|
||||
serviceEntitySummaryMockReturnValue: {
|
||||
serviceEntitySummary: undefined,
|
||||
serviceEntitySummaryStatus: FETCH_STATUS.SUCCESS,
|
||||
|
@ -141,10 +117,7 @@ describe('Entity link', () => {
|
|||
|
||||
it('renders Service Overview page when EEM is disabled', () => {
|
||||
renderEntityLink({
|
||||
entityManagerMockReturnValue: {
|
||||
isEntityCentricExperienceViewEnabled: false,
|
||||
isEnablementPending: false,
|
||||
},
|
||||
isEntityCentricExperienceEnabled: false,
|
||||
serviceEntitySummaryMockReturnValue: {
|
||||
serviceEntitySummary: undefined,
|
||||
serviceEntitySummaryStatus: FETCH_STATUS.SUCCESS,
|
||||
|
@ -171,10 +144,7 @@ describe('Entity link', () => {
|
|||
|
||||
it('renders Service Overview page when EEM is enabled but Service is not found on EEM but it has raw APM data', () => {
|
||||
renderEntityLink({
|
||||
entityManagerMockReturnValue: {
|
||||
isEntityCentricExperienceViewEnabled: true,
|
||||
isEnablementPending: false,
|
||||
},
|
||||
isEntityCentricExperienceEnabled: true,
|
||||
serviceEntitySummaryMockReturnValue: {
|
||||
serviceEntitySummary: undefined,
|
||||
serviceEntitySummaryStatus: FETCH_STATUS.SUCCESS,
|
||||
|
@ -201,10 +171,7 @@ describe('Entity link', () => {
|
|||
|
||||
it('renders Service Overview page when EEM is enabled and Service is found on EEM', () => {
|
||||
renderEntityLink({
|
||||
entityManagerMockReturnValue: {
|
||||
isEntityCentricExperienceViewEnabled: true,
|
||||
isEnablementPending: false,
|
||||
},
|
||||
isEntityCentricExperienceEnabled: true,
|
||||
serviceEntitySummaryMockReturnValue: {
|
||||
serviceEntitySummary: { dataStreamTypes: ['metrics'] } as unknown as ServiceEntitySummary,
|
||||
serviceEntitySummaryStatus: FETCH_STATUS.SUCCESS,
|
||||
|
@ -231,10 +198,7 @@ describe('Entity link', () => {
|
|||
|
||||
it('renders Service Overview page setting time range from data plugin', () => {
|
||||
renderEntityLink({
|
||||
entityManagerMockReturnValue: {
|
||||
isEntityCentricExperienceViewEnabled: true,
|
||||
isEnablementPending: false,
|
||||
},
|
||||
isEntityCentricExperienceEnabled: true,
|
||||
serviceEntitySummaryMockReturnValue: {
|
||||
serviceEntitySummary: { dataStreamTypes: ['metrics'] } as unknown as ServiceEntitySummary,
|
||||
serviceEntitySummaryStatus: FETCH_STATUS.SUCCESS,
|
||||
|
|
|
@ -14,9 +14,9 @@ import React from 'react';
|
|||
import { Redirect } from 'react-router-dom';
|
||||
import { ENVIRONMENT_ALL_VALUE } from '../../../../../common/environment_filter_values';
|
||||
import { useServiceEntitySummaryFetcher } from '../../../../context/apm_service/use_service_entity_summary_fetcher';
|
||||
import { useEntityManagerEnablementContext } from '../../../../context/entity_manager_context/use_entity_manager_enablement_context';
|
||||
import { useApmParams } from '../../../../hooks/use_apm_params';
|
||||
import { useApmRouter } from '../../../../hooks/use_apm_router';
|
||||
import { useEntityCentricExperienceSetting } from '../../../../hooks/use_entity_centric_experience_setting';
|
||||
import { FETCH_STATUS, isPending, useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { useTheme } from '../../../../hooks/use_theme';
|
||||
import { ApmPluginStartDeps } from '../../../../plugin';
|
||||
|
@ -36,8 +36,7 @@ export function EntityLink() {
|
|||
path: { serviceName },
|
||||
query: { rangeFrom = timeRange.from, rangeTo = timeRange.to },
|
||||
} = useApmParams('/link-to/entity/{serviceName}');
|
||||
const { isEntityCentricExperienceViewEnabled, isEnablementPending } =
|
||||
useEntityManagerEnablementContext();
|
||||
const { isEntityCentricExperienceEnabled } = useEntityCentricExperienceSetting();
|
||||
|
||||
const { serviceEntitySummary, serviceEntitySummaryStatus } = useServiceEntitySummaryFetcher({
|
||||
serviceName,
|
||||
|
@ -48,17 +47,13 @@ export function EntityLink() {
|
|||
return callApmApi('GET /internal/apm/has_data');
|
||||
}, []);
|
||||
|
||||
if (
|
||||
isEnablementPending ||
|
||||
serviceEntitySummaryStatus === FETCH_STATUS.LOADING ||
|
||||
isPending(hasApmDataStatus)
|
||||
) {
|
||||
if (serviceEntitySummaryStatus === FETCH_STATUS.LOADING || isPending(hasApmDataStatus)) {
|
||||
return <EuiLoadingSpinner data-test-subj="apmEntityLinkLoadingSpinner" />;
|
||||
}
|
||||
|
||||
if (
|
||||
// When EEM is enabled and the service is not found on the EEM indices and there's no APM data, display a callout guiding on the limitations of EEM
|
||||
isEntityCentricExperienceViewEnabled === true &&
|
||||
isEntityCentricExperienceEnabled === true &&
|
||||
(serviceEntitySummary?.dataStreamTypes === undefined ||
|
||||
serviceEntitySummary.dataStreamTypes.length === 0) &&
|
||||
hasApmData?.hasData !== true
|
||||
|
|
|
@ -1,117 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiImage,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiButtonEmpty,
|
||||
useEuiTheme,
|
||||
EuiButtonIcon,
|
||||
} from '@elastic/eui';
|
||||
import { apmLight } from '@kbn/shared-svg';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useKibana } from '../../../../context/kibana_context/use_kibana';
|
||||
import { ApmPluginStartDeps, ApmServices } from '../../../../plugin';
|
||||
import { AddApmData } from '../../../shared/add_data_buttons/buttons';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function AddAPMCallOut({ onClose }: Props) {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { services } = useKibana<ApmPluginStartDeps & ApmServices>();
|
||||
|
||||
function handleClick() {
|
||||
services.telemetry.reportEntityInventoryAddData({
|
||||
view: 'add_apm_cta',
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPanel color="subdued" hasShadow={false}>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="flexStart">
|
||||
<EuiFlexItem grow={0}>
|
||||
<EuiImage
|
||||
css={{
|
||||
background: euiTheme.colors.emptyShade,
|
||||
}}
|
||||
width="160"
|
||||
height="100"
|
||||
size="m"
|
||||
src={apmLight}
|
||||
alt="apm-logo"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={4}>
|
||||
<EuiTitle size="xs">
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="xpack.apm.addAPMCallOut.title"
|
||||
defaultMessage="Detect and resolve issues faster with deep visibility into your application"
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.apm.addAPMCallOut.description"
|
||||
defaultMessage="Understanding your application performance, relationships and dependencies by
|
||||
instrumenting with APM."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
data-test-subj="apmAddAPMCallOutButton"
|
||||
iconType="cross"
|
||||
onClick={onClose}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<div>
|
||||
<AddApmData data-test-subj="apmAddDataLogOnlyCallout" onClick={handleClick} />
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="apmAddApmCallOutLearnMoreButton"
|
||||
iconType="popout"
|
||||
iconSide="right"
|
||||
target="_blank"
|
||||
href="https://www.elastic.co/observability/application-performance-monitoring"
|
||||
>
|
||||
{i18n.translate('xpack.apm.addAPMCallOut.linkToElasticcoButtonEmptyLabel', {
|
||||
defaultMessage: 'Learn more',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
|
@ -24,14 +24,14 @@ import { Sort } from './sort';
|
|||
import { RefreshServiceGroupsSubscriber } from '../refresh_service_groups_subscriber';
|
||||
import { ServiceGroupSaveButton } from '../service_group_save';
|
||||
import { BetaBadge } from '../../../shared/beta_badge';
|
||||
import { useEntityManagerEnablementContext } from '../../../../context/entity_manager_context/use_entity_manager_enablement_context';
|
||||
import { useEntityCentricExperienceSetting } from '../../../../hooks/use_entity_centric_experience_setting';
|
||||
|
||||
export type ServiceGroupsSortType = 'recently_added' | 'alphabetical';
|
||||
|
||||
const GET_STARTED_URL = 'https://www.elastic.co/guide/en/apm/get-started/current/index.html';
|
||||
|
||||
export function ServiceGroupsList() {
|
||||
const { isEntityCentricExperienceViewEnabled } = useEntityManagerEnablementContext();
|
||||
const { isEntityCentricExperienceEnabled } = useEntityCentricExperienceSetting();
|
||||
|
||||
const [filter, setFilter] = useState('');
|
||||
|
||||
|
@ -137,7 +137,7 @@ export function ServiceGroupsList() {
|
|||
{i18n.translate('xpack.apm.serviceGroups.listDescription', {
|
||||
defaultMessage: 'Displayed service counts reflect the last 24 hours.',
|
||||
})}
|
||||
{isEntityCentricExperienceViewEnabled && (
|
||||
{isEntityCentricExperienceEnabled && (
|
||||
<FormattedMessage
|
||||
id="xpack.apm.serviceGroups.onlyApm"
|
||||
defaultMessage="Only showing services {link}"
|
||||
|
|
|
@ -1,322 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { usePerformanceContext } from '@kbn/ebt-tools';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { ApmDocumentType } from '../../../../../common/document_type';
|
||||
import {
|
||||
ServiceInventoryFieldName,
|
||||
ServiceListItem,
|
||||
} from '../../../../../common/service_inventory';
|
||||
import { useAnomalyDetectionJobsContext } from '../../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useApmParams } from '../../../../hooks/use_apm_params';
|
||||
import { useStateDebounced } from '../../../../hooks/use_debounce';
|
||||
import { FETCH_STATUS, isFailure, isPending } from '../../../../hooks/use_fetcher';
|
||||
import { useLocalStorage } from '../../../../hooks/use_local_storage';
|
||||
import { usePreferredDataSourceAndBucketSize } from '../../../../hooks/use_preferred_data_source_and_bucket_size';
|
||||
import { useProgressiveFetcher } from '../../../../hooks/use_progressive_fetcher';
|
||||
import { useTimeRange } from '../../../../hooks/use_time_range';
|
||||
import { APIReturnType } from '../../../../services/rest/create_call_apm_api';
|
||||
import { SortFunction } from '../../../shared/managed_table';
|
||||
import { MLCallout, shouldDisplayMlCallout } from '../../../shared/ml_callout';
|
||||
import { SearchBar } from '../../../shared/search_bar/search_bar';
|
||||
import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options';
|
||||
import { ApmServicesTable } from './service_list/apm_services_table';
|
||||
import { orderServiceItems } from './service_list/order_service_items';
|
||||
|
||||
type MainStatisticsApiResponse = APIReturnType<'GET /internal/apm/services'>;
|
||||
|
||||
const INITIAL_PAGE_SIZE = 25;
|
||||
const INITIAL_DATA: MainStatisticsApiResponse & { requestId: string } = {
|
||||
requestId: '',
|
||||
items: [],
|
||||
serviceOverflowCount: 0,
|
||||
maxCountExceeded: false,
|
||||
};
|
||||
|
||||
function useServicesMainStatisticsFetcher(searchQuery: string | undefined) {
|
||||
const {
|
||||
query: {
|
||||
rangeFrom,
|
||||
rangeTo,
|
||||
environment,
|
||||
kuery,
|
||||
serviceGroup,
|
||||
page = 0,
|
||||
pageSize = INITIAL_PAGE_SIZE,
|
||||
sortDirection,
|
||||
sortField,
|
||||
},
|
||||
} = useApmParams('/services');
|
||||
|
||||
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
|
||||
|
||||
const preferred = usePreferredDataSourceAndBucketSize({
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
type: ApmDocumentType.ServiceTransactionMetric,
|
||||
numBuckets: 20,
|
||||
});
|
||||
|
||||
const shouldUseDurationSummary = !!preferred?.source?.hasDurationSummaryField;
|
||||
|
||||
const { data = INITIAL_DATA, status } = useProgressiveFetcher(
|
||||
(callApmApi) => {
|
||||
if (preferred) {
|
||||
return callApmApi('GET /internal/apm/services', {
|
||||
params: {
|
||||
query: {
|
||||
environment,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
serviceGroup,
|
||||
useDurationSummary: shouldUseDurationSummary,
|
||||
documentType: preferred.source.documentType,
|
||||
rollupInterval: preferred.source.rollupInterval,
|
||||
searchQuery,
|
||||
},
|
||||
},
|
||||
}).then((mainStatisticsData) => {
|
||||
return {
|
||||
requestId: uuidv4(),
|
||||
...mainStatisticsData,
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
environment,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
serviceGroup,
|
||||
preferred,
|
||||
searchQuery,
|
||||
// not used, but needed to update the requestId to call the details statistics API when table options are updated
|
||||
page,
|
||||
pageSize,
|
||||
sortField,
|
||||
sortDirection,
|
||||
]
|
||||
);
|
||||
|
||||
return { mainStatisticsData: data, mainStatisticsStatus: status };
|
||||
}
|
||||
|
||||
function useServicesDetailedStatisticsFetcher({
|
||||
mainStatisticsFetch,
|
||||
renderedItems,
|
||||
}: {
|
||||
mainStatisticsFetch: ReturnType<typeof useServicesMainStatisticsFetcher>;
|
||||
renderedItems: ServiceListItem[];
|
||||
}) {
|
||||
const {
|
||||
query: { rangeFrom, rangeTo, environment, kuery, offset, comparisonEnabled },
|
||||
} = useApmParams('/services');
|
||||
|
||||
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
|
||||
|
||||
const dataSourceOptions = usePreferredDataSourceAndBucketSize({
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
type: ApmDocumentType.ServiceTransactionMetric,
|
||||
numBuckets: 20,
|
||||
});
|
||||
|
||||
const { mainStatisticsData, mainStatisticsStatus } = mainStatisticsFetch;
|
||||
|
||||
const comparisonFetch = useProgressiveFetcher(
|
||||
(callApmApi) => {
|
||||
const serviceNames = renderedItems.map(({ serviceName }) => serviceName);
|
||||
|
||||
if (
|
||||
start &&
|
||||
end &&
|
||||
serviceNames.length > 0 &&
|
||||
mainStatisticsStatus === FETCH_STATUS.SUCCESS &&
|
||||
dataSourceOptions
|
||||
) {
|
||||
return callApmApi('POST /internal/apm/services/detailed_statistics', {
|
||||
params: {
|
||||
query: {
|
||||
environment,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
offset: comparisonEnabled && isTimeComparison(offset) ? offset : undefined,
|
||||
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, renderedItems, offset, comparisonEnabled],
|
||||
{ preservePreviousData: false }
|
||||
);
|
||||
|
||||
return { comparisonFetch };
|
||||
}
|
||||
|
||||
export function ApmServiceInventory() {
|
||||
const [debouncedSearchQuery, setDebouncedSearchQuery] = useStateDebounced('');
|
||||
const { onPageReady } = usePerformanceContext();
|
||||
const [renderedItems, setRenderedItems] = useState<ServiceListItem[]>([]);
|
||||
const mainStatisticsFetch = useServicesMainStatisticsFetcher(debouncedSearchQuery);
|
||||
const { mainStatisticsData, mainStatisticsStatus } = mainStatisticsFetch;
|
||||
|
||||
const displayHealthStatus = mainStatisticsData.items.some((item) => 'healthStatus' in item);
|
||||
|
||||
const serviceOverflowCount = mainStatisticsData?.serviceOverflowCount ?? 0;
|
||||
|
||||
const displayAlerts = mainStatisticsData.items.some(
|
||||
(item) => ServiceInventoryFieldName.AlertsCount in item
|
||||
);
|
||||
|
||||
const tiebreakerField = ServiceInventoryFieldName.Throughput;
|
||||
|
||||
const initialSortField = displayHealthStatus
|
||||
? ServiceInventoryFieldName.HealthStatus
|
||||
: tiebreakerField;
|
||||
|
||||
const initialSortDirection = 'desc';
|
||||
|
||||
const { comparisonFetch } = useServicesDetailedStatisticsFetcher({
|
||||
mainStatisticsFetch,
|
||||
renderedItems,
|
||||
});
|
||||
|
||||
const { anomalyDetectionSetupState } = useAnomalyDetectionJobsContext();
|
||||
|
||||
const [userHasDismissedCallout, setUserHasDismissedCallout] = useLocalStorage(
|
||||
`apm.userHasDismissedServiceInventoryMlCallout.${anomalyDetectionSetupState}`,
|
||||
false
|
||||
);
|
||||
|
||||
const displayMlCallout =
|
||||
!userHasDismissedCallout && shouldDisplayMlCallout(anomalyDetectionSetupState);
|
||||
|
||||
const noItemsMessage = useMemo(() => {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
<div>
|
||||
{i18n.translate('xpack.apm.servicesTable.notFoundLabel', {
|
||||
defaultMessage: 'No services found',
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
titleSize="s"
|
||||
/>
|
||||
);
|
||||
}, []);
|
||||
|
||||
const mlCallout = (
|
||||
<EuiFlexItem>
|
||||
<MLCallout
|
||||
isOnSettingsPage={false}
|
||||
anomalyDetectionSetupState={anomalyDetectionSetupState}
|
||||
onDismiss={() => setUserHasDismissedCallout(true)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
||||
const sortFn: SortFunction<ServiceListItem> = useCallback(
|
||||
(itemsToSort, sortField, sortDirection) => {
|
||||
return orderServiceItems({
|
||||
items: itemsToSort,
|
||||
primarySortField: sortField,
|
||||
sortDirection,
|
||||
tiebreakerField,
|
||||
});
|
||||
},
|
||||
[tiebreakerField]
|
||||
);
|
||||
|
||||
// TODO verify this with AI team
|
||||
const setScreenContext = useApmPluginContext().observabilityAIAssistant?.service.setScreenContext;
|
||||
|
||||
useEffect(() => {
|
||||
if (!setScreenContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFailure(mainStatisticsStatus)) {
|
||||
return setScreenContext({
|
||||
screenDescription: 'The services have failed to load',
|
||||
});
|
||||
}
|
||||
|
||||
if (isPending(mainStatisticsStatus)) {
|
||||
return setScreenContext({
|
||||
screenDescription: 'The services are still loading',
|
||||
});
|
||||
}
|
||||
|
||||
return setScreenContext({
|
||||
data: [
|
||||
{
|
||||
name: 'services',
|
||||
description: 'The list of services that the user is looking at',
|
||||
value: mainStatisticsData.items,
|
||||
},
|
||||
],
|
||||
});
|
||||
}, [mainStatisticsStatus, mainStatisticsData.items, setScreenContext]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
mainStatisticsStatus === FETCH_STATUS.SUCCESS &&
|
||||
comparisonFetch.status === FETCH_STATUS.SUCCESS
|
||||
) {
|
||||
onPageReady();
|
||||
}
|
||||
}, [mainStatisticsStatus, comparisonFetch.status, onPageReady]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchBar showTimeComparison />
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
{displayMlCallout && mlCallout}
|
||||
<EuiFlexItem>
|
||||
<ApmServicesTable
|
||||
status={mainStatisticsStatus}
|
||||
items={mainStatisticsData.items}
|
||||
comparisonDataLoading={comparisonFetch.status === FETCH_STATUS.LOADING}
|
||||
displayHealthStatus={displayHealthStatus}
|
||||
displayAlerts={displayAlerts}
|
||||
initialSortField={initialSortField}
|
||||
initialSortDirection={initialSortDirection}
|
||||
sortFn={sortFn}
|
||||
comparisonData={comparisonFetch?.data}
|
||||
noItemsMessage={noItemsMessage}
|
||||
initialPageSize={INITIAL_PAGE_SIZE}
|
||||
serviceOverflowCount={serviceOverflowCount}
|
||||
onChangeSearchQuery={setDebouncedSearchQuery}
|
||||
maxCountExceeded={mainStatisticsData?.maxCountExceeded ?? false}
|
||||
onChangeRenderedItems={setRenderedItems}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -4,9 +4,316 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { ApmServiceInventory } from './apm_signal_inventory';
|
||||
|
||||
import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { usePerformanceContext } from '@kbn/ebt-tools';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { ApmDocumentType } from '../../../../common/document_type';
|
||||
import { ServiceInventoryFieldName, ServiceListItem } from '../../../../common/service_inventory';
|
||||
import { useAnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context';
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useApmParams } from '../../../hooks/use_apm_params';
|
||||
import { useStateDebounced } from '../../../hooks/use_debounce';
|
||||
import { FETCH_STATUS, isFailure, isPending } from '../../../hooks/use_fetcher';
|
||||
import { useLocalStorage } from '../../../hooks/use_local_storage';
|
||||
import { usePreferredDataSourceAndBucketSize } from '../../../hooks/use_preferred_data_source_and_bucket_size';
|
||||
import { useProgressiveFetcher } from '../../../hooks/use_progressive_fetcher';
|
||||
import { useTimeRange } from '../../../hooks/use_time_range';
|
||||
import { APIReturnType } from '../../../services/rest/create_call_apm_api';
|
||||
import { SortFunction } from '../../shared/managed_table';
|
||||
import { MLCallout, shouldDisplayMlCallout } from '../../shared/ml_callout';
|
||||
import { SearchBar } from '../../shared/search_bar/search_bar';
|
||||
import { isTimeComparison } from '../../shared/time_comparison/get_comparison_options';
|
||||
import { ApmServicesTable } from './service_list/apm_services_table';
|
||||
import { orderServiceItems } from './service_list/order_service_items';
|
||||
|
||||
type MainStatisticsApiResponse = APIReturnType<'GET /internal/apm/services'>;
|
||||
|
||||
const INITIAL_PAGE_SIZE = 25;
|
||||
const INITIAL_DATA: MainStatisticsApiResponse & { requestId: string } = {
|
||||
requestId: '',
|
||||
items: [],
|
||||
serviceOverflowCount: 0,
|
||||
maxCountExceeded: false,
|
||||
};
|
||||
|
||||
function useServicesMainStatisticsFetcher(searchQuery: string | undefined) {
|
||||
const {
|
||||
query: {
|
||||
rangeFrom,
|
||||
rangeTo,
|
||||
environment,
|
||||
kuery,
|
||||
serviceGroup,
|
||||
page = 0,
|
||||
pageSize = INITIAL_PAGE_SIZE,
|
||||
sortDirection,
|
||||
sortField,
|
||||
},
|
||||
} = useApmParams('/services');
|
||||
|
||||
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
|
||||
|
||||
const preferred = usePreferredDataSourceAndBucketSize({
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
type: ApmDocumentType.ServiceTransactionMetric,
|
||||
numBuckets: 20,
|
||||
});
|
||||
|
||||
const shouldUseDurationSummary = !!preferred?.source?.hasDurationSummaryField;
|
||||
|
||||
const { data = INITIAL_DATA, status } = useProgressiveFetcher(
|
||||
(callApmApi) => {
|
||||
if (preferred) {
|
||||
return callApmApi('GET /internal/apm/services', {
|
||||
params: {
|
||||
query: {
|
||||
environment,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
serviceGroup,
|
||||
useDurationSummary: shouldUseDurationSummary,
|
||||
documentType: preferred.source.documentType,
|
||||
rollupInterval: preferred.source.rollupInterval,
|
||||
searchQuery,
|
||||
},
|
||||
},
|
||||
}).then((mainStatisticsData) => {
|
||||
return {
|
||||
requestId: uuidv4(),
|
||||
...mainStatisticsData,
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
environment,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
serviceGroup,
|
||||
preferred,
|
||||
searchQuery,
|
||||
// not used, but needed to update the requestId to call the details statistics API when table options are updated
|
||||
page,
|
||||
pageSize,
|
||||
sortField,
|
||||
sortDirection,
|
||||
]
|
||||
);
|
||||
|
||||
return { mainStatisticsData: data, mainStatisticsStatus: status };
|
||||
}
|
||||
|
||||
function useServicesDetailedStatisticsFetcher({
|
||||
mainStatisticsFetch,
|
||||
renderedItems,
|
||||
}: {
|
||||
mainStatisticsFetch: ReturnType<typeof useServicesMainStatisticsFetcher>;
|
||||
renderedItems: ServiceListItem[];
|
||||
}) {
|
||||
const {
|
||||
query: { rangeFrom, rangeTo, environment, kuery, offset, comparisonEnabled },
|
||||
} = useApmParams('/services');
|
||||
|
||||
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
|
||||
|
||||
const dataSourceOptions = usePreferredDataSourceAndBucketSize({
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
type: ApmDocumentType.ServiceTransactionMetric,
|
||||
numBuckets: 20,
|
||||
});
|
||||
|
||||
const { mainStatisticsData, mainStatisticsStatus } = mainStatisticsFetch;
|
||||
|
||||
const comparisonFetch = useProgressiveFetcher(
|
||||
(callApmApi) => {
|
||||
const serviceNames = renderedItems.map(({ serviceName }) => serviceName);
|
||||
|
||||
if (
|
||||
start &&
|
||||
end &&
|
||||
serviceNames.length > 0 &&
|
||||
mainStatisticsStatus === FETCH_STATUS.SUCCESS &&
|
||||
dataSourceOptions
|
||||
) {
|
||||
return callApmApi('POST /internal/apm/services/detailed_statistics', {
|
||||
params: {
|
||||
query: {
|
||||
environment,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
offset: comparisonEnabled && isTimeComparison(offset) ? offset : undefined,
|
||||
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, renderedItems, offset, comparisonEnabled],
|
||||
{ preservePreviousData: false }
|
||||
);
|
||||
|
||||
return { comparisonFetch };
|
||||
}
|
||||
|
||||
export function ServiceInventory() {
|
||||
return <ApmServiceInventory />;
|
||||
const [debouncedSearchQuery, setDebouncedSearchQuery] = useStateDebounced('');
|
||||
const { onPageReady } = usePerformanceContext();
|
||||
const [renderedItems, setRenderedItems] = useState<ServiceListItem[]>([]);
|
||||
const mainStatisticsFetch = useServicesMainStatisticsFetcher(debouncedSearchQuery);
|
||||
const { mainStatisticsData, mainStatisticsStatus } = mainStatisticsFetch;
|
||||
|
||||
const displayHealthStatus = mainStatisticsData.items.some((item) => 'healthStatus' in item);
|
||||
|
||||
const serviceOverflowCount = mainStatisticsData?.serviceOverflowCount ?? 0;
|
||||
|
||||
const displayAlerts = mainStatisticsData.items.some(
|
||||
(item) => ServiceInventoryFieldName.AlertsCount in item
|
||||
);
|
||||
|
||||
const tiebreakerField = ServiceInventoryFieldName.Throughput;
|
||||
|
||||
const initialSortField = displayHealthStatus
|
||||
? ServiceInventoryFieldName.HealthStatus
|
||||
: tiebreakerField;
|
||||
|
||||
const initialSortDirection = 'desc';
|
||||
|
||||
const { comparisonFetch } = useServicesDetailedStatisticsFetcher({
|
||||
mainStatisticsFetch,
|
||||
renderedItems,
|
||||
});
|
||||
|
||||
const { anomalyDetectionSetupState } = useAnomalyDetectionJobsContext();
|
||||
|
||||
const [userHasDismissedCallout, setUserHasDismissedCallout] = useLocalStorage(
|
||||
`apm.userHasDismissedServiceInventoryMlCallout.${anomalyDetectionSetupState}`,
|
||||
false
|
||||
);
|
||||
|
||||
const displayMlCallout =
|
||||
!userHasDismissedCallout && shouldDisplayMlCallout(anomalyDetectionSetupState);
|
||||
|
||||
const noItemsMessage = useMemo(() => {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
<div>
|
||||
{i18n.translate('xpack.apm.servicesTable.notFoundLabel', {
|
||||
defaultMessage: 'No services found',
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
titleSize="s"
|
||||
/>
|
||||
);
|
||||
}, []);
|
||||
|
||||
const mlCallout = (
|
||||
<EuiFlexItem>
|
||||
<MLCallout
|
||||
isOnSettingsPage={false}
|
||||
anomalyDetectionSetupState={anomalyDetectionSetupState}
|
||||
onDismiss={() => setUserHasDismissedCallout(true)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
||||
const sortFn: SortFunction<ServiceListItem> = useCallback(
|
||||
(itemsToSort, sortField, sortDirection) => {
|
||||
return orderServiceItems({
|
||||
items: itemsToSort,
|
||||
primarySortField: sortField,
|
||||
sortDirection,
|
||||
tiebreakerField,
|
||||
});
|
||||
},
|
||||
[tiebreakerField]
|
||||
);
|
||||
|
||||
// TODO verify this with AI team
|
||||
const setScreenContext = useApmPluginContext().observabilityAIAssistant?.service.setScreenContext;
|
||||
|
||||
useEffect(() => {
|
||||
if (!setScreenContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFailure(mainStatisticsStatus)) {
|
||||
return setScreenContext({
|
||||
screenDescription: 'The services have failed to load',
|
||||
});
|
||||
}
|
||||
|
||||
if (isPending(mainStatisticsStatus)) {
|
||||
return setScreenContext({
|
||||
screenDescription: 'The services are still loading',
|
||||
});
|
||||
}
|
||||
|
||||
return setScreenContext({
|
||||
data: [
|
||||
{
|
||||
name: 'services',
|
||||
description: 'The list of services that the user is looking at',
|
||||
value: mainStatisticsData.items,
|
||||
},
|
||||
],
|
||||
});
|
||||
}, [mainStatisticsStatus, mainStatisticsData.items, setScreenContext]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
mainStatisticsStatus === FETCH_STATUS.SUCCESS &&
|
||||
comparisonFetch.status === FETCH_STATUS.SUCCESS
|
||||
) {
|
||||
onPageReady();
|
||||
}
|
||||
}, [mainStatisticsStatus, comparisonFetch.status, onPageReady]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchBar showTimeComparison />
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
{displayMlCallout && mlCallout}
|
||||
<EuiFlexItem>
|
||||
<ApmServicesTable
|
||||
status={mainStatisticsStatus}
|
||||
items={mainStatisticsData.items}
|
||||
comparisonDataLoading={comparisonFetch.status === FETCH_STATUS.LOADING}
|
||||
displayHealthStatus={displayHealthStatus}
|
||||
displayAlerts={displayAlerts}
|
||||
initialSortField={initialSortField}
|
||||
initialSortDirection={initialSortDirection}
|
||||
sortFn={sortFn}
|
||||
comparisonData={comparisonFetch?.data}
|
||||
noItemsMessage={noItemsMessage}
|
||||
initialPageSize={INITIAL_PAGE_SIZE}
|
||||
serviceOverflowCount={serviceOverflowCount}
|
||||
onChangeSearchQuery={setDebouncedSearchQuery}
|
||||
maxCountExceeded={mainStatisticsData?.maxCountExceeded ?? false}
|
||||
onChangeRenderedItems={setRenderedItems}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,218 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useEffect } from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { APIReturnType } from '../../../../services/rest/create_call_apm_api';
|
||||
import { useApmParams } from '../../../../hooks/use_apm_params';
|
||||
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';
|
||||
import {
|
||||
getItemsFilteredBySearchQuery,
|
||||
TableSearchBar,
|
||||
} from '../../../shared/table_search_bar/table_search_bar';
|
||||
import {
|
||||
MultiSignalServicesTable,
|
||||
ServiceInventoryFieldName,
|
||||
} from './table/multi_signal_services_table';
|
||||
import { ServiceListItem } from '../../../../../common/service_inventory';
|
||||
import { NoEntitiesEmptyState } from './table/no_entities_empty_state';
|
||||
import { Welcome } from '../../../shared/entity_enablement/welcome_modal';
|
||||
import { useKibana } from '../../../../context/kibana_context/use_kibana';
|
||||
import { ApmPluginStartDeps, ApmServices } from '../../../../plugin';
|
||||
import { useEntityManagerEnablementContext } from '../../../../context/entity_manager_context/use_entity_manager_enablement_context';
|
||||
|
||||
type MainStatisticsApiResponse = APIReturnType<'GET /internal/apm/entities/services'>;
|
||||
|
||||
const INITIAL_PAGE_SIZE = 25;
|
||||
const INITIAL_SORT_DIRECTION = 'desc';
|
||||
|
||||
type MainStatisticsApiResponseWithRequestId = MainStatisticsApiResponse & { requestId: string };
|
||||
|
||||
const INITIAL_DATA: MainStatisticsApiResponseWithRequestId = {
|
||||
services: [],
|
||||
requestId: '',
|
||||
};
|
||||
|
||||
function useServicesEntitiesMainStatisticsFetcher() {
|
||||
const {
|
||||
query: {
|
||||
rangeFrom,
|
||||
rangeTo,
|
||||
environment,
|
||||
kuery,
|
||||
page = 0,
|
||||
pageSize = INITIAL_PAGE_SIZE,
|
||||
sortDirection,
|
||||
sortField,
|
||||
},
|
||||
} = useApmParams('/services');
|
||||
|
||||
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
|
||||
|
||||
const { data = INITIAL_DATA, status } = useFetcher(
|
||||
(callApmApi) => {
|
||||
return callApmApi('GET /internal/apm/entities/services', {
|
||||
params: {
|
||||
query: {
|
||||
environment,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
},
|
||||
},
|
||||
}).then((mainStatisticsData) => {
|
||||
return {
|
||||
requestId: uuidv4(),
|
||||
...mainStatisticsData,
|
||||
};
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[environment, kuery, start, end, page, pageSize, sortField, sortDirection]
|
||||
);
|
||||
|
||||
return { mainStatisticsData: data, mainStatisticsStatus: status };
|
||||
}
|
||||
|
||||
function useServicesEntitiesDetailedStatisticsFetcher({
|
||||
mainStatisticsData,
|
||||
mainStatisticsStatus,
|
||||
services,
|
||||
}: {
|
||||
mainStatisticsData: MainStatisticsApiResponseWithRequestId;
|
||||
mainStatisticsStatus: FETCH_STATUS;
|
||||
services: ServiceListItem[];
|
||||
}) {
|
||||
const {
|
||||
query: { rangeFrom, rangeTo, environment, kuery },
|
||||
} = useApmParams('/services');
|
||||
|
||||
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
|
||||
|
||||
const timeseriesDataFetch = useFetcher(
|
||||
(callApmApi) => {
|
||||
const serviceNames = services.map(({ serviceName }) => serviceName);
|
||||
|
||||
if (
|
||||
start &&
|
||||
end &&
|
||||
serviceNames.length > 0 &&
|
||||
mainStatisticsStatus === FETCH_STATUS.SUCCESS
|
||||
) {
|
||||
return callApmApi('POST /internal/apm/entities/services/detailed_statistics', {
|
||||
params: {
|
||||
query: {
|
||||
environment,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
},
|
||||
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],
|
||||
{ preservePreviousData: false }
|
||||
);
|
||||
|
||||
return { timeseriesDataFetch };
|
||||
}
|
||||
|
||||
export function MultiSignalInventory() {
|
||||
const [searchQuery, setSearchQuery] = React.useState('');
|
||||
const { services } = useKibana<ApmPluginStartDeps & ApmServices>();
|
||||
const { mainStatisticsData, mainStatisticsStatus } = useServicesEntitiesMainStatisticsFetcher();
|
||||
const { tourState, updateTourState } = useEntityManagerEnablementContext();
|
||||
|
||||
const initialSortField = ServiceInventoryFieldName.Throughput;
|
||||
|
||||
const filteredData = getItemsFilteredBySearchQuery({
|
||||
items: mainStatisticsData.services,
|
||||
searchQuery,
|
||||
fieldsToSearch: [ServiceInventoryFieldName.ServiceName],
|
||||
});
|
||||
|
||||
const { timeseriesDataFetch } = useServicesEntitiesDetailedStatisticsFetcher({
|
||||
mainStatisticsData,
|
||||
mainStatisticsStatus,
|
||||
services: mainStatisticsData.services,
|
||||
});
|
||||
|
||||
const { data, status } = useFetcher((callApmApi) => {
|
||||
return callApmApi('GET /internal/apm/has_entities');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.hasData) {
|
||||
services.telemetry.reportEntityInventoryPageState({ state: 'available' });
|
||||
}
|
||||
}, [services.telemetry, data?.hasData]);
|
||||
|
||||
function handleModalClose() {
|
||||
updateTourState({ isModalVisible: false, isTourActive: true });
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!data?.hasData && status === FETCH_STATUS.SUCCESS ? (
|
||||
<NoEntitiesEmptyState />
|
||||
) : (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem grow>
|
||||
<TableSearchBar
|
||||
placeholder={i18n.translate('xpack.apm.servicesTable.filterServicesPlaceholder', {
|
||||
defaultMessage: 'Search services by name',
|
||||
})}
|
||||
searchQuery={searchQuery}
|
||||
onChangeSearchQuery={setSearchQuery}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<SearchBar showQueryInput={false} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<MultiSignalServicesTable
|
||||
status={mainStatisticsStatus}
|
||||
data={filteredData}
|
||||
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', {
|
||||
defaultMessage: 'No services found',
|
||||
})}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
<Welcome
|
||||
isModalVisible={tourState.isModalVisible ?? false}
|
||||
onClose={handleModalClose}
|
||||
onConfirm={handleModalClose}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,39 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { ReactElement } from 'react';
|
||||
import { EuiFlexGroup } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { TooltipContent } from './tooltip_content';
|
||||
import { Popover } from './popover';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
toolTip?: ReactElement | string;
|
||||
formula?: string;
|
||||
}
|
||||
|
||||
export const ColumnHeader = React.memo(({ label, toolTip, formula }: Props) => (
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<div
|
||||
css={css`
|
||||
overflow-wrap: break-word !important;
|
||||
word-break: break-word;
|
||||
min-width: 0;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
|
||||
{toolTip && (
|
||||
<Popover>
|
||||
<TooltipContent formula={formula} description={toolTip} />
|
||||
</Popover>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
));
|
|
@ -1,268 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, RIGHT_ALIGNMENT } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TypeOf } from '@kbn/typed-react-router-config';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { css } from '@emotion/react';
|
||||
import {
|
||||
asDecimalOrInteger,
|
||||
asMillisecondDuration,
|
||||
asPercent,
|
||||
asTransactionRate,
|
||||
} from '../../../../../../common/utils/formatters';
|
||||
import { Breakpoints } from '../../../../../hooks/use_breakpoints';
|
||||
import { unit } from '../../../../../utils/style';
|
||||
import { ApmRoutes } from '../../../../routing/apm_route_config';
|
||||
import {
|
||||
getTimeSeriesColor,
|
||||
ChartType,
|
||||
} from '../../../../shared/charts/helper/get_timeseries_color';
|
||||
import {
|
||||
getMetricsFormula,
|
||||
ChartMetricType,
|
||||
} from '../../../../shared/charts/helper/get_metrics_formulas';
|
||||
import { EnvironmentBadge } from '../../../../shared/environment_badge';
|
||||
import { ServiceLink } from '../../../../shared/links/apm/service_link';
|
||||
import { ListMetric } from '../../../../shared/list_metric';
|
||||
import { ITableColumn } from '../../../../shared/managed_table';
|
||||
import { NotAvailableApmMetrics } from '../../../../shared/not_available_popover/not_available_apm_metrics';
|
||||
import { TruncateWithTooltip } from '../../../../shared/truncate_with_tooltip';
|
||||
import { ServiceInventoryFieldName } from './multi_signal_services_table';
|
||||
import { EntityDataStreamType } from '../../../../../../common/entities/types';
|
||||
import { isApmSignal } from '../../../../../utils/get_signal_type';
|
||||
import { ColumnHeader } from './column_header';
|
||||
import { APIReturnType } from '../../../../../services/rest/create_call_apm_api';
|
||||
|
||||
type ServicesDetailedStatisticsAPIResponse =
|
||||
APIReturnType<'POST /internal/apm/entities/services/detailed_statistics'>;
|
||||
|
||||
type EntityServiceListItem = APIReturnType<'GET /internal/apm/entities/services'>['services'][0];
|
||||
|
||||
export function getServiceColumns({
|
||||
query,
|
||||
breakpoints,
|
||||
timeseriesDataLoading,
|
||||
timeseriesData,
|
||||
}: {
|
||||
query: TypeOf<ApmRoutes, '/services'>['query'];
|
||||
breakpoints: Breakpoints;
|
||||
timeseriesDataLoading: boolean;
|
||||
timeseriesData?: ServicesDetailedStatisticsAPIResponse;
|
||||
}): Array<ITableColumn<EntityServiceListItem>> {
|
||||
const { isSmall, isLarge } = breakpoints;
|
||||
const showWhenSmallOrGreaterThanLarge = isSmall || !isLarge;
|
||||
return [
|
||||
{
|
||||
field: ServiceInventoryFieldName.ServiceName,
|
||||
name: i18n.translate('xpack.apm.multiSignal.servicesTable.nameColumnLabel', {
|
||||
defaultMessage: 'Name',
|
||||
}),
|
||||
sortable: true,
|
||||
render: (_, { serviceName, agentName, dataStreamTypes }) => (
|
||||
<TruncateWithTooltip
|
||||
data-test-subj="apmServiceListAppLink"
|
||||
text={serviceName}
|
||||
content={
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="flexStart">
|
||||
<EuiFlexItem grow={false}>
|
||||
<ServiceLink serviceName={serviceName} agentName={agentName} query={query} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: ServiceInventoryFieldName.Environments,
|
||||
name: i18n.translate('xpack.apm.multiSignal.servicesTable.environmentColumnLabel', {
|
||||
defaultMessage: 'Environment',
|
||||
}),
|
||||
sortable: true,
|
||||
width: `${unit * 9}px`,
|
||||
dataType: 'number',
|
||||
render: (_, { environments, dataStreamTypes }) => (
|
||||
<EnvironmentBadge
|
||||
environments={environments}
|
||||
isMetricsSignalType={dataStreamTypes.includes(EntityDataStreamType.METRICS)}
|
||||
/>
|
||||
),
|
||||
align: RIGHT_ALIGNMENT,
|
||||
},
|
||||
{
|
||||
field: ServiceInventoryFieldName.Latency,
|
||||
name: i18n.translate('xpack.apm.multiSignal.servicesTable.latencyAvgColumnLabel', {
|
||||
defaultMessage: 'Latency (avg.)',
|
||||
}),
|
||||
sortable: true,
|
||||
dataType: 'number',
|
||||
align: RIGHT_ALIGNMENT,
|
||||
render: (_, { metrics, serviceName, dataStreamTypes }) => {
|
||||
const { currentPeriodColor } = getTimeSeriesColor(ChartType.LATENCY_AVG);
|
||||
|
||||
return !isApmSignal(dataStreamTypes) ? (
|
||||
<NotAvailableApmMetrics />
|
||||
) : (
|
||||
<ListMetric
|
||||
isLoading={timeseriesDataLoading}
|
||||
series={timeseriesData?.currentPeriod?.[serviceName]?.latency}
|
||||
color={currentPeriodColor}
|
||||
valueLabel={asMillisecondDuration(metrics.latency)}
|
||||
hideSeries={!showWhenSmallOrGreaterThanLarge}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: ServiceInventoryFieldName.Throughput,
|
||||
name: i18n.translate('xpack.apm.multiSignal.servicesTable.throughputColumnLabel', {
|
||||
defaultMessage: 'Throughput',
|
||||
}),
|
||||
sortable: true,
|
||||
dataType: 'number',
|
||||
align: RIGHT_ALIGNMENT,
|
||||
render: (_, { metrics, serviceName, dataStreamTypes }) => {
|
||||
const { currentPeriodColor } = getTimeSeriesColor(ChartType.THROUGHPUT);
|
||||
|
||||
return !isApmSignal(dataStreamTypes) ? (
|
||||
<NotAvailableApmMetrics />
|
||||
) : (
|
||||
<ListMetric
|
||||
color={currentPeriodColor}
|
||||
valueLabel={asTransactionRate(metrics.throughput)}
|
||||
isLoading={timeseriesDataLoading}
|
||||
series={timeseriesData?.currentPeriod?.[serviceName]?.throughput}
|
||||
hideSeries={!showWhenSmallOrGreaterThanLarge}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: ServiceInventoryFieldName.FailedTransactionRate,
|
||||
name: i18n.translate('xpack.apm.multiSignal.servicesTable.transactionErrorRate', {
|
||||
defaultMessage: 'Failed transaction rate',
|
||||
}),
|
||||
sortable: true,
|
||||
dataType: 'number',
|
||||
align: RIGHT_ALIGNMENT,
|
||||
render: (_, { metrics, serviceName, dataStreamTypes }) => {
|
||||
const { currentPeriodColor } = getTimeSeriesColor(ChartType.FAILED_TRANSACTION_RATE);
|
||||
|
||||
return !isApmSignal(dataStreamTypes) ? (
|
||||
<NotAvailableApmMetrics />
|
||||
) : (
|
||||
<ListMetric
|
||||
color={currentPeriodColor}
|
||||
valueLabel={asPercent(metrics.failedTransactionRate, 1)}
|
||||
isLoading={timeseriesDataLoading}
|
||||
series={timeseriesData?.currentPeriod?.[serviceName]?.failedTransactionRate}
|
||||
hideSeries={!showWhenSmallOrGreaterThanLarge}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: ServiceInventoryFieldName.logRate,
|
||||
name: (
|
||||
<ColumnHeader
|
||||
label={i18n.translate('xpack.apm.multiSignal.servicesTable.logRate', {
|
||||
defaultMessage: 'Log rate (per min.)',
|
||||
})}
|
||||
formula={getMetricsFormula(ChartMetricType.LOG_RATE)}
|
||||
toolTip={
|
||||
<FormattedMessage
|
||||
defaultMessage="Rate of logs per minute observed for given {serviceName}."
|
||||
id="xpack.apm.multiSignal.servicesTable.logRate.tooltip.description"
|
||||
values={{
|
||||
serviceName: (
|
||||
<code
|
||||
css={css`
|
||||
word-break: break-word;
|
||||
`}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.apm.multiSignal.servicesTable.logRate.tooltip.serviceNameLabel',
|
||||
{
|
||||
defaultMessage: 'service.name',
|
||||
}
|
||||
)}
|
||||
</code>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
sortable: true,
|
||||
dataType: 'number',
|
||||
align: RIGHT_ALIGNMENT,
|
||||
render: (_, { metrics, serviceName, dataStreamTypes, hasLogMetrics }) => {
|
||||
const { currentPeriodColor } = getTimeSeriesColor(ChartType.LOG_RATE);
|
||||
return (
|
||||
<ListMetric
|
||||
isLoading={timeseriesDataLoading}
|
||||
color={currentPeriodColor}
|
||||
series={timeseriesData?.currentPeriod?.[serviceName]?.logRate}
|
||||
valueLabel={asDecimalOrInteger(metrics.logRate)}
|
||||
hideSeries={!showWhenSmallOrGreaterThanLarge}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: ServiceInventoryFieldName.LogErrorRate,
|
||||
name: (
|
||||
<ColumnHeader
|
||||
label={i18n.translate('xpack.apm.multiSignal.servicesTable.logErrorRate', {
|
||||
defaultMessage: 'Log error rate (per min.)',
|
||||
})}
|
||||
formula={getMetricsFormula(ChartMetricType.LOG_ERROR_RATE)}
|
||||
toolTip={
|
||||
<FormattedMessage
|
||||
defaultMessage="Rate of error logs per minute observed for given {serviceName}."
|
||||
id="xpack.apm.multiSignal.servicesTable.logErrorRate.tooltip.description"
|
||||
values={{
|
||||
serviceName: (
|
||||
<code
|
||||
css={css`
|
||||
word-break: break-word;
|
||||
`}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.apm.multiSignal.servicesTable.logErrorRate.tooltip.serviceNameLabel',
|
||||
{
|
||||
defaultMessage: 'service.name',
|
||||
}
|
||||
)}
|
||||
</code>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
sortable: true,
|
||||
dataType: 'number',
|
||||
align: RIGHT_ALIGNMENT,
|
||||
render: (_, { metrics, serviceName, dataStreamTypes, hasLogMetrics }) => {
|
||||
const { currentPeriodColor } = getTimeSeriesColor(ChartType.LOG_ERROR_RATE);
|
||||
|
||||
return (
|
||||
<ListMetric
|
||||
isLoading={timeseriesDataLoading}
|
||||
color={currentPeriodColor}
|
||||
series={timeseriesData?.currentPeriod?.[serviceName]?.logErrorRate}
|
||||
valueLabel={asDecimalOrInteger(metrics.logErrorRate)}
|
||||
hideSeries={!showWhenSmallOrGreaterThanLarge}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
|
@ -1,82 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { omit } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useApmParams } from '../../../../../hooks/use_apm_params';
|
||||
import { useBreakpoints } from '../../../../../hooks/use_breakpoints';
|
||||
import { FETCH_STATUS, isFailure, isPending } from '../../../../../hooks/use_fetcher';
|
||||
import { APIReturnType } from '../../../../../services/rest/create_call_apm_api';
|
||||
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',
|
||||
Environments = 'environments',
|
||||
Throughput = 'metrics.throughput',
|
||||
Latency = 'metrics.latency',
|
||||
FailedTransactionRate = 'metrics.failedTransactionRate',
|
||||
logRate = 'metrics.logRate',
|
||||
LogErrorRate = 'metrics.logErrorRate',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
status: FETCH_STATUS;
|
||||
initialSortField: ServiceInventoryFieldName;
|
||||
initialPageSize: number;
|
||||
initialSortDirection: 'asc' | 'desc';
|
||||
noItemsMessage: React.ReactNode;
|
||||
data: MainStatisticsApiResponse['services'];
|
||||
timeseriesDataLoading: boolean;
|
||||
timeseriesData?: ServicesDetailedStatisticsAPIResponse;
|
||||
}
|
||||
|
||||
export function MultiSignalServicesTable({
|
||||
status,
|
||||
data,
|
||||
initialSortField,
|
||||
initialPageSize,
|
||||
initialSortDirection,
|
||||
noItemsMessage,
|
||||
timeseriesDataLoading,
|
||||
timeseriesData,
|
||||
}: Props) {
|
||||
const breakpoints = useBreakpoints();
|
||||
const { query } = useApmParams('/services');
|
||||
|
||||
const serviceColumns = useMemo(() => {
|
||||
return getServiceColumns({
|
||||
// removes pagination and sort instructions from the query so it won't be passed down to next route
|
||||
query: omit(query, 'page', 'pageSize', 'sortDirection', 'sortField'),
|
||||
breakpoints,
|
||||
timeseriesDataLoading,
|
||||
timeseriesData,
|
||||
});
|
||||
}, [query, breakpoints, timeseriesDataLoading, timeseriesData]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="xs" direction="column" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<ManagedTable
|
||||
isLoading={isPending(status)}
|
||||
error={isFailure(status)}
|
||||
columns={serviceColumns}
|
||||
items={data}
|
||||
noItemsMessage={noItemsMessage}
|
||||
initialSortField={initialSortField}
|
||||
initialSortDirection={initialSortDirection}
|
||||
initialPageSize={initialPageSize}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -1,144 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiEmptyPrompt,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiImage,
|
||||
EuiLink,
|
||||
EuiText,
|
||||
EuiTextColor,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { dashboardsLight } from '@kbn/shared-svg';
|
||||
import useEffectOnce from 'react-use/lib/useEffectOnce';
|
||||
import { useKibana } from '../../../../../context/kibana_context/use_kibana';
|
||||
import { useLocalStorage } from '../../../../../hooks/use_local_storage';
|
||||
import { ApmPluginStartDeps, ApmServices } from '../../../../../plugin';
|
||||
import { EntityInventoryAddDataParams } from '../../../../../services/telemetry';
|
||||
import {
|
||||
AddApmData,
|
||||
AssociateServiceLogs,
|
||||
CollectServiceLogs,
|
||||
} from '../../../../shared/add_data_buttons/buttons';
|
||||
import { useBreakpoints } from '../../../../../hooks/use_breakpoints';
|
||||
|
||||
export function NoEntitiesEmptyState() {
|
||||
const { isLarge } = useBreakpoints();
|
||||
const { services } = useKibana<ApmPluginStartDeps & ApmServices>();
|
||||
const [userHasDismissedCallout, setUserHasDismissedCallout] = useLocalStorage(
|
||||
'apm.uiNewExperienceCallout',
|
||||
false
|
||||
);
|
||||
|
||||
useEffectOnce(() => {
|
||||
services.telemetry.reportEntityInventoryPageState({ state: 'empty_state' });
|
||||
});
|
||||
|
||||
function reportButtonClick(journey: EntityInventoryAddDataParams['journey']) {
|
||||
services.telemetry.reportEntityInventoryAddData({
|
||||
view: 'empty_state',
|
||||
journey,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column">
|
||||
{!userHasDismissedCallout && (
|
||||
<EuiFlexItem>
|
||||
<EuiCallOut
|
||||
css={{ textAlign: 'left' }}
|
||||
onDismiss={() => setUserHasDismissedCallout(true)}
|
||||
title={i18n.translate('xpack.apm.noEntitiesEmptyState.callout.title', {
|
||||
defaultMessage: 'Trying for the first time?',
|
||||
})}
|
||||
>
|
||||
<p>
|
||||
{i18n.translate('xpack.apm.noEntitiesEmptyState.description', {
|
||||
defaultMessage:
|
||||
'It can take up to a couple of minutes for your services to show. Try refreshing the page in a minute.',
|
||||
})}
|
||||
</p>
|
||||
<EuiLink
|
||||
external
|
||||
target="_blank"
|
||||
data-test-subj="apmNewExperienceEmptyStateLink"
|
||||
href="https://ela.st/elastic-entity-model-first-time"
|
||||
>
|
||||
{i18n.translate('xpack.apm.noEntitiesEmptyState.learnMore.link', {
|
||||
defaultMessage: 'Learn more',
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiCallOut>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiEmptyPrompt
|
||||
hasShadow={false}
|
||||
hasBorder={false}
|
||||
id="apmNewExperienceEmptyState"
|
||||
icon={<EuiImage size="fullWidth" src={dashboardsLight} alt="" />}
|
||||
title={
|
||||
<h2>
|
||||
{i18n.translate('xpack.apm.noEntitiesEmptyState.title', {
|
||||
defaultMessage: 'No services available.',
|
||||
})}
|
||||
</h2>
|
||||
}
|
||||
layout={isLarge ? 'vertical' : 'horizontal'}
|
||||
color="plain"
|
||||
body={
|
||||
<>
|
||||
<p>
|
||||
{i18n.translate('xpack.apm.noEntitiesEmptyState.body.description', {
|
||||
defaultMessage:
|
||||
'The services inventory provides an overview of the health and general performance of your services. To add data to this page, instrument your services using the APM agent or detect services from your logs.',
|
||||
})}
|
||||
</p>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<EuiText textAlign="left">
|
||||
<h5>
|
||||
<EuiTextColor color="default">
|
||||
{i18n.translate('xpack.apm.noEntitiesEmptyState.actions.title', {
|
||||
defaultMessage: 'Start observing your services:',
|
||||
})}
|
||||
</EuiTextColor>
|
||||
</h5>
|
||||
</EuiText>
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<EuiFlexGroup responsive={false} wrap gutterSize="xl" direction="column">
|
||||
<EuiFlexGroup direction="row" gutterSize="xs">
|
||||
<AddApmData
|
||||
data-test-subj="apmAddDataEmptyState"
|
||||
onClick={() => {
|
||||
reportButtonClick('add_apm_agent');
|
||||
}}
|
||||
/>
|
||||
<CollectServiceLogs
|
||||
onClick={() => {
|
||||
reportButtonClick('collect_new_service_logs');
|
||||
}}
|
||||
/>
|
||||
<AssociateServiceLogs
|
||||
onClick={() => {
|
||||
reportButtonClick('associate_existing_service_logs');
|
||||
}}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -8,16 +8,16 @@
|
|||
import { CoreStart } from '@kbn/core/public';
|
||||
import { Meta, Story } from '@storybook/react';
|
||||
import React from 'react';
|
||||
import { ApmServiceInventory } from '.';
|
||||
import { AnomalyDetectionSetupState } from '../../../../../common/anomaly_detection/get_anomaly_detection_setup_state';
|
||||
import { AnomalyDetectionJobsContext } from '../../../../context/anomaly_detection_jobs/anomaly_detection_jobs_context';
|
||||
import { ApmPluginContextValue } from '../../../../context/apm_plugin/apm_plugin_context';
|
||||
import { MockApmPluginStorybook } from '../../../../context/apm_plugin/mock_apm_plugin_storybook';
|
||||
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
|
||||
import { ServiceInventory } from '.';
|
||||
import { AnomalyDetectionSetupState } from '../../../../common/anomaly_detection/get_anomaly_detection_setup_state';
|
||||
import { AnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/anomaly_detection_jobs_context';
|
||||
import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context';
|
||||
import { MockApmPluginStorybook } from '../../../context/apm_plugin/mock_apm_plugin_storybook';
|
||||
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
|
||||
|
||||
const stories: Meta<{}> = {
|
||||
title: 'app/ServiceInventory',
|
||||
component: ApmServiceInventory,
|
||||
component: ServiceInventory,
|
||||
decorators: [
|
||||
(StoryComponent) => {
|
||||
const coreMock = {
|
||||
|
@ -60,5 +60,5 @@ const stories: Meta<{}> = {
|
|||
export default stories;
|
||||
|
||||
export const Example: Story<{}> = () => {
|
||||
return <ApmServiceInventory />;
|
||||
return <ServiceInventory />;
|
||||
};
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { APIReturnType } from '../../../../../../services/rest/create_call_apm_api';
|
||||
import { APIReturnType } from '../../../../../services/rest/create_call_apm_api';
|
||||
|
||||
type ServiceListAPIResponse = APIReturnType<'GET /internal/apm/services'>;
|
||||
|
|
@ -20,40 +20,37 @@ import { ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils';
|
|||
import { TypeOf } from '@kbn/typed-react-router-config';
|
||||
import { omit } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { ServiceHealthStatus } from '../../../../../../common/service_health_status';
|
||||
import { ServiceHealthStatus } from '../../../../../common/service_health_status';
|
||||
import {
|
||||
ServiceInventoryFieldName,
|
||||
ServiceListItem,
|
||||
} from '../../../../../../common/service_inventory';
|
||||
import { isDefaultTransactionType } from '../../../../../../common/transaction_types';
|
||||
} from '../../../../../common/service_inventory';
|
||||
import { isDefaultTransactionType } from '../../../../../common/transaction_types';
|
||||
import {
|
||||
asMillisecondDuration,
|
||||
asPercent,
|
||||
asTransactionRate,
|
||||
} from '../../../../../../common/utils/formatters';
|
||||
import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useApmParams } from '../../../../../hooks/use_apm_params';
|
||||
import { useApmRouter } from '../../../../../hooks/use_apm_router';
|
||||
import { Breakpoints, useBreakpoints } from '../../../../../hooks/use_breakpoints';
|
||||
import { useFallbackToTransactionsFetcher } from '../../../../../hooks/use_fallback_to_transactions_fetcher';
|
||||
import { FETCH_STATUS, isFailure, isPending } from '../../../../../hooks/use_fetcher';
|
||||
import { APIReturnType } from '../../../../../services/rest/create_call_apm_api';
|
||||
import { unit } from '../../../../../utils/style';
|
||||
import { ApmRoutes } from '../../../../routing/apm_route_config';
|
||||
import { AggregatedTransactionsBadge } from '../../../../shared/aggregated_transactions_badge';
|
||||
import {
|
||||
ChartType,
|
||||
getTimeSeriesColor,
|
||||
} from '../../../../shared/charts/helper/get_timeseries_color';
|
||||
import { EnvironmentBadge } from '../../../../shared/environment_badge';
|
||||
import { ServiceLink } from '../../../../shared/links/apm/service_link';
|
||||
import { ListMetric } from '../../../../shared/list_metric';
|
||||
} from '../../../../../common/utils/formatters';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useApmParams } from '../../../../hooks/use_apm_params';
|
||||
import { useApmRouter } from '../../../../hooks/use_apm_router';
|
||||
import { Breakpoints, useBreakpoints } from '../../../../hooks/use_breakpoints';
|
||||
import { useFallbackToTransactionsFetcher } from '../../../../hooks/use_fallback_to_transactions_fetcher';
|
||||
import { FETCH_STATUS, isFailure, isPending } from '../../../../hooks/use_fetcher';
|
||||
import { APIReturnType } from '../../../../services/rest/create_call_apm_api';
|
||||
import { unit } from '../../../../utils/style';
|
||||
import { ApmRoutes } from '../../../routing/apm_route_config';
|
||||
import { AggregatedTransactionsBadge } from '../../../shared/aggregated_transactions_badge';
|
||||
import { ChartType, getTimeSeriesColor } from '../../../shared/charts/helper/get_timeseries_color';
|
||||
import { EnvironmentBadge } from '../../../shared/environment_badge';
|
||||
import { ServiceLink } from '../../../shared/links/apm/service_link';
|
||||
import { ListMetric } from '../../../shared/list_metric';
|
||||
import {
|
||||
ITableColumn,
|
||||
ManagedTable,
|
||||
SortFunction,
|
||||
TableSearchBar,
|
||||
} from '../../../../shared/managed_table';
|
||||
} from '../../../shared/managed_table';
|
||||
import { ColumnHeaderWithTooltip } from './column_header_with_tooltip';
|
||||
import { HealthBadge } from './health_badge';
|
||||
|
|
@ -11,8 +11,8 @@ import {
|
|||
getServiceHealthStatusBadgeColor,
|
||||
getServiceHealthStatusLabel,
|
||||
ServiceHealthStatus,
|
||||
} from '../../../../../../common/service_health_status';
|
||||
import { useTheme } from '../../../../../hooks/use_theme';
|
||||
} from '../../../../../common/service_health_status';
|
||||
import { useTheme } from '../../../../hooks/use_theme';
|
||||
|
||||
export function HealthBadge({ healthStatus }: { healthStatus: ServiceHealthStatus }) {
|
||||
const theme = useTheme();
|
|
@ -4,8 +4,8 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { ServiceHealthStatus } from '../../../../../../common/service_health_status';
|
||||
import { ServiceInventoryFieldName } from '../../../../../../common/service_inventory';
|
||||
import { ServiceHealthStatus } from '../../../../../common/service_health_status';
|
||||
import { ServiceInventoryFieldName } from '../../../../../common/service_inventory';
|
||||
import { orderServiceItems } from './order_service_items';
|
||||
|
||||
describe('orderServiceItems', () => {
|
|
@ -5,11 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { orderBy } from 'lodash';
|
||||
import { ServiceHealthStatus } from '../../../../../../common/service_health_status';
|
||||
import { ServiceHealthStatus } from '../../../../../common/service_health_status';
|
||||
import {
|
||||
ServiceListItem,
|
||||
ServiceInventoryFieldName,
|
||||
} from '../../../../../../common/service_inventory';
|
||||
} from '../../../../../common/service_inventory';
|
||||
|
||||
type SortValueGetter = (item: ServiceListItem) => string | number;
|
||||
|
|
@ -8,13 +8,13 @@
|
|||
import { CoreStart } from '@kbn/core/public';
|
||||
import { Meta, Story } from '@storybook/react';
|
||||
import React, { ComponentProps } from 'react';
|
||||
import { FETCH_STATUS } from '../../../../../hooks/use_fetcher';
|
||||
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
|
||||
import { ApmServicesTable } from './apm_services_table';
|
||||
import { ServiceHealthStatus } from '../../../../../../common/service_health_status';
|
||||
import { ServiceInventoryFieldName } from '../../../../../../common/service_inventory';
|
||||
import type { ApmPluginContextValue } from '../../../../../context/apm_plugin/apm_plugin_context';
|
||||
import { MockApmPluginStorybook } from '../../../../../context/apm_plugin/mock_apm_plugin_storybook';
|
||||
import { mockApmApiCallResponse } from '../../../../../services/rest/call_apm_api_spy';
|
||||
import { ServiceHealthStatus } from '../../../../../common/service_health_status';
|
||||
import { ServiceInventoryFieldName } from '../../../../../common/service_inventory';
|
||||
import type { ApmPluginContextValue } from '../../../../context/apm_plugin/apm_plugin_context';
|
||||
import { MockApmPluginStorybook } from '../../../../context/apm_plugin/mock_apm_plugin_storybook';
|
||||
import { mockApmApiCallResponse } from '../../../../services/rest/call_apm_api_spy';
|
||||
import { items, overflowItems } from './__fixtures__/service_api_mock_data';
|
||||
|
||||
type Args = ComponentProps<typeof ApmServicesTable>;
|
|
@ -9,10 +9,10 @@ import { composeStories } from '@storybook/testing-react';
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { getServiceColumns } from './apm_services_table';
|
||||
import { ENVIRONMENT_ALL } from '../../../../../../common/environment_filter_values';
|
||||
import { Breakpoints } from '../../../../../hooks/use_breakpoints';
|
||||
import { apmRouter } from '../../../../routing/apm_route_config';
|
||||
import * as timeSeriesColor from '../../../../shared/charts/helper/get_timeseries_color';
|
||||
import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values';
|
||||
import { Breakpoints } from '../../../../hooks/use_breakpoints';
|
||||
import { apmRouter } from '../../../routing/apm_route_config';
|
||||
import * as timeSeriesColor from '../../../shared/charts/helper/get_timeseries_color';
|
||||
import * as stories from './service_list.stories';
|
||||
|
||||
const { ServiceListEmptyState, ServiceListWithItems } = composeStories(stories);
|
|
@ -11,7 +11,6 @@ import { AnnotationsContextProvider } from '../../../context/annotations/annotat
|
|||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
|
||||
import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context';
|
||||
import { useEntityManagerEnablementContext } from '../../../context/entity_manager_context/use_entity_manager_enablement_context';
|
||||
import { useApmParams } from '../../../hooks/use_apm_params';
|
||||
import { useTimeRange } from '../../../hooks/use_time_range';
|
||||
import { isApmSignal, isLogsSignal, isLogsOnlySignal } from '../../../utils/get_signal_type';
|
||||
|
@ -21,6 +20,7 @@ import { ServiceTabEmptyState } from '../service_tab_empty_state';
|
|||
import { useLocalStorage } from '../../../hooks/use_local_storage';
|
||||
import { SearchBar } from '../../shared/search_bar/search_bar';
|
||||
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
|
||||
import { useEntityCentricExperienceSetting } from '../../../hooks/use_entity_centric_experience_setting';
|
||||
/**
|
||||
* The height a chart should be if it's next to a table with 5 rows and a title.
|
||||
* Add the height of the pagination row.
|
||||
|
@ -28,7 +28,7 @@ import { FETCH_STATUS } from '../../../hooks/use_fetcher';
|
|||
export const chartHeight = 288;
|
||||
|
||||
export function ServiceOverview() {
|
||||
const { isEntityCentricExperienceViewEnabled } = useEntityManagerEnablementContext();
|
||||
const { isEntityCentricExperienceEnabled } = useEntityCentricExperienceSetting();
|
||||
const { serviceName, serviceEntitySummary, serviceEntitySummaryStatus } = useApmServiceContext();
|
||||
|
||||
const setScreenContext = useApmPluginContext().observabilityAIAssistant?.service.setScreenContext;
|
||||
|
@ -68,8 +68,7 @@ export function ServiceOverview() {
|
|||
const hasApmSignal = hasSignal && isApmSignal(serviceEntitySummary.dataStreamTypes);
|
||||
|
||||
// Shows APM overview when entity has APM signal or when Entity centric is not enabled or when entity has no signal
|
||||
const showApmOverview =
|
||||
isEntityCentricExperienceViewEnabled === false || hasApmSignal || !hasSignal;
|
||||
const showApmOverview = isEntityCentricExperienceEnabled === false || hasApmSignal || !hasSignal;
|
||||
|
||||
if (serviceEntitySummaryStatus === FETCH_STATUS.LOADING) {
|
||||
return (
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { LogRateChart } from '../../entities/charts/log_rate_chart';
|
||||
import { LogErrorRateChart } from '../../entities/charts/log_error_rate_chart';
|
||||
import { chartHeight } from '..';
|
||||
import { LogRateChart } from '../../../shared/charts/log_rates/log_rate_chart';
|
||||
import { LogErrorRateChart } from '../../../shared/charts/log_rates/log_error_rate_chart';
|
||||
|
||||
export function LogsOverview() {
|
||||
return (
|
||||
|
|
|
@ -22,8 +22,6 @@ import {
|
|||
collectServiceLogs,
|
||||
addApmData,
|
||||
} from '../../../shared/add_data_buttons/buttons';
|
||||
import { ServiceEcoTour } from '../../../shared/entity_enablement/service_eco_tour';
|
||||
import { useEntityManagerEnablementContext } from '../../../../context/entity_manager_context/use_entity_manager_enablement_context';
|
||||
|
||||
const addData = i18n.translate('xpack.apm.addDataContextMenu.link', {
|
||||
defaultMessage: 'Add data',
|
||||
|
@ -31,7 +29,6 @@ const addData = i18n.translate('xpack.apm.addDataContextMenu.link', {
|
|||
|
||||
export function AddDataContextMenu() {
|
||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||
const { tourState, updateTourState } = useEntityManagerEnablementContext();
|
||||
const { services } = useKibana<ApmPluginStartDeps & ApmServices>();
|
||||
const {
|
||||
core: {
|
||||
|
@ -93,23 +90,17 @@ export function AddDataContextMenu() {
|
|||
},
|
||||
];
|
||||
|
||||
const handleTourClose = () => {
|
||||
updateTourState({ isTourActive: false });
|
||||
setPopoverOpen(false);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<EuiPopover
|
||||
id="integrations-menu"
|
||||
button={button}
|
||||
isOpen={popoverOpen || tourState.isTourActive}
|
||||
isOpen={popoverOpen}
|
||||
closePopover={() => setPopoverOpen(false)}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downRight"
|
||||
>
|
||||
<ServiceEcoTour onFinish={handleTourClose}>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</ServiceEcoTour>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -17,7 +17,7 @@ import { AnomalyDetectionSetupLink } from './anomaly_detection_setup_link';
|
|||
import { InspectorHeaderLink } from './inspector_header_link';
|
||||
import { Labs } from './labs';
|
||||
import { AddDataContextMenu } from './add_data_context_menu';
|
||||
import { useEntityManagerEnablementContext } from '../../../../context/entity_manager_context/use_entity_manager_enablement_context';
|
||||
import { useEntityCentricExperienceSetting } from '../../../../hooks/use_entity_centric_experience_setting';
|
||||
|
||||
export function ApmHeaderActionMenu() {
|
||||
const { core, plugins, config } = useApmPluginContext();
|
||||
|
@ -33,8 +33,7 @@ export function ApmHeaderActionMenu() {
|
|||
capabilities
|
||||
);
|
||||
const canSaveApmAlerts = capabilities.apm.save && canSaveAlerts;
|
||||
const { isEntityCentricExperienceViewEnabled, isEnablementPending } =
|
||||
useEntityManagerEnablementContext();
|
||||
const { isEntityCentricExperienceEnabled } = useEntityCentricExperienceSetting();
|
||||
|
||||
function apmHref(path: string) {
|
||||
return getLegacyApmHref({ basePath, path, search });
|
||||
|
@ -73,11 +72,10 @@ export function ApmHeaderActionMenu() {
|
|||
canReadMlJobs={canReadMlJobs}
|
||||
/>
|
||||
)}
|
||||
{isEntityCentricExperienceViewEnabled ? (
|
||||
{isEntityCentricExperienceEnabled ? (
|
||||
<AddDataContextMenu />
|
||||
) : (
|
||||
<EuiHeaderLink
|
||||
isLoading={isEnablementPending}
|
||||
color="primary"
|
||||
href={kibanaHref('/app/apm/tutorial')}
|
||||
iconType="indexOpen"
|
||||
|
|
|
@ -42,7 +42,6 @@ import { RedirectWithDefaultEnvironment } from './redirect_with_default_environm
|
|||
import { RedirectWithOffset } from './redirect_with_offset';
|
||||
import { ScrollToTopOnPathChange } from './scroll_to_top_on_path_change';
|
||||
import { UpdateExecutionContextOnRouteChange } from './update_execution_context_on_route_change';
|
||||
import { EntityManagerEnablementContextProvider } from '../../../context/entity_manager_context/entity_manager_context';
|
||||
|
||||
const storage = new Storage(localStorage);
|
||||
|
||||
|
@ -83,17 +82,15 @@ export function ApmAppRoot({
|
|||
<BreadcrumbsContextProvider>
|
||||
<UrlParamsProvider>
|
||||
<LicenseProvider>
|
||||
<EntityManagerEnablementContextProvider>
|
||||
<AnomalyDetectionJobsContextProvider>
|
||||
<InspectorContextProvider>
|
||||
<ApmThemeProvider>
|
||||
<MountApmHeaderActionMenu />
|
||||
<Route component={ScrollToTopOnPathChange} />
|
||||
<RouteRenderer />
|
||||
</ApmThemeProvider>
|
||||
</InspectorContextProvider>
|
||||
</AnomalyDetectionJobsContextProvider>
|
||||
</EntityManagerEnablementContextProvider>
|
||||
<AnomalyDetectionJobsContextProvider>
|
||||
<InspectorContextProvider>
|
||||
<ApmThemeProvider>
|
||||
<MountApmHeaderActionMenu />
|
||||
<Route component={ScrollToTopOnPathChange} />
|
||||
<RouteRenderer />
|
||||
</ApmThemeProvider>
|
||||
</InspectorContextProvider>
|
||||
</AnomalyDetectionJobsContextProvider>
|
||||
</LicenseProvider>
|
||||
</UrlParamsProvider>
|
||||
</BreadcrumbsContextProvider>
|
||||
|
|
|
@ -14,7 +14,6 @@ import React, { useContext } from 'react';
|
|||
import { useLocation } from 'react-router-dom';
|
||||
import { FeatureFeedbackButton } from '@kbn/observability-shared-plugin/public';
|
||||
import { useLocalStorage } from '../../../../hooks/use_local_storage';
|
||||
import { useEntityManagerEnablementContext } from '../../../../context/entity_manager_context/use_entity_manager_enablement_context';
|
||||
import { useDefaultAiAssistantStarterPromptsForAPM } from '../../../../hooks/use_default_ai_assistant_starter_prompts_for_apm';
|
||||
import { KibanaEnvironmentContext } from '../../../../context/kibana_environment_context/kibana_environment_context';
|
||||
import { getPathForFeedback } from '../../../../utils/get_path_for_feedback';
|
||||
|
@ -27,6 +26,7 @@ import { ApmEnvironmentFilter } from '../../../shared/environment_filter';
|
|||
import { getNoDataConfig } from '../no_data_config';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { EntitiesInventoryCallout } from './entities_inventory_callout';
|
||||
import { useEntityCentricExperienceSetting } from '../../../../hooks/use_entity_centric_experience_setting';
|
||||
|
||||
// Paths that must skip the no data screen
|
||||
const bypassNoDataScreenPaths = ['/settings', '/diagnostics'];
|
||||
|
@ -77,7 +77,7 @@ export function ApmMainTemplate({
|
|||
true
|
||||
);
|
||||
|
||||
const { isEntityCentricExperienceViewEnabled } = useEntityManagerEnablementContext();
|
||||
const { isEntityCentricExperienceEnabled } = useEntityCentricExperienceSetting();
|
||||
|
||||
const ObservabilityPageTemplate = observabilityShared.navigation.PageTemplate;
|
||||
|
||||
|
@ -146,7 +146,7 @@ export function ApmMainTemplate({
|
|||
<FeatureFeedbackButton
|
||||
data-test-subj="infraApmFeedbackLink"
|
||||
formUrl={
|
||||
isEntityCentricExperienceViewEnabled && sanitizedPath.includes('service')
|
||||
isEntityCentricExperienceEnabled && sanitizedPath.includes('service')
|
||||
? APM_NEW_EXPERIENCE_FEEDBACK_LINK
|
||||
: APM_FEEDBACK_LINK
|
||||
}
|
||||
|
|
|
@ -14,18 +14,15 @@ import { useApmParams } from '../../../../hooks/use_apm_params';
|
|||
import { useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { useTimeRange } from '../../../../hooks/use_time_range';
|
||||
import { APIReturnType } from '../../../../services/rest/create_call_apm_api';
|
||||
import { getTimeSeriesColor, ChartType } from '../../../shared/charts/helper/get_timeseries_color';
|
||||
import { TimeseriesChartWithContext } from '../../../shared/charts/timeseries_chart_with_context';
|
||||
import { asInteger } from '../../../../../common/utils/formatters';
|
||||
import { TooltipContent } from '../../service_inventory/multi_signal_inventory/table/tooltip_content';
|
||||
import { Popover } from '../../service_inventory/multi_signal_inventory/table/popover';
|
||||
import {
|
||||
ChartMetricType,
|
||||
getMetricsFormula,
|
||||
} from '../../../shared/charts/helper/get_metrics_formulas';
|
||||
import { ExploreLogsButton } from '../../../shared/explore_logs_button/explore_logs_button';
|
||||
import { TooltipContent } from './tooltip_content';
|
||||
import { Popover } from './popover';
|
||||
import { mergeKueries, toKueryFilterFormat } from '../../../../../common/utils/kuery_utils';
|
||||
import { ERROR_LOG_LEVEL, LOG_LEVEL } from '../../../../../common/es_fields/apm';
|
||||
import { ExploreLogsButton } from '../../explore_logs_button/explore_logs_button';
|
||||
import { TimeseriesChartWithContext } from '../timeseries_chart_with_context';
|
||||
import { ChartMetricType, getMetricsFormula } from '../helper/get_metrics_formulas';
|
||||
import { getTimeSeriesColor, ChartType } from '../helper/get_timeseries_color';
|
||||
|
||||
type LogErrorRateReturnType =
|
||||
APIReturnType<'GET /internal/apm/entities/services/{serviceName}/logs_error_rate_timeseries'>;
|
|
@ -13,16 +13,13 @@ import { useApmParams } from '../../../../hooks/use_apm_params';
|
|||
import { useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { useTimeRange } from '../../../../hooks/use_time_range';
|
||||
import { APIReturnType } from '../../../../services/rest/create_call_apm_api';
|
||||
import { getTimeSeriesColor, ChartType } from '../../../shared/charts/helper/get_timeseries_color';
|
||||
import { TimeseriesChartWithContext } from '../../../shared/charts/timeseries_chart_with_context';
|
||||
import { asInteger } from '../../../../../common/utils/formatters';
|
||||
import { TooltipContent } from '../../service_inventory/multi_signal_inventory/table/tooltip_content';
|
||||
import { Popover } from '../../service_inventory/multi_signal_inventory/table/popover';
|
||||
import {
|
||||
getMetricsFormula,
|
||||
ChartMetricType,
|
||||
} from '../../../shared/charts/helper/get_metrics_formulas';
|
||||
import { ExploreLogsButton } from '../../../shared/explore_logs_button/explore_logs_button';
|
||||
import { TooltipContent } from './tooltip_content';
|
||||
import { Popover } from './popover';
|
||||
import { ChartType, getTimeSeriesColor } from '../helper/get_timeseries_color';
|
||||
import { ChartMetricType, getMetricsFormula } from '../helper/get_metrics_formulas';
|
||||
import { ExploreLogsButton } from '../../explore_logs_button/explore_logs_button';
|
||||
import { TimeseriesChartWithContext } from '../timeseries_chart_with_context';
|
||||
|
||||
type LogRateReturnType =
|
||||
APIReturnType<'GET /internal/apm/entities/services/{serviceName}/logs_rate_timeseries'>;
|
|
@ -10,7 +10,7 @@ import { EuiText } from '@elastic/eui';
|
|||
import { css } from '@emotion/react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
export interface TooltipContentProps extends Pick<HTMLAttributes<HTMLDivElement>, 'style'> {
|
||||
interface TooltipContentProps extends Pick<HTMLAttributes<HTMLDivElement>, 'style'> {
|
||||
description: ReactElement | string;
|
||||
formula?: string;
|
||||
}
|
|
@ -1,49 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiText, EuiTourStep } from '@elastic/eui';
|
||||
import { useEntityManagerEnablementContext } from '../../../context/entity_manager_context/use_entity_manager_enablement_context';
|
||||
|
||||
export function ServiceEcoTour({
|
||||
children,
|
||||
onFinish,
|
||||
}: {
|
||||
children: React.ReactElement;
|
||||
onFinish: () => void;
|
||||
}) {
|
||||
const { tourState } = useEntityManagerEnablementContext();
|
||||
|
||||
return (
|
||||
<EuiTourStep
|
||||
content={
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate('xpack.apm.serviceEcoTour.content', {
|
||||
defaultMessage: 'You can now add services from logs to the service inventory',
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
}
|
||||
isStepOpen={tourState.isTourActive}
|
||||
minWidth={200}
|
||||
onFinish={onFinish}
|
||||
step={1}
|
||||
stepsTotal={1}
|
||||
title={i18n.translate('xpack.apm.serviceEcoTour.title', {
|
||||
defaultMessage: 'Add services from logs',
|
||||
})}
|
||||
subtitle={i18n.translate('xpack.apm.serviceEcoTour.subtitle', {
|
||||
defaultMessage: 'New Services Inventory',
|
||||
})}
|
||||
anchorPosition="rightUp"
|
||||
>
|
||||
{children}
|
||||
</EuiTourStep>
|
||||
);
|
||||
}
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useEntityCentricExperienceSetting } from '../../hooks/use_entity_centric_experience_setting';
|
||||
import { useFetcher } from '../../hooks/use_fetcher';
|
||||
import { APIReturnType } from '../../services/rest/create_call_apm_api';
|
||||
import { useEntityManagerEnablementContext } from '../entity_manager_context/use_entity_manager_enablement_context';
|
||||
|
||||
export type ServiceEntitySummary =
|
||||
APIReturnType<'GET /internal/apm/entities/services/{serviceName}/summary'>;
|
||||
|
@ -21,17 +21,17 @@ export function useServiceEntitySummaryFetcher({
|
|||
end?: string;
|
||||
environment?: string;
|
||||
}) {
|
||||
const { isEntityCentricExperienceViewEnabled } = useEntityManagerEnablementContext();
|
||||
const { isEntityCentricExperienceEnabled } = useEntityCentricExperienceSetting();
|
||||
|
||||
const { data, status } = useFetcher(
|
||||
(callAPI) => {
|
||||
if (isEntityCentricExperienceViewEnabled && serviceName && environment) {
|
||||
if (isEntityCentricExperienceEnabled && serviceName && environment) {
|
||||
return callAPI('GET /internal/apm/entities/services/{serviceName}/summary', {
|
||||
params: { path: { serviceName }, query: { environment } },
|
||||
});
|
||||
}
|
||||
},
|
||||
[environment, isEntityCentricExperienceViewEnabled, serviceName]
|
||||
[environment, isEntityCentricExperienceEnabled, serviceName]
|
||||
);
|
||||
|
||||
return { serviceEntitySummary: data, serviceEntitySummaryStatus: status };
|
||||
|
|
|
@ -1,94 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { entityCentricExperience } from '@kbn/observability-plugin/common';
|
||||
import React, { createContext } from 'react';
|
||||
import {
|
||||
SERVICE_INVENTORY_STORAGE_KEY,
|
||||
serviceInventoryViewType$,
|
||||
} from '../../analytics/register_service_inventory_view_type_context';
|
||||
import { useLocalStorage } from '../../hooks/use_local_storage';
|
||||
import { ApmPluginStartDeps, ApmServices } from '../../plugin';
|
||||
import { useApmPluginContext } from '../apm_plugin/use_apm_plugin_context';
|
||||
import { useKibana } from '../kibana_context/use_kibana';
|
||||
import { ENTITY_FETCH_STATUS, useEntityManager } from './use_entity_manager';
|
||||
|
||||
export interface EntityManagerEnablementContextValue {
|
||||
isEntityManagerEnabled: boolean;
|
||||
isEnablementPending: boolean;
|
||||
refetch: () => void;
|
||||
serviceInventoryViewLocalStorageSetting: ServiceInventoryView;
|
||||
setServiceInventoryViewLocalStorageSetting: (view: ServiceInventoryView) => void;
|
||||
isEntityCentricExperienceViewEnabled: boolean;
|
||||
tourState: TourState;
|
||||
updateTourState: (newState: Partial<TourState>) => void;
|
||||
}
|
||||
|
||||
export enum ServiceInventoryView {
|
||||
classic = 'classic',
|
||||
entity = 'entity',
|
||||
}
|
||||
|
||||
export const EntityManagerEnablementContext = createContext(
|
||||
{} as EntityManagerEnablementContextValue
|
||||
);
|
||||
|
||||
interface TourState {
|
||||
isModalVisible?: boolean;
|
||||
isTourActive: boolean;
|
||||
}
|
||||
const TOUR_INITIAL_STATE: TourState = {
|
||||
isModalVisible: undefined,
|
||||
isTourActive: false,
|
||||
};
|
||||
|
||||
export function EntityManagerEnablementContextProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactChild;
|
||||
}) {
|
||||
const { core } = useApmPluginContext();
|
||||
const { services } = useKibana<ApmPluginStartDeps & ApmServices>();
|
||||
const { isEnabled: isEntityManagerEnabled, status, refetch } = useEntityManager();
|
||||
const [tourState, setTourState] = useLocalStorage('apm.serviceEcoTour', TOUR_INITIAL_STATE);
|
||||
const [serviceInventoryViewLocalStorageSetting, setServiceInventoryViewLocalStorageSetting] =
|
||||
useLocalStorage(SERVICE_INVENTORY_STORAGE_KEY, ServiceInventoryView.classic);
|
||||
|
||||
const isEntityCentricExperienceSettingEnabled = core.uiSettings.get<boolean>(
|
||||
entityCentricExperience,
|
||||
true
|
||||
);
|
||||
|
||||
function handleServiceInventoryViewChange(nextView: ServiceInventoryView) {
|
||||
setServiceInventoryViewLocalStorageSetting(nextView);
|
||||
// Updates the telemetry context variable every time the user switches views
|
||||
serviceInventoryViewType$.next({ serviceInventoryViewType: nextView });
|
||||
services.telemetry.reportEntityExperienceStatusChange({
|
||||
status: nextView === ServiceInventoryView.entity ? 'enabled' : 'disabled',
|
||||
});
|
||||
}
|
||||
|
||||
function handleTourStateUpdate(newTourState: Partial<TourState>) {
|
||||
setTourState({ ...tourState, ...newTourState });
|
||||
}
|
||||
|
||||
return (
|
||||
<EntityManagerEnablementContext.Provider
|
||||
value={{
|
||||
isEntityManagerEnabled,
|
||||
isEnablementPending: status === ENTITY_FETCH_STATUS.LOADING,
|
||||
refetch,
|
||||
serviceInventoryViewLocalStorageSetting,
|
||||
setServiceInventoryViewLocalStorageSetting: handleServiceInventoryViewChange,
|
||||
isEntityCentricExperienceViewEnabled: isEntityCentricExperienceSettingEnabled,
|
||||
tourState,
|
||||
updateTourState: handleTourStateUpdate,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</EntityManagerEnablementContext.Provider>
|
||||
);
|
||||
}
|
|
@ -1,55 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ApmPluginStartDeps } from '../../plugin';
|
||||
|
||||
export enum ENTITY_FETCH_STATUS {
|
||||
LOADING = 'loading',
|
||||
SUCCESS = 'success',
|
||||
FAILURE = 'failure',
|
||||
NOT_INITIATED = 'not_initiated',
|
||||
}
|
||||
|
||||
export function useEntityManager() {
|
||||
const {
|
||||
services: { entityManager },
|
||||
} = useKibana<ApmPluginStartDeps>();
|
||||
const [counter, setCounter] = useState(0);
|
||||
const [result, setResult] = useState({
|
||||
isEnabled: false,
|
||||
status: ENTITY_FETCH_STATUS.NOT_INITIATED,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function isManagedEntityDiscoveryEnabled() {
|
||||
setResult({ isEnabled: false, status: ENTITY_FETCH_STATUS.LOADING });
|
||||
|
||||
try {
|
||||
const response = await entityManager.entityClient.isManagedEntityDiscoveryEnabled();
|
||||
setResult({ isEnabled: response?.enabled, status: ENTITY_FETCH_STATUS.SUCCESS });
|
||||
} catch (err) {
|
||||
setResult({ isEnabled: false, status: ENTITY_FETCH_STATUS.FAILURE });
|
||||
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
isManagedEntityDiscoveryEnabled();
|
||||
}, [entityManager, counter]);
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
...result,
|
||||
refetch: () => {
|
||||
// this will invalidate the deps to `useEffect` and will result in a new request
|
||||
setCounter((count) => count + 1);
|
||||
},
|
||||
};
|
||||
}, [result]);
|
||||
}
|
|
@ -1,13 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useContext } from 'react';
|
||||
import { EntityManagerEnablementContext } from './entity_manager_context';
|
||||
|
||||
export function useEntityManagerEnablementContext() {
|
||||
return useContext(EntityManagerEnablementContext);
|
||||
}
|
|
@ -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 { entityCentricExperience } from '@kbn/observability-plugin/common';
|
||||
import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context';
|
||||
|
||||
export function useEntityCentricExperienceSetting() {
|
||||
const { core } = useApmPluginContext();
|
||||
|
||||
const isEntityCentricExperienceEnabled = core.uiSettings.get<boolean>(
|
||||
entityCentricExperience,
|
||||
true
|
||||
);
|
||||
|
||||
return { isEntityCentricExperienceEnabled };
|
||||
}
|
|
@ -19,10 +19,6 @@ import {
|
|||
PluginInitializerContext,
|
||||
SecurityServiceStart,
|
||||
} from '@kbn/core/public';
|
||||
import {
|
||||
EntityManagerPublicPluginSetup,
|
||||
EntityManagerPublicPluginStart,
|
||||
} from '@kbn/entityManager-plugin/public';
|
||||
import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { DiscoverSetup, DiscoverStart } from '@kbn/discover-plugin/public';
|
||||
|
@ -86,7 +82,6 @@ import { getLazyAPMPolicyEditExtension } from './components/fleet_integration/la
|
|||
import { featureCatalogueEntry } from './feature_catalogue_entry';
|
||||
import { APMServiceDetailLocator } from './locator/service_detail_locator';
|
||||
import { ITelemetryClient, TelemetryService } from './services/telemetry';
|
||||
import { registerServiceInventoryViewTypeContext } from './analytics/register_service_inventory_view_type_context';
|
||||
|
||||
export type ApmPluginSetup = ReturnType<ApmPlugin['setup']>;
|
||||
export type ApmPluginStart = void;
|
||||
|
@ -111,7 +106,6 @@ export interface ApmPluginSetupDeps {
|
|||
uiActions: UiActionsSetup;
|
||||
profiling?: ProfilingPluginSetup;
|
||||
cloud?: CloudSetup;
|
||||
entityManager: EntityManagerPublicPluginSetup;
|
||||
}
|
||||
|
||||
export interface ApmServices {
|
||||
|
@ -148,7 +142,6 @@ export interface ApmPluginStartDeps {
|
|||
dashboard: DashboardStart;
|
||||
metricsDataAccess: MetricsDataPluginStart;
|
||||
uiSettings: IUiSettingsClient;
|
||||
entityManager: EntityManagerPublicPluginStart;
|
||||
}
|
||||
|
||||
const applicationsTitle = i18n.translate('xpack.apm.navigation.rootTitle', {
|
||||
|
@ -279,7 +272,6 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
|||
};
|
||||
|
||||
this.telemetry.setup({ analytics: core.analytics });
|
||||
registerServiceInventoryViewTypeContext(core.analytics);
|
||||
|
||||
// Registers a status check callback for the tutorial to call and verify if the APM integration is installed on fleet.
|
||||
pluginSetupDeps.home?.tutorials.registerCustomStatusCheck(
|
||||
|
|
|
@ -9,9 +9,7 @@ import { AnalyticsServiceSetup } from '@kbn/core-analytics-browser';
|
|||
import {
|
||||
ITelemetryClient,
|
||||
SearchQuerySubmittedParams,
|
||||
EntityExperienceStatusParams,
|
||||
TelemetryEventTypes,
|
||||
EntityInventoryPageStateParams,
|
||||
EntityInventoryAddDataParams,
|
||||
EmptyStateClickParams,
|
||||
} from './types';
|
||||
|
@ -31,14 +29,6 @@ export class TelemetryClient implements ITelemetryClient {
|
|||
});
|
||||
};
|
||||
|
||||
public reportEntityExperienceStatusChange = (params: EntityExperienceStatusParams) => {
|
||||
this.analytics.reportEvent(TelemetryEventTypes.ENTITY_EXPERIENCE_STATUS, params);
|
||||
};
|
||||
|
||||
public reportEntityInventoryPageState = (params: EntityInventoryPageStateParams) => {
|
||||
this.analytics.reportEvent(TelemetryEventTypes.ENTITY_INVENTORY_PAGE_STATE, params);
|
||||
};
|
||||
|
||||
public reportEntityInventoryAddData = (params: EntityInventoryAddDataParams) => {
|
||||
this.analytics.reportEvent(TelemetryEventTypes.ENTITY_INVENTORY_ADD_DATA, params);
|
||||
};
|
||||
|
|
|
@ -33,30 +33,6 @@ const searchQuerySubmittedEventType: TelemetryEvent = {
|
|||
},
|
||||
};
|
||||
|
||||
const entityExperienceStatusEventType: TelemetryEvent = {
|
||||
eventType: TelemetryEventTypes.ENTITY_EXPERIENCE_STATUS,
|
||||
schema: {
|
||||
status: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: 'The status of the Entity experience (Enabled or Disabled)',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const entityInventoryPageStateEventType: TelemetryEvent = {
|
||||
eventType: TelemetryEventTypes.ENTITY_INVENTORY_PAGE_STATE,
|
||||
schema: {
|
||||
state: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: 'The current entity inventory page state (empty_state or available)',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const entityInventoryAddDataEventType: TelemetryEvent = {
|
||||
eventType: TelemetryEventTypes.ENTITY_INVENTORY_ADD_DATA,
|
||||
schema: {
|
||||
|
@ -106,8 +82,6 @@ const learnMoreClickEventType: TelemetryEvent = {
|
|||
|
||||
export const apmTelemetryEventBasedTypes = [
|
||||
searchQuerySubmittedEventType,
|
||||
entityExperienceStatusEventType,
|
||||
entityInventoryPageStateEventType,
|
||||
entityInventoryAddDataEventType,
|
||||
tryItClickEventType,
|
||||
learnMoreClickEventType,
|
||||
|
|
|
@ -21,14 +21,6 @@ export interface SearchQuerySubmittedParams {
|
|||
action: SearchQueryActions;
|
||||
}
|
||||
|
||||
export interface EntityExperienceStatusParams {
|
||||
status: 'enabled' | 'disabled';
|
||||
}
|
||||
|
||||
export interface EntityInventoryPageStateParams {
|
||||
state: 'empty_state' | 'available';
|
||||
}
|
||||
|
||||
export interface EntityInventoryAddDataParams {
|
||||
view: 'empty_state' | 'add_data_button' | 'add_apm_cta' | 'add_apm_n/a';
|
||||
journey?: 'add_apm_agent' | 'associate_existing_service_logs' | 'collect_new_service_logs';
|
||||
|
@ -40,15 +32,11 @@ export interface EmptyStateClickParams {
|
|||
|
||||
export type TelemetryEventParams =
|
||||
| SearchQuerySubmittedParams
|
||||
| EntityExperienceStatusParams
|
||||
| EntityInventoryPageStateParams
|
||||
| EntityInventoryAddDataParams
|
||||
| EmptyStateClickParams;
|
||||
|
||||
export interface ITelemetryClient {
|
||||
reportSearchQuerySubmitted(params: SearchQuerySubmittedParams): void;
|
||||
reportEntityExperienceStatusChange(params: EntityExperienceStatusParams): void;
|
||||
reportEntityInventoryPageState(params: EntityInventoryPageStateParams): void;
|
||||
reportEntityInventoryAddData(params: EntityInventoryAddDataParams): void;
|
||||
reportTryItClick(params: EmptyStateClickParams): void;
|
||||
reportLearnMoreClick(params: EmptyStateClickParams): void;
|
||||
|
@ -56,8 +44,6 @@ export interface ITelemetryClient {
|
|||
|
||||
export enum TelemetryEventTypes {
|
||||
SEARCH_QUERY_SUBMITTED = 'Search Query Submitted',
|
||||
ENTITY_EXPERIENCE_STATUS = 'entity_experience_status',
|
||||
ENTITY_INVENTORY_PAGE_STATE = 'entity_inventory_page_state',
|
||||
ENTITY_INVENTORY_ADD_DATA = 'entity_inventory_add_data',
|
||||
TRY_IT_CLICK = 'try_it_click',
|
||||
LEARN_MORE_CLICK = 'learn_more_click',
|
||||
|
|
|
@ -119,7 +119,6 @@
|
|||
"@kbn/react-kibana-context-theme",
|
||||
"@kbn/test-jest-helpers",
|
||||
"@kbn/security-plugin-types-common",
|
||||
"@kbn/entityManager-plugin",
|
||||
"@kbn/server-route-repository-utils",
|
||||
"@kbn/core-analytics-browser",
|
||||
"@kbn/apm-types",
|
||||
|
|
|
@ -10647,12 +10647,6 @@
|
|||
"xpack.apm.mobileServiceDetails.serviceMapTabLabel": "Carte des services",
|
||||
"xpack.apm.mobileServiceDetails.transactionsTabLabel": "Transactions",
|
||||
"xpack.apm.mobileServices.breadcrumb.title": "Services",
|
||||
"xpack.apm.multiSignal.servicesTable.environmentColumnLabel": "Environnement",
|
||||
"xpack.apm.multiSignal.servicesTable.latencyAvgColumnLabel": "Latence (moy.)",
|
||||
"xpack.apm.multiSignal.servicesTable.logErrorRate": "Taux d'erreur des logs",
|
||||
"xpack.apm.multiSignal.servicesTable.nameColumnLabel": "Nom",
|
||||
"xpack.apm.multiSignal.servicesTable.throughputColumnLabel": "Rendement",
|
||||
"xpack.apm.multiSignal.servicesTable.transactionErrorRate": "Taux de transactions ayant échoué",
|
||||
"xpack.apm.navigation.apmSettingsTitle": "Paramètres",
|
||||
"xpack.apm.navigation.apmStorageExplorerTitle": "Explorateur de stockage",
|
||||
"xpack.apm.navigation.apmTutorialTitle": "Tutoriel",
|
||||
|
|
|
@ -10396,12 +10396,6 @@
|
|||
"xpack.apm.mobileServiceDetails.serviceMapTabLabel": "サービスマップ",
|
||||
"xpack.apm.mobileServiceDetails.transactionsTabLabel": "トランザクション",
|
||||
"xpack.apm.mobileServices.breadcrumb.title": "サービス",
|
||||
"xpack.apm.multiSignal.servicesTable.environmentColumnLabel": "環境",
|
||||
"xpack.apm.multiSignal.servicesTable.latencyAvgColumnLabel": "レイテンシ(平均)",
|
||||
"xpack.apm.multiSignal.servicesTable.logErrorRate": "ログエラー率",
|
||||
"xpack.apm.multiSignal.servicesTable.nameColumnLabel": "名前",
|
||||
"xpack.apm.multiSignal.servicesTable.throughputColumnLabel": "スループット",
|
||||
"xpack.apm.multiSignal.servicesTable.transactionErrorRate": "失敗したトランザクション率",
|
||||
"xpack.apm.navigation.apmSettingsTitle": "設定",
|
||||
"xpack.apm.navigation.apmStorageExplorerTitle": "ストレージエクスプローラー",
|
||||
"xpack.apm.navigation.apmTutorialTitle": "チュートリアル",
|
||||
|
|
|
@ -10418,12 +10418,6 @@
|
|||
"xpack.apm.mobileServiceDetails.serviceMapTabLabel": "服务地图",
|
||||
"xpack.apm.mobileServiceDetails.transactionsTabLabel": "事务",
|
||||
"xpack.apm.mobileServices.breadcrumb.title": "服务",
|
||||
"xpack.apm.multiSignal.servicesTable.environmentColumnLabel": "环境",
|
||||
"xpack.apm.multiSignal.servicesTable.latencyAvgColumnLabel": "延迟(平均值)",
|
||||
"xpack.apm.multiSignal.servicesTable.logErrorRate": "日志错误率",
|
||||
"xpack.apm.multiSignal.servicesTable.nameColumnLabel": "名称",
|
||||
"xpack.apm.multiSignal.servicesTable.throughputColumnLabel": "吞吐量",
|
||||
"xpack.apm.multiSignal.servicesTable.transactionErrorRate": "失败事务率",
|
||||
"xpack.apm.navigation.apmSettingsTitle": "设置",
|
||||
"xpack.apm.navigation.apmStorageExplorerTitle": "Storage Explorer",
|
||||
"xpack.apm.navigation.apmTutorialTitle": "教程",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue