Skip to content

Commit

Permalink
[Observability Onboarding] Change CTA for System integration in Auto …
Browse files Browse the repository at this point in the history
…Detect (elastic#197836)

Closes elastic/observability-dev#4053 🔒

* Adds an option to specify metadata for integrations installed from
registry as a third parameter in the TSV provided to the
`/integrations/install` endpoint. For now only `system` integration has
metadata with a hostname, but it's made generic to support other
integrations when needed.
* Changes CTA for the System integration to point to the Host details
* Adds sorting in the detected integrations in the UI to alway show
System integration at the top
  • Loading branch information
mykolaharmash authored Oct 30, 2024
1 parent 731c5a4 commit 0ee9684
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from '@kbn/deeplinks-observability/locators';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
import { ASSET_DETAILS_LOCATOR_ID } from '@kbn/observability-shared-plugin/common';
import { getAutoDetectCommand } from './get_auto_detect_command';
import { DASHBOARDS, useOnboardingFlow } from './use_onboarding_flow';
import { ProgressIndicator } from '../shared/progress_indicator';
Expand Down Expand Up @@ -66,6 +67,7 @@ export const AutoDetectPanel: FunctionComponent = () => {
(integration) => integration.installSource === 'custom'
);
const dashboardLocator = share.url.locators.get(DASHBOARD_APP_LOCATOR);
const assetDetailsLocator = share.url.locators.get(ASSET_DETAILS_LOCATOR_ID);

return (
<EuiPanel hasBorder paddingSize="xl">
Expand Down Expand Up @@ -147,88 +149,133 @@ export const AutoDetectPanel: FunctionComponent = () => {
installedIntegrations.length > 0 ? (
<>
<EuiSpacer />
{registryIntegrations.map((integration) => (
<AccordionWithIcon
key={integration.pkgName}
id={`${accordionId}_${integration.pkgName}`}
icon={
isSupportedLogo(integration.pkgName) ? (
<LogoIcon size="l" logo={integration.pkgName} />
) : (
<EuiIcon type="desktop" size="l" />
)
}
title={i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.h3.getStartedWithNginxLabel',
{
defaultMessage: 'Get started with {title}',
values: { title: integration.title },
}
)}
isDisabled={status !== 'dataReceived'}
initialIsOpen
>
<GetStartedPanel
onboardingFlowType="auto-detect"
dataset={integration.pkgName}
onboardingId={data?.onboardingFlow?.id}
telemetryEventContext={{
autoDetect: {
installSource: integration.installSource,
pkgVersion: integration.pkgVersion,
title: integration.title,
},
}}
integration={integration.pkgName}
newTab
isLoading={status !== 'dataReceived'}
actionLinks={integration.kibanaAssets
.filter((asset) => asset.type === 'dashboard')
.map((asset) => {
const dashboard = DASHBOARDS[asset.id as keyof typeof DASHBOARDS];
const href =
dashboardLocator?.getRedirectUrl({
dashboardId: asset.id,
}) ?? '';
{registryIntegrations
.slice()
/**
* System integration should always be on top
*/
.sort((a, b) => (a.pkgName === 'system' ? -1 : 0))
.map((integration) => {
let actionLinks;

return {
id: asset.id,
title:
dashboard.type === 'metrics'
? i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.exploreMetricsDataTitle',
{
defaultMessage:
'Overview your metrics data with this pre-made dashboard',
}
)
: i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.exploreLogsDataTitle',
switch (integration.pkgName) {
case 'system':
actionLinks =
assetDetailsLocator !== undefined
? [
{
id: 'inventory-host-details',
title: i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.systemOverviewTitle',
{
defaultMessage:
'Overview your logs data with this pre-made dashboard',
'Overview your system health within the Hosts Inventory',
}
),
label:
dashboard.type === 'metrics'
? i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.exploreMetricsDataLabel',
label: i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.systemOverviewLabel',
{
defaultMessage: 'Explore metrics data',
}
)
: i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.exploreLogsDataLabel',
{
defaultMessage: 'Explore logs data',
}
),
href,
};
})}
/>
</AccordionWithIcon>
))}
href: assetDetailsLocator.getRedirectUrl({
assetType: 'host',
assetId: integration.metadata?.hostname,
}),
},
]
: [];
break;
default:
actionLinks =
dashboardLocator !== undefined
? integration.kibanaAssets
.filter((asset) => asset.type === 'dashboard')
.map((asset) => {
const dashboard =
DASHBOARDS[asset.id as keyof typeof DASHBOARDS];
const href = dashboardLocator.getRedirectUrl({
dashboardId: asset.id,
});

return {
id: asset.id,
title:
dashboard.type === 'metrics'
? i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.exploreMetricsDataTitle',
{
defaultMessage:
'Overview your metrics data with this pre-made dashboard',
}
)
: i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.exploreLogsDataTitle',
{
defaultMessage:
'Overview your logs data with this pre-made dashboard',
}
),
label:
dashboard.type === 'metrics'
? i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.exploreMetricsDataLabel',
{
defaultMessage: 'Explore metrics data',
}
)
: i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.exploreLogsDataLabel',
{
defaultMessage: 'Explore logs data',
}
),
href,
};
})
: [];
}

return (
<AccordionWithIcon
key={integration.pkgName}
id={`${accordionId}_${integration.pkgName}`}
icon={
isSupportedLogo(integration.pkgName) ? (
<LogoIcon size="l" logo={integration.pkgName} />
) : (
<EuiIcon type="desktop" size="l" />
)
}
title={i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.h3.getStartedWithNginxLabel',
{
defaultMessage: 'Get started with {title}',
values: { title: integration.title },
}
)}
isDisabled={status !== 'dataReceived'}
initialIsOpen
>
<GetStartedPanel
onboardingFlowType="auto-detect"
dataset={integration.pkgName}
onboardingId={data?.onboardingFlow?.id}
telemetryEventContext={{
autoDetect: {
installSource: integration.installSource,
pkgVersion: integration.pkgVersion,
title: integration.title,
},
}}
integration={integration.pkgName}
newTab
isLoading={status !== 'dataReceived'}
actionLinks={actionLinks}
/>
</AccordionWithIcon>
);
})}
{customIntegrations.length > 0 && (
<AccordionWithIcon
id={`${accordionId}_custom`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,15 @@ install_integrations() {
local install_integrations_api_body_string=""

for item in "${selected_known_integrations_array[@]}"; do
install_integrations_api_body_string+="$item\tregistry\n"
local metadata=""

case "$item" in
"system")
metadata="\t$(hostname | tr '[:upper:]' '[:lower:]')"
;;
esac

install_integrations_api_body_string+="$item\tregistry$metadata\n"
done

for item in "${selected_unknown_log_file_pattern_array[@]}" "${custom_log_file_path_list_array[@]}"; do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,9 +392,16 @@ const integrationsInstallRoute = createObservabilityOnboardingServerRoute({
},
});

interface InstalledSystemIntegrationMetadata {
hostname: string;
}

type RegistryIntegrationMetadata = InstalledSystemIntegrationMetadata;

export interface RegistryIntegrationToInstall {
pkgName: string;
installSource: 'registry';
metadata?: RegistryIntegrationMetadata;
}
export interface CustomIntegrationToInstall {
pkgName: string;
Expand Down Expand Up @@ -426,6 +433,7 @@ async function ensureInstalledIntegrations(
dataStreams:
packageInfo.data_streams?.map(({ type, dataset }) => ({ type, dataset })) ?? [],
kibanaAssets: pkg.installed_kibana,
metadata: integration.metadata,
};
}

Expand Down Expand Up @@ -482,7 +490,8 @@ async function ensureInstalledIntegrations(
* Example input:
*
* ```text
* system registry
* system registry hostname
* nginx registry
* product_service custom /path/to/access.log
* product_service custom /path/to/error.log
* checkout_service custom /path/to/access.log
Expand All @@ -495,42 +504,55 @@ function parseIntegrationsTSV(tsv: string) {
.trim()
.split('\n')
.map((line) => line.split('\t', 3))
.reduce<Record<string, IntegrationToInstall>>(
(acc, [pkgName, installSource, logFilePath]) => {
const key = `${pkgName}-${installSource}`;
if (installSource === 'registry') {
if (logFilePath) {
throw new Error(`Integration '${pkgName}' does not support a file path`);
}
acc[key] = {
pkgName,
installSource,
};
return acc;
} else if (installSource === 'custom') {
if (!logFilePath) {
throw new Error(`Missing file path for integration: ${pkgName}`);
}
// Append file path if integration is already in the list
const existing = acc[key];
if (existing && existing.installSource === 'custom') {
existing.logFilePaths.push(logFilePath);
return acc;
}
acc[key] = {
pkgName,
installSource,
logFilePaths: [logFilePath],
};
.reduce<Record<string, IntegrationToInstall>>((acc, [pkgName, installSource, parameter]) => {
const key = `${pkgName}-${installSource}`;
if (installSource === 'registry') {
const metadata = parseRegistryIntegrationMetadata(pkgName, parameter);

acc[key] = {
pkgName,
installSource,
metadata,
};
return acc;
} else if (installSource === 'custom') {
if (!parameter) {
throw new Error(`Missing file path for integration: ${pkgName}`);
}
// Append file path if integration is already in the list
const existing = acc[key];
if (existing && existing.installSource === 'custom') {
existing.logFilePaths.push(parameter);
return acc;
}
throw new Error(`Invalid install source: ${installSource}`);
},
{}
)
acc[key] = {
pkgName,
installSource,
logFilePaths: [parameter],
};
return acc;
}
throw new Error(`Invalid install source: ${installSource}`);
}, {})
);
}

function parseRegistryIntegrationMetadata(
pkgName: string,
parameter: string
): RegistryIntegrationMetadata | undefined {
switch (pkgName) {
case 'system':
if (!parameter) {
throw new Error('Missing hostname for System integration');
}

return { hostname: parameter };
default:
return undefined;
}
}

const generateAgentConfigTar = ({
elasticsearchUrl,
installedIntegrations,
Expand Down
Loading

0 comments on commit 0ee9684

Please sign in to comment.