mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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 installed4162319f
-35d3-42d3-bd4d-821d1da26a8b ##### When integration is already installed5f1bf76e
-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:
parent
fb1cf79a90
commit
10102cce60
5 changed files with 332 additions and 0 deletions
|
@ -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 }}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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 };
|
Loading…
Add table
Add a link
Reference in a new issue