[Logs onboarding] Install system integration when onboarding system logs (#163794)

Closes https://github.com/elastic/kibana/issues/163244.

### Changes
- Use `@kbn/use-tracked-promise` to fetch data from fleet apis, couldn't
use the `useFetcher` hook because it's tailoring internal routes in the
plugin;
- `useInstallSystemIntegration ` is a new hook responsible for
installing the system integration.
- `system-integration-banner` component have been created to hold all
the logic related to system integration.

#### After this changes

##### When integration is not installed


4162319f-35d3-42d3-bd4d-821d1da26a8b

##### When integration is already installed


5f1bf76e-7ed4-4f2c-ba4c-a8b2f3ff80a2

If a user doesn't have the required privileges to install the
integrations they can still continue with the onboarding process but
will see the following message in the UI. After the onboarding is
finished they will be redirected to discover using dataset names
`system.auth` and `system.syslog`.

<img width="2055" alt="image"
src="8214b761-1712-4c7b-888a-394e68ded59f">

### How to test?

- Enter the [custom
deployment](https://yngrdyn-deploy-kiban-pr163794.kb.us-west2.gcp.elastic-cloud.com/)
- Check [installed
integrations](https://yngrdyn-deploy-kiban-pr163794.kb.us-west2.gcp.elastic-cloud.com/app/integrations/installed)
`/app/integrations/installed`
- Go to observability onboarding [landing
page](https://yngrdyn-deploy-kiban-pr163794.kb.us-west2.gcp.elastic-cloud.com/app/observabilityOnboarding):
`app/observabilityOnboarding`
- Select `Stream host system logs` or `Quickstart`
- System integration should install (if it's not installed in the
deployment) or just notify that has been installed (if it's already
installed in the deployment)
- After entering `Stream host system logs` page System integration
should be installed
This commit is contained in:
Yngrid Coello 2023-08-14 17:08:22 +02:00 committed by GitHub
parent fb1cf79a90
commit 10102cce60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 332 additions and 0 deletions

View file

@ -37,6 +37,7 @@ import {
} from '../../shared/step_panel';
import { ApiKeyBanner } from '../custom_logs/wizard/api_key_banner';
import { getDiscoverNavigationParams } from '../utils';
import { SystemIntegrationBanner } from './system_integration_banner';
import { TroubleshootingLink } from '../../shared/troubleshooting_link';
export function InstallElasticAgent() {
@ -226,6 +227,8 @@ export function InstallElasticAgent() {
</p>
</EuiText>
<EuiSpacer size="m" />
<SystemIntegrationBanner />
<EuiSpacer size="m" />
{apiKeyEncoded && onboardingId ? (
<ApiKeyBanner
payload={{ apiKeyEncoded, onboardingId }}

View file

@ -0,0 +1,169 @@
/*
* 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,
EuiFlexGroup,
EuiFlexItem,
EuiLink,
EuiLoadingSpinner,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useCallback, useEffect, useState } from 'react';
import type { MouseEvent } from 'react';
import {
SystemIntegrationError,
useInstallSystemIntegration,
} from '../../../hooks/use_install_system_integration';
import { useKibanaNavigation } from '../../../hooks/use_kibana_navigation';
import { PopoverTooltip } from '../../shared/popover_tooltip';
export function SystemIntegrationBanner() {
const { navigateToAppUrl } = useKibanaNavigation();
const [integrationVersion, setIntegrationVersion] = useState<string>();
const [error, setError] = useState<SystemIntegrationError>();
const onIntegrationCreationSuccess = useCallback(
({ version }: { version?: string }) => {
setIntegrationVersion(version);
},
[]
);
const onIntegrationCreationFailure = useCallback(
(e: SystemIntegrationError) => {
setError(e);
},
[]
);
const { performRequest, requestState } = useInstallSystemIntegration({
onIntegrationCreationSuccess,
onIntegrationCreationFailure,
});
useEffect(() => {
performRequest();
}, [performRequest]);
const isInstallingIntegration = requestState.state === 'pending';
const hasFailedInstallingIntegration = requestState.state === 'rejected';
const hasInstalledIntegration = requestState.state === 'resolved';
if (isInstallingIntegration) {
return (
<EuiCallOut
title={
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="m" />
</EuiFlexItem>
<EuiFlexItem>
{i18n.translate(
'xpack.observability_onboarding.systemIntegration.installing',
{
defaultMessage: 'Installing system integration',
}
)}
</EuiFlexItem>
</EuiFlexGroup>
}
color="primary"
/>
);
}
if (hasFailedInstallingIntegration) {
return (
<EuiFlexItem>
<EuiCallOut
title={i18n.translate(
'xpack.observability_onboarding.systemIntegration.status.failed',
{
defaultMessage: 'System integration installation failed',
}
)}
color="warning"
iconType="warning"
>
{error?.message}
</EuiCallOut>
</EuiFlexItem>
);
}
if (hasInstalledIntegration) {
return (
<EuiFlexItem>
<EuiCallOut
title={
<FormattedMessage
id="xpack.observability_onboarding.systemIntegration.installed"
defaultMessage="System integration installed. {systemIntegrationTooltip}"
values={{
systemIntegrationTooltip: (
<PopoverTooltip
ariaLabel={i18n.translate(
'xpack.observability_onboarding.systemIntegration.installed.tooltip.label',
{
defaultMessage: 'Integration details',
}
)}
>
<EuiFlexGroup direction="column" gutterSize="xs">
<EuiFlexItem>
{i18n.translate(
'xpack.observability_onboarding.systemIntegration.installed.tooltip.description',
{
defaultMessage:
'Integrations streamline connecting your data to the Elastic Stack.',
}
)}
</EuiFlexItem>
<EuiFlexItem
style={{ flexDirection: 'row', alignItems: 'center' }}
>
<FormattedMessage
id="xpack.observability_onboarding.systemIntegration.installed.tooltip.link"
defaultMessage="{learnMoreLink} about the data you can collect using the Systems integration."
values={{
learnMoreLink: (
<EuiLink
data-test-subj="observabilityOnboardingSystemIntegrationLearnMore"
target="_blank"
style={{ marginRight: '3px' }}
onClick={(event: MouseEvent) => {
event.preventDefault();
navigateToAppUrl(
`/integrations/detail/system-${integrationVersion}`
);
}}
>
{i18n.translate(
'xpack.observability_onboarding.systemIntegration.installed.tooltip.link.label',
{
defaultMessage: 'Learn more',
}
)}
</EuiLink>
),
}}
/>
</EuiFlexItem>
</EuiFlexGroup>
</PopoverTooltip>
),
}}
/>
}
color="success"
iconType="check"
/>
</EuiFlexItem>
);
}
return null;
}

View file

@ -0,0 +1,50 @@
/*
* 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 { EuiButtonIcon, EuiPopover, EuiPopoverTitle } from '@elastic/eui';
import React, { useState } from 'react';
interface PopoverTooltipProps {
ariaLabel?: string;
iconType?: string;
title?: string;
children: React.ReactNode;
}
export function PopoverTooltip({
ariaLabel,
iconType = 'iInCircle',
title,
children,
}: PopoverTooltipProps) {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
return (
<EuiPopover
panelPaddingSize="s"
anchorPosition={'upCenter'}
isOpen={isPopoverOpen}
closePopover={() => setIsPopoverOpen(false)}
style={{ margin: '-5px 0 0 -5px' }}
button={
<EuiButtonIcon
aria-label={ariaLabel}
onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
setIsPopoverOpen(!isPopoverOpen);
event.stopPropagation();
}}
size="xs"
color="primary"
iconType={iconType}
/>
}
>
{title && <EuiPopoverTitle>{title}</EuiPopoverTitle>}
{children}
</EuiPopover>
);
}

View file

@ -0,0 +1,91 @@
/*
* 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 { useCallback } from 'react';
import { useTrackedPromise } from '@kbn/use-tracked-promise';
import { i18n } from '@kbn/i18n';
import { useKibana } from './use_kibana';
// Errors
const UNAUTHORIZED_ERROR = i18n.translate(
'xpack.observability_onboarding.installSystemIntegration.error.unauthorized',
{
defaultMessage:
'Required kibana privilege {requiredKibanaPrivileges} is missing, please add the required privilege to the role of the authenticated user.',
values: {
requiredKibanaPrivileges: "['Fleet', 'Integrations']",
},
}
);
type ErrorType = 'AuthorizationError' | 'UnknownError';
export interface SystemIntegrationError {
type: ErrorType;
message: string;
}
type IntegrationInstallStatus =
| 'installed'
| 'installing'
| 'install_failed'
| 'not_installed';
export const useInstallSystemIntegration = ({
onIntegrationCreationSuccess,
onIntegrationCreationFailure,
}: {
onIntegrationCreationSuccess: ({ version }: { version?: string }) => void;
onIntegrationCreationFailure: (error: SystemIntegrationError) => void;
}) => {
const {
services: { http },
} = useKibana();
const [requestState, callPerformRequest] = useTrackedPromise(
{
cancelPreviousOn: 'creation',
createPromise: async () => {
const { item: systemIntegration } = await http.get<{
item: { version: string; status: IntegrationInstallStatus };
}>('/api/fleet/epm/packages/system');
if (systemIntegration.status !== 'installed') {
await http.post('/api/fleet/epm/packages/system');
}
return {
version: systemIntegration.version,
};
},
onResolve: ({ version }: { version?: string }) => {
onIntegrationCreationSuccess({ version });
},
onReject: (requestError: any) => {
if (requestError?.body?.statusCode === 403) {
onIntegrationCreationFailure({
type: 'AuthorizationError' as const,
message: UNAUTHORIZED_ERROR,
});
} else {
onIntegrationCreationFailure({
type: 'UnknownError' as const,
message: requestError?.body?.message,
});
}
},
},
[onIntegrationCreationSuccess, onIntegrationCreationFailure]
);
const performRequest = useCallback(() => {
callPerformRequest();
}, [callPerformRequest]);
return {
performRequest,
requestState,
};
};

View file

@ -0,0 +1,19 @@
/*
* 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 { CoreStart } from '@kbn/core/public';
import {
context as KibanaContext,
KibanaContextProvider,
useKibana,
} from '@kbn/kibana-react-plugin/public';
export type Services = CoreStart;
const useTypedKibana = () => useKibana<Services>();
export { KibanaContextProvider, useTypedKibana as useKibana, KibanaContext };