Skip to content

Commit

Permalink
[Profiling] Empty state (elastic#172295)
Browse files Browse the repository at this point in the history
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="https://github.com/elastic/kibana/assets/55978943/97a313be-db4f-4a5a-af36-df574f9793d5">
<img width="1036" alt="Screenshot 2023-11-30 at 14 47 48"
src="https://github.com/elastic/kibana/assets/55978943/2622cad6-6763-4abc-9469-fa292137efda">
  • Loading branch information
cauemarcondes authored Dec 5, 2023
1 parent a29b0f4 commit 4a308c6
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down Expand Up @@ -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 (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,36 @@
*/

import {
EuiBadge,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingLogo,
EuiPageHeaderProps,
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';
Expand All @@ -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:
Expand Down Expand Up @@ -220,7 +218,6 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) {
plugins,
capabilities
);
const { isProfilingAvailable } = useProfilingPlugin();

const router = useApmRouter();
const isInfraTabAvailable = useApmFeatureFlag(
Expand Down Expand Up @@ -407,7 +404,6 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) {
defaultMessage: 'Universal Profiling',
}),
hidden:
!isProfilingAvailable ||
isRumOrMobileAgentName(agentName) ||
isAWSLambdaAgentName(serverlessType),
append: (
Expand Down
5 changes: 3 additions & 2 deletions x-pack/plugins/apm/public/hooks/use_profiling_plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -16,7 +16,7 @@ export function useProfilingPlugin() {
true
);

const { data } = useFetcher((callApmApi) => {
const { data, status } = useFetcher((callApmApi) => {
return callApmApi('GET /internal/apm/profiling/status');
}, []);

Expand All @@ -30,5 +30,6 @@ export function useProfilingPlugin() {
isProfilingPluginInitialized: data?.initialized,
isProfilingIntegrationEnabled,
isProfilingAvailable,
isLoading: isPending(status),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand All @@ -22,7 +22,6 @@ export const Content = () => {
ContentTabIds.LOGS,
ContentTabIds.METADATA,
ContentTabIds.PROCESSES,
ContentTabIds.PROFILING,
ContentTabIds.ANOMALIES,
]}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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]} />
</>
)}
</>
);
}
2 changes: 1 addition & 1 deletion x-pack/plugins/infra/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
}),
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -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>,
]}
/>
);
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions x-pack/plugins/observability_shared/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,5 @@ export {
EmbeddableProfilingSearchBar,
type EmbeddableProfilingSearchBarProps,
} from './components/profiling/embeddables';

export { ProfilingEmptyState } from './components/profiling/profiling_empty_state';

0 comments on commit 4a308c6

Please sign in to comment.