mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Profiling] Empty state (#172295)
The empty state will show up when `has_setup` from the Profiling Status API returns `false`. <img width="1276" alt="Screenshot 2023-11-30 at 15 06 30" src="97a313be
-db4f-4a5a-af36-df574f9793d5"> <img width="1036" alt="Screenshot 2023-11-30 at 14 47 48" src="2622cad6
-6763-4abc-9469-fa292137efda">
This commit is contained in:
parent
a29b0f4573
commit
4a308c60fd
10 changed files with 182 additions and 22 deletions
|
@ -11,13 +11,18 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLink,
|
||||
EuiLoadingSpinner,
|
||||
EuiSpacer,
|
||||
EuiTabbedContent,
|
||||
EuiTabbedContentProps,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { EmbeddableProfilingSearchBar } from '@kbn/observability-shared-plugin/public';
|
||||
import {
|
||||
EmbeddableProfilingSearchBar,
|
||||
ProfilingEmptyState,
|
||||
} from '@kbn/observability-shared-plugin/public';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { ApmDocumentType } from '../../../../common/document_type';
|
||||
|
@ -38,7 +43,7 @@ export function ProfilingOverview() {
|
|||
path: { serviceName },
|
||||
query: { rangeFrom, rangeTo, environment, kuery },
|
||||
} = useApmParams('/services/{serviceName}/profiling');
|
||||
const { isProfilingAvailable } = useProfilingPlugin();
|
||||
const { isProfilingAvailable, isLoading } = useProfilingPlugin();
|
||||
const { start, end, refreshTimeRange } = useTimeRange({ rangeFrom, rangeTo });
|
||||
const preferred = usePreferredDataSourceAndBucketSize({
|
||||
start,
|
||||
|
@ -101,8 +106,21 @@ export function ProfilingOverview() {
|
|||
];
|
||||
}, [end, environment, kuery, preferred?.source, serviceName, start]);
|
||||
|
||||
if (!isProfilingAvailable) {
|
||||
return null;
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`}
|
||||
>
|
||||
<EuiLoadingSpinner size="m" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isProfilingAvailable === false) {
|
||||
return <ProfilingEmptyState />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLoadingLogo,
|
||||
|
@ -13,27 +14,28 @@ import {
|
|||
EuiSpacer,
|
||||
EuiTitle,
|
||||
EuiToolTip,
|
||||
EuiBadge,
|
||||
} from '@elastic/eui';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { enableAwsLambdaMetrics } from '@kbn/observability-plugin/common';
|
||||
import { omit } from 'lodash';
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
isMobileAgentName,
|
||||
isRumAgentName,
|
||||
isAWSLambdaAgentName,
|
||||
isAzureFunctionsAgentName,
|
||||
isServerlessAgentName,
|
||||
isMobileAgentName,
|
||||
isRumAgentName,
|
||||
isRumOrMobileAgentName,
|
||||
isServerlessAgentName,
|
||||
} from '../../../../../common/agent_name';
|
||||
import { ApmFeatureFlagName } from '../../../../../common/apm_feature_flags';
|
||||
import { ServerlessType } from '../../../../../common/serverless';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { ApmServiceContextProvider } from '../../../../context/apm_service/apm_service_context';
|
||||
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
|
||||
import { useBreadcrumb } from '../../../../context/breadcrumbs/use_breadcrumb';
|
||||
import { ServiceAnomalyTimeseriesContextProvider } from '../../../../context/service_anomaly_timeseries/service_anomaly_timeseries_context';
|
||||
import { useApmFeatureFlag } from '../../../../hooks/use_apm_feature_flag';
|
||||
import { useApmParams } from '../../../../hooks/use_apm_params';
|
||||
import { useApmRouter } from '../../../../hooks/use_apm_router';
|
||||
import { isPending, useFetcher } from '../../../../hooks/use_fetcher';
|
||||
|
@ -46,10 +48,6 @@ import { ServiceIcons } from '../../../shared/service_icons';
|
|||
import { TechnicalPreviewBadge } from '../../../shared/technical_preview_badge';
|
||||
import { ApmMainTemplate } from '../apm_main_template';
|
||||
import { AnalyzeDataButton } from './analyze_data_button';
|
||||
import { ServerlessType } from '../../../../../common/serverless';
|
||||
import { useApmFeatureFlag } from '../../../../hooks/use_apm_feature_flag';
|
||||
import { ApmFeatureFlagName } from '../../../../../common/apm_feature_flags';
|
||||
import { useProfilingPlugin } from '../../../../hooks/use_profiling_plugin';
|
||||
|
||||
type Tab = NonNullable<EuiPageHeaderProps['tabs']>[0] & {
|
||||
key:
|
||||
|
@ -220,7 +218,6 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) {
|
|||
plugins,
|
||||
capabilities
|
||||
);
|
||||
const { isProfilingAvailable } = useProfilingPlugin();
|
||||
|
||||
const router = useApmRouter();
|
||||
const isInfraTabAvailable = useApmFeatureFlag(
|
||||
|
@ -407,7 +404,6 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) {
|
|||
defaultMessage: 'Universal Profiling',
|
||||
}),
|
||||
hidden:
|
||||
!isProfilingAvailable ||
|
||||
isRumOrMobileAgentName(agentName) ||
|
||||
isAWSLambdaAgentName(serverlessType),
|
||||
append: (
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { apmEnableProfilingIntegration } from '@kbn/observability-plugin/common';
|
||||
import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useFetcher } from './use_fetcher';
|
||||
import { isPending, useFetcher } from './use_fetcher';
|
||||
|
||||
export function useProfilingPlugin() {
|
||||
const { plugins, core } = useApmPluginContext();
|
||||
|
@ -16,7 +16,7 @@ export function useProfilingPlugin() {
|
|||
true
|
||||
);
|
||||
|
||||
const { data } = useFetcher((callApmApi) => {
|
||||
const { data, status } = useFetcher((callApmApi) => {
|
||||
return callApmApi('GET /internal/apm/profiling/status');
|
||||
}, []);
|
||||
|
||||
|
@ -30,5 +30,6 @@ export function useProfilingPlugin() {
|
|||
isProfilingPluginInitialized: data?.initialized,
|
||||
isProfilingIntegrationEnabled,
|
||||
isProfilingAvailable,
|
||||
isLoading: isPending(status),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
|||
import React from 'react';
|
||||
import { DatePicker } from '../date_picker/date_picker';
|
||||
import { useTabSwitcherContext } from '../hooks/use_tab_switcher';
|
||||
import { Anomalies, Metadata, Processes, Osquery, Logs, Overview, Profiling } from '../tabs';
|
||||
import { Anomalies, Logs, Metadata, Osquery, Overview, Processes, Profiling } from '../tabs';
|
||||
import { ContentTabIds } from '../types';
|
||||
|
||||
export const Content = () => {
|
||||
|
@ -22,7 +22,6 @@ export const Content = () => {
|
|||
ContentTabIds.LOGS,
|
||||
ContentTabIds.METADATA,
|
||||
ContentTabIds.PROCESSES,
|
||||
ContentTabIds.PROFILING,
|
||||
ContentTabIds.ANOMALIES,
|
||||
]}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 type { ProfilingStatus } from '@kbn/profiling-utils';
|
||||
import { useEffect } from 'react';
|
||||
import { useHTTPRequest } from '../../../hooks/use_http_request';
|
||||
import { useRequestObservable } from './use_request_observable';
|
||||
|
||||
interface Props {
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export function useProfilingStatusData({ isActive }: Props) {
|
||||
const { request$ } = useRequestObservable<ProfilingStatus>();
|
||||
const { loading, error, response, makeRequest } = useHTTPRequest<ProfilingStatus>(
|
||||
`/api/infra/profiling/status`,
|
||||
'GET',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
request$.next(makeRequest);
|
||||
}, [isActive, makeRequest, request$]);
|
||||
|
||||
return {
|
||||
loading,
|
||||
error,
|
||||
response,
|
||||
};
|
||||
}
|
|
@ -8,10 +8,23 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { EuiSpacer, EuiTabbedContent, type EuiTabbedContentProps } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { ProfilingEmptyState } from '@kbn/observability-shared-plugin/public';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { Flamegraph } from './flamegraph';
|
||||
import { Functions } from './functions';
|
||||
import { DatePicker } from '../../date_picker/date_picker';
|
||||
import { useProfilingStatusData } from '../../hooks/use_profiling_status_data';
|
||||
import { useTabSwitcherContext } from '../../hooks/use_tab_switcher';
|
||||
import { ContentTabIds } from '../../types';
|
||||
import { ErrorPrompt } from './error_prompt';
|
||||
|
||||
export function Profiling() {
|
||||
const { activeTabId } = useTabSwitcherContext();
|
||||
const { error, loading, response } = useProfilingStatusData({
|
||||
isActive: activeTabId === ContentTabIds.PROFILING,
|
||||
});
|
||||
|
||||
const tabs: EuiTabbedContentProps['tabs'] = [
|
||||
{
|
||||
id: 'flamegraph',
|
||||
|
@ -39,9 +52,33 @@ export function Profiling() {
|
|||
},
|
||||
];
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`}
|
||||
>
|
||||
<EuiLoadingSpinner size="m" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error !== null) {
|
||||
return <ErrorPrompt />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[0]} />
|
||||
{response?.has_setup === false ? (
|
||||
<ProfilingEmptyState />
|
||||
) : (
|
||||
<>
|
||||
<DatePicker />
|
||||
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[0]} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ export const config: PluginConfigDescriptor<InfraConfig> = {
|
|||
* before enabling this flag.
|
||||
*/
|
||||
profilingEnabled: offeringBasedSchema({
|
||||
traditional: schema.boolean({ defaultValue: false }),
|
||||
traditional: schema.boolean({ defaultValue: true }),
|
||||
serverless: schema.boolean({ defaultValue: false }),
|
||||
}),
|
||||
}),
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { EuiButton, EuiEmptyPrompt, EuiImage, EuiLink } from '@elastic/eui';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import profilingImg from '../../images/profiling.png';
|
||||
|
||||
export function ProfilingEmptyState() {
|
||||
const { services } = useKibana();
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
icon={<EuiImage size="fullWidth" src={profilingImg} alt="" />}
|
||||
title={
|
||||
<h2>
|
||||
{i18n.translate('xpack.observabilityShared.profilingEmptyState.title', {
|
||||
defaultMessage:
|
||||
'Improve computational efficiency. Debug performance regressions. Reduce cloud spend.',
|
||||
})}
|
||||
</h2>
|
||||
}
|
||||
layout="horizontal"
|
||||
color="plain"
|
||||
hasBorder
|
||||
hasShadow={false}
|
||||
body={
|
||||
<>
|
||||
<p>
|
||||
{i18n.translate('xpack.observabilityShared.profilingEmptyState.body', {
|
||||
defaultMessage:
|
||||
'Elastic Universal Profiling is a whole-system, always-on, continuous profiling solution that eliminates the need for code instrumentation, recompilation, on-host debug symbols, or service restarts. Leveraging eBPF, Universal Profiling operates within the Linux kernel space, capturing only the needed data with minimal overhead in an unobtrusive manner.',
|
||||
})}
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
actions={[
|
||||
<EuiButton
|
||||
href={services.http?.basePath.prepend(`/app/profiling`)}
|
||||
data-test-subj="infraProfilingEmptyStateAddProfilingButton"
|
||||
color="primary"
|
||||
fill
|
||||
>
|
||||
{i18n.translate('xpack.observabilityShared.profilingEmptyState.addProfiling', {
|
||||
defaultMessage: 'Add profiling',
|
||||
})}
|
||||
</EuiButton>,
|
||||
<EuiLink
|
||||
href={`${services.docLinks?.ELASTIC_WEBSITE_URL}/guide/en/observability/${services.docLinks?.DOC_LINK_VERSION}/profiling-get-started.html`}
|
||||
data-test-subj="infraProfilingEmptyStateGoToDocsButton"
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
{i18n.translate('xpack.observabilityShared.profilingEmptyState.goToDocs', {
|
||||
defaultMessage: 'Go to docs',
|
||||
})}
|
||||
</EuiLink>,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
BIN
x-pack/plugins/observability_shared/public/images/profiling.png
Normal file
BIN
x-pack/plugins/observability_shared/public/images/profiling.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
|
@ -88,3 +88,5 @@ export {
|
|||
EmbeddableProfilingSearchBar,
|
||||
type EmbeddableProfilingSearchBarProps,
|
||||
} from './components/profiling/embeddables';
|
||||
|
||||
export { ProfilingEmptyState } from './components/profiling/profiling_empty_state';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue