Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Observability Onboarding] Auto-detect flow #186106

Merged
merged 24 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
63e1321
Add auto-detect onboarding flow
thomheymann Jun 12, 2024
ee37a72
Merge branch 'main' of https://github.com/elastic/kibana into 183362-…
thomheymann Jun 12, 2024
d30158c
Parallelize installation and UI fixes
thomheymann Jun 12, 2024
6524770
Fix accordion id
thomheymann Jun 12, 2024
3c36774
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jun 12, 2024
4fb7baf
Refactor copy to clipboard
thomheymann Jun 13, 2024
22fa187
Merge branch '183362-auto-detect-flow' of https://github.com/thomheym…
thomheymann Jun 13, 2024
4bd2fc5
Fix API key creation and open links in new tab
thomheymann Jun 15, 2024
09a92e9
Merge branch 'main' of https://github.com/elastic/kibana into 183362-…
thomheymann Jun 15, 2024
37dff1a
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jun 15, 2024
b15fbdc
Add links to dashboards
thomheymann Jun 18, 2024
cc18fed
Merge branch 'main' of https://github.com/elastic/kibana into 183362-…
thomheymann Jun 18, 2024
1703e14
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jun 18, 2024
6c76c59
Use EUI copy component
thomheymann Jun 19, 2024
3c58df5
Merge branch '183362-auto-detect-flow' of https://github.com/thomheym…
thomheymann Jun 19, 2024
884fda5
Merge branch 'main' into 183362-auto-detect-flow
flash1293 Jun 20, 2024
0d7b6ca
Add elastic internal origin header
thomheymann Jun 20, 2024
d48d2f8
Merge branch '183362-auto-detect-flow' of https://github.com/thomheym…
thomheymann Jun 20, 2024
6537d89
Merge branch 'main' of https://github.com/elastic/kibana into 183362-…
thomheymann Jun 20, 2024
6fdafa7
Hide auto-detect flow
thomheymann Jun 21, 2024
60b21ff
Merge branch 'main' of https://github.com/elastic/kibana into 183362-…
thomheymann Jun 21, 2024
a085986
Add title
thomheymann Jun 21, 2024
17c73f7
Move components to shared
thomheymann Jun 21, 2024
8464513
Merge branch 'main' into 183362-auto-detect-flow
thomheymann Jun 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
{
"type": "plugin",
"id": "@kbn/observability-onboarding-plugin",
"owner": ["@elastic/obs-ux-logs-team", "@elastic/obs-ux-onboarding-team"],
"owner": [
"@elastic/obs-ux-logs-team",
"@elastic/obs-ux-onboarding-team"
],
"plugin": {
"id": "observabilityOnboarding",
"server": true,
"browser": true,
"configPath": ["xpack", "observability_onboarding"],
"configPath": [
"xpack",
"observability_onboarding"
],
"requiredPlugins": [
"data",
"observability",
"observabilityShared",
"discover",
"share",
"fleet"
"fleet",
"security"
],
"optionalPlugins": [
"cloud",
"cloudExperiments",
"usageCollection"
],
"requiredBundles": [
"kibanaReact"
],
"optionalPlugins": ["cloud", "cloudExperiments", "usageCollection"],
"requiredBundles": ["kibanaReact"],
"extraPublicDirs": ["common"]
"extraPublicDirs": [
"common"
]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ObservabilityOnboardingHeaderActionMenu } from './shared/header_action_
import {
ObservabilityOnboardingPluginSetupDeps,
ObservabilityOnboardingPluginStartDeps,
ObservabilityOnboardingContextValue,
} from '../plugin';
import { ObservabilityOnboardingFlow } from './observability_onboarding_flow';

Expand All @@ -40,14 +41,13 @@ export const breadcrumbsApp = {
export function ObservabilityOnboardingAppRoot({
appMountParameters,
core,
deps,
corePlugins: { observability, data },
corePlugins,
config,
}: {
appMountParameters: AppMountParameters;
} & RenderAppProps) {
const { history, setHeaderActionMenu, theme$ } = appMountParameters;
const plugins = { ...deps };
const services: ObservabilityOnboardingContextValue = { ...core, ...corePlugins, config };

const renderFeedbackLinkAsPortal = !config.serverless.enabled;

Expand All @@ -63,15 +63,7 @@ export function ObservabilityOnboardingAppRoot({
application: core.application,
}}
>
<KibanaContextProvider
services={{
...core,
...plugins,
observability,
data,
config,
}}
>
<KibanaContextProvider services={services}>
<KibanaThemeProvider
theme={{ theme$ }}
modify={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { OnboardingFlowForm } from './onboarding_flow_form/onboarding_flow_form'
import { Header } from './header/header';
import { SystemLogsPanel } from './quickstart_flows/system_logs';
import { CustomLogsPanel } from './quickstart_flows/custom_logs';
import { AutoDetectPanel } from './quickstart_flows/auto_detect';
import { BackButton } from './shared/back_button';

const queryClient = new QueryClient();
Expand Down Expand Up @@ -52,6 +53,10 @@ export function ObservabilityOnboardingFlow() {
</EuiPageTemplate.Section>
<EuiPageTemplate.Section paddingSize="xl" color="subdued" restrictWidth>
<Routes>
<Route path="/auto-detect">
<BackButton />
<AutoDetectPanel />
</Route>
<Route path="/systemLogs">
<BackButton />
<SystemLogsPanel />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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, { type FunctionComponent } from 'react';
import {
EuiAccordion,
EuiIcon,
EuiTitle,
EuiFlexGroup,
EuiFlexItem,
type EuiAccordionProps,
} from '@elastic/eui';

interface AccordionWithIconProps
extends Omit<EuiAccordionProps, 'buttonContent' | 'buttonProps' | 'borders' | 'paddingSize'> {
title: string;
iconType: string;
}
export const AccordionWithIcon: FunctionComponent<AccordionWithIconProps> = ({
title,
iconType,
children,
...rest
}) => {
return (
<EuiAccordion
{...rest}
buttonContent={
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false} css={{ marginLeft: 8 }}>
<EuiFlexItem grow={false}>
<EuiIcon type={iconType} size="l" />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="xs" css={rest.isDisabled ? { color: 'inherit' } : undefined}>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: better to have isDisabled as a de-structured prop as you use it directly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But then I would also have to manually pass it into accordion. Leaving it inside rest means it will get spread with the rest of the props that are just pass through props from the EuiAccordion.

<h3>{title}</h3>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
}
buttonProps={{ paddingSize: 'l' }}
borders="horizontal"
paddingSize="none"
>
<div css={{ paddingLeft: 36, paddingBottom: 24 }}>{children}</div>
</EuiAccordion>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
/*
* 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, { type FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiPanel,
EuiSteps,
EuiCodeBlock,
EuiSpacer,
EuiSkeletonText,
EuiBadge,
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiImage,
EuiSkeletonRectangle,
useGeneratedHtmlId,
} from '@elastic/eui';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import {
type SingleDatasetLocatorParams,
SINGLE_DATASET_LOCATOR_ID,
} from '@kbn/deeplinks-observability/locators';
import { type DashboardLocatorParams } from '@kbn/dashboard-plugin/public';
import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
import { getAutoDetectCommand } from './get_auto_detect_command';
import { useOnboardingFlow } from './use_onboarding_flow';
import { ProgressIndicator } from './progress_indicator';
import { AccordionWithIcon } from './accordion_with_icon';
import { type ObservabilityOnboardingContextValue } from '../../../plugin';
import { EmptyPrompt } from './empty_prompt';
import { CopyToClipboardButton } from './copy_to_clipboard_button';
import { LocatorButtonEmpty } from './locator_button_empty';

export const AutoDetectPanel: FunctionComponent = () => {
const {
services: { http },
} = useKibana<ObservabilityOnboardingContextValue>();
const { status, data, error, refetch, installedIntegrations } = useOnboardingFlow();
const command = data ? getAutoDetectCommand(data) : undefined;
const accordionId = useGeneratedHtmlId({ prefix: 'accordion' });

if (error) {
return <EmptyPrompt error={error} onRetryClick={refetch} />;
}

const registryIntegrations = installedIntegrations.filter(
(integration) => integration.installSource === 'registry'
);
const customIntegrations = installedIntegrations.filter(
(integration) => integration.installSource === 'custom'
);

return (
<EuiPanel hasBorder paddingSize="xl">
<EuiSteps
steps={[
{
title: i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.runTheCommandOnLabel',
{ defaultMessage: 'Run the command on your host' }
),
status: status === 'notStarted' ? 'current' : 'complete',
children: command ? (
<>
<EuiText>
<p>
{i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.p.wellScanYourHostLabel',
{
defaultMessage: "We'll scan your host for logs and metrics, including:",
}
)}
</p>
</EuiText>
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="s">
{['Apache', 'Docker', 'Nginx', 'System', 'Custom .log files'].map((item) => (
<EuiFlexItem key={item} grow={false}>
<EuiBadge color="hollow">{item}</EuiBadge>
</EuiFlexItem>
))}
</EuiFlexGroup>
<EuiSpacer />
{/* Bash syntax highlighting only highlights a few random numbers (badly) so it looks less messy to go with plain text */}
<EuiCodeBlock paddingSize="m" language="text">
{command}
</EuiCodeBlock>
<EuiSpacer />
<CopyToClipboardButton textToCopy={command} fill={status === 'notStarted'} />
</>
) : (
<EuiSkeletonText lines={6} />
),
},
{
title: i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.visualizeYourDataLabel',
{ defaultMessage: 'Visualize your data' }
),
status:
status === 'dataReceived'
? 'complete'
: status === 'awaitingData' || status === 'inProgress'
? 'current'
: 'incomplete',
children: (
<>
{status === 'dataReceived' ? (
<ProgressIndicator
iconType="cheer"
title={i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.yourDataIsReadyToExploreLabel',
{ defaultMessage: 'Your data is ready to explore!' }
)}
isLoading={false}
/>
) : status === 'awaitingData' ? (
<ProgressIndicator
title={i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.installingElasticAgentFlexItemLabel',
{ defaultMessage: 'Waiting for data to arrive...' }
)}
/>
) : status === 'inProgress' ? (
<ProgressIndicator
title={i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.lookingForLogFilesFlexItemLabel',
{ defaultMessage: 'Waiting for installation to complete...' }
)}
/>
) : null}
{(status === 'awaitingData' || status === 'dataReceived') &&
installedIntegrations.length > 0 ? (
<>
<EuiSpacer />
{registryIntegrations.map((integration) => (
<AccordionWithIcon
key={integration.pkgName}
id={`${accordionId}_${integration.pkgName}`}
iconType="desktop"
title={i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.h3.getStartedWithNginxLabel',
{
defaultMessage: 'Get started with {pkgName} logs',
Copy link
Contributor

Choose a reason for hiding this comment

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

can we use the title of the integration instead of the technical name?

values: { pkgName: integration.pkgName },
}
)}
isDisabled={status !== 'dataReceived'}
initialIsOpen
>
<EuiFlexGroup responsive={false}>
<EuiFlexItem grow={false}>
{status === 'dataReceived' ? (
<EuiImage
src={http.staticAssets.getPluginAssetHref('charts_screen.svg')}
width={162}
height={117}
alt=""
hasShadow
/>
) : (
<EuiSkeletonRectangle width={162} height={117} />
)}
</EuiFlexItem>
<EuiFlexItem>
<ul>
{integration.kibanaAssets
.filter((asset) => asset.type === 'dashboard')
.map((dashboard) => (
Copy link
Contributor

Choose a reason for hiding this comment

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

Not all of them will be relevant (e.g. we don't support windows). I'm aware there is no easy way to tell which ones matter to the current user - if we can't come up with something I think it's not a big issue either

Screenshot 2024-06-21 at 10 46 41

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeh, we need a way of establishing which dashboard is the main one we want to present. Usually there's an "overview" dashboard that can be considered the best entry point so we might be able to do a naive pattern matching.

Ideally there would also be a way of determining whether a dashboard is relevant in the first place (e.g. whether it contains any data) but I'm not sure this can be easily done currently.

I think this panel needs another design iteration where we look at what information is returned from fleet for each of the supported integrations and then how we want to present that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense, could you prepare that discussion for our next sync?

Choose a reason for hiding this comment

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

I agree we should have some logic on which dashboard should be the one we want to show and group the rest on the link to see all of them. As mentioned, usually there is an Overview. In this case we'll need to evaluate host vs system metrics overview dashboards.

<li key={dashboard.id}>
<LocatorButtonEmpty<DashboardLocatorParams>
locator={DASHBOARD_APP_LOCATOR}
params={{ dashboardId: dashboard.id }}
target="_blank"
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's great opening in a new tab! However, when using it I didn't trust it because there's no indication this will happen, should we append a suitable icon to the end?

Copy link
Contributor

Choose a reason for hiding this comment

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

Adds some visual noise as well though, so not 100% sure

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, buttons can only have one icon. It think it would be too confusing otherwise.

We could change the icon to an external link icon on the right hand side but then we would have to remove the current icons

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's discuss with @sileschristian once he's back - no strong opinion on it

Choose a reason for hiding this comment

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

@thomheymann

We could change the icon to an external link icon on the right hand side but then we would have to remove the current icons

This makes sense. Let's do this. Dashboard icon is not bringing that much of value. Also, we are missing a copy that we usually we had before the link that gives better context.

image

iconType="dashboardApp"
isDisabled={status !== 'dataReceived'}
flush="left"
size="s"
>
{dashboard.attributes.title}
</LocatorButtonEmpty>
</li>
))}
</ul>
</EuiFlexItem>
</EuiFlexGroup>
</AccordionWithIcon>
))}
{customIntegrations.length > 0 && (
<AccordionWithIcon
id={`${accordionId}_custom`}
iconType="documents"
title={i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.h3.getStartedWithlogLabel',
{ defaultMessage: 'Get started with custom .log files' }
)}
isDisabled={status !== 'dataReceived'}
initialIsOpen
>
<ul>
{customIntegrations.map((integration) =>
integration.dataStreams.map((datastream) => (
<li key={`${integration.pkgName}/${datastream.dataset}`}>
<LocatorButtonEmpty<SingleDatasetLocatorParams>
locator={SINGLE_DATASET_LOCATOR_ID}
params={{
integration: integration.pkgName,
dataset: datastream.dataset,
}}
target="_blank"
iconType="document"
isDisabled={status !== 'dataReceived'}
flush="left"
size="s"
>
{integration.pkgName}
</LocatorButtonEmpty>
</li>
))
)}
</ul>
</AccordionWithIcon>
)}
</>
) : null}
</>
),
},
]}
/>
</EuiPanel>
);
};
Loading