Skip to content

Commit

Permalink
[Fleet] Add reinstall button to integration settings page (#135590) (#…
Browse files Browse the repository at this point in the history
…136158)

* Add package reinstall button in settings tab

* Fix uninstall unavailable message

Co-authored-by: Kibana Machine <[email protected]>
(cherry picked from commit e817d5e)

Co-authored-by: Jen Huang <[email protected]>
  • Loading branch information
kibanamachine and jen-huang authored Jul 11, 2022
1 parent 2ac8754 commit a2f538d
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 60 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/types/models/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export enum InstallStatus {
installed = 'installed',
notInstalled = 'not_installed',
installing = 'installing',
reinstalling = 'reinstalling',
uninstalling = 'uninstalling',
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface PackageInstallItem {
}

type InstallPackageProps = Pick<PackageInfo, 'name' | 'version' | 'title'> & {
isReinstall?: boolean;
fromUpdate?: boolean;
};
type SetPackageInstallStatusProps = Pick<PackageInfo, 'name'> & PackageInstallItem;
Expand Down Expand Up @@ -66,12 +67,22 @@ function usePackageInstall({
);

const installPackage = useCallback(
async ({ name, version, title, fromUpdate = false }: InstallPackageProps) => {
async ({
name,
version,
title,
fromUpdate = false,
isReinstall = false,
}: InstallPackageProps) => {
const currStatus = getPackageInstallStatus(name);
const newStatus = { ...currStatus, name, status: InstallStatus.installing };
const newStatus = {
...currStatus,
name,
status: isReinstall ? InstallStatus.reinstalling : InstallStatus.installing,
};
setPackageInstallStatus(newStatus);

const res = await sendInstallPackage(name, version);
const res = await sendInstallPackage(name, version, isReinstall);
if (res.error) {
if (fromUpdate) {
// if there is an error during update, set it back to the previous version
Expand Down Expand Up @@ -107,24 +118,45 @@ function usePackageInstall({
history.push(settingsPath);
}

notifications.toasts.addSuccess({
title: toMountPoint(
<FormattedMessage
id="xpack.fleet.integrations.packageInstallSuccessTitle"
defaultMessage="Installed {title}"
values={{ title }}
/>,
{ theme$ }
),
text: toMountPoint(
<FormattedMessage
id="xpack.fleet.integrations.packageInstallSuccessDescription"
defaultMessage="Successfully installed {title}"
values={{ title }}
/>,
{ theme$ }
),
});
if (isReinstall) {
notifications.toasts.addSuccess({
title: toMountPoint(
<FormattedMessage
id="xpack.fleet.integrations.packageReinstallSuccessTitle"
defaultMessage="Reinstalled {title}"
values={{ title }}
/>,
{ theme$ }
),
text: toMountPoint(
<FormattedMessage
id="xpack.fleet.integrations.packageReinstallSuccessDescription"
defaultMessage="Successfully reinstalled {title}"
values={{ title }}
/>,
{ theme$ }
),
});
} else {
notifications.toasts.addSuccess({
title: toMountPoint(
<FormattedMessage
id="xpack.fleet.integrations.packageInstallSuccessTitle"
defaultMessage="Installed {title}"
values={{ title }}
/>,
{ theme$ }
),
text: toMountPoint(
<FormattedMessage
id="xpack.fleet.integrations.packageInstallSuccessDescription"
defaultMessage="Successfully installed {title}"
values={{ title }}
/>,
{ theme$ }
),
});
}
}
},
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ export function Detail() {
return getPackageInstallStatus(packageInfo.name).status;
}, [packageInfo, getPackageInstallStatus]);

const isInstalled = useMemo(
() =>
packageInstallStatus === InstallStatus.installed ||
packageInstallStatus === InstallStatus.reinstalling,
[packageInstallStatus]
);

const updateAvailable =
packageInfo &&
'savedObject' in packageInfo &&
Expand Down Expand Up @@ -326,7 +333,7 @@ export function Detail() {
</EuiFlexGroup>
),
},
...(packageInstallStatus === 'installed'
...(isInstalled
? [
{ isDivider: true },
{
Expand Down Expand Up @@ -376,15 +383,15 @@ export function Detail() {
[
packageInfo,
updateAvailable,
packageInstallStatus,
isInstalled,
userCanInstallPackages,
getHref,
pkgkey,
integration,
agentPolicyIdFromContext,
handleAddIntegrationPolicyClick,
missingSecurityConfiguration,
integrationInfo?.title,
handleAddIntegrationPolicyClick,
]
);

Expand Down Expand Up @@ -412,7 +419,7 @@ export function Detail() {
},
];

if (canReadIntegrationPolicies && packageInstallStatus === InstallStatus.installed) {
if (canReadIntegrationPolicies && isInstalled) {
tabs.push({
id: 'policies',
name: (
Expand All @@ -430,7 +437,7 @@ export function Detail() {
});
}

if (packageInstallStatus === InstallStatus.installed && (packageInfo.assets || CustomAssets)) {
if (isInstalled && (packageInfo.assets || CustomAssets)) {
tabs.push({
id: 'assets',
name: (
Expand Down Expand Up @@ -491,9 +498,9 @@ export function Detail() {
getHref,
integration,
canReadIntegrationPolicies,
canReadPackageSettings,
packageInstallStatus,
isInstalled,
CustomAssets,
canReadPackageSettings,
showCustomTab,
]);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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 } from '@elastic/eui';
import React, { Fragment, useCallback } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';

import type { PackageInfo } from '../../../../../types';
import { InstallStatus } from '../../../../../types';
import { useAuthz, useGetPackageInstallStatus, useInstallPackage } from '../../../../../hooks';

type ReinstallationButtonProps = Pick<PackageInfo, 'name' | 'title' | 'version'>;
export function ReinstallButton(props: ReinstallationButtonProps) {
const { name, title, version } = props;
const canInstallPackages = useAuthz().integrations.installPackages;
const installPackage = useInstallPackage();
const getPackageInstallStatus = useGetPackageInstallStatus();
const { status: installationStatus } = getPackageInstallStatus(name);

const isReinstalling = installationStatus === InstallStatus.reinstalling;

const handleClickReinstall = useCallback(() => {
installPackage({ name, version, title, isReinstall: true });
}, [installPackage, name, title, version]);

return canInstallPackages ? (
<Fragment>
<EuiButton iconType="refresh" isLoading={isReinstalling} onClick={handleClickReinstall}>
{isReinstalling ? (
<FormattedMessage
id="xpack.fleet.integrations.installPackage.reinstallingPackageButtonLabel"
defaultMessage="Reinstalling {title}"
values={{
title,
}}
/>
) : (
<FormattedMessage
id="xpack.fleet.integrations.installPackage.reinstallPackageButtonLabel"
defaultMessage="Reinstall {title}"
values={{
title,
}}
/>
)}
</EuiButton>
</Fragment>
) : null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
import { KeepPoliciesUpToDateSwitch } from '../components';

import { InstallButton } from './install_button';
import { ReinstallButton } from './reinstall_button';
import { UpdateButton } from './update_button';
import { UninstallButton } from './uninstall_button';

Expand Down Expand Up @@ -344,51 +345,74 @@ export const SettingsPage: React.FC<Props> = memo(({ packageInfo, theme$ }: Prop
</div>
) : (
<>
<div>
<EuiTitle>
<h4>
<FormattedMessage
id="xpack.fleet.integrations.settings.packageUninstallTitle"
defaultMessage="Uninstall"
/>
</h4>
</EuiTitle>
<EuiSpacer size="s" />
<p>
<EuiFlexGroup direction="column" gutterSize="m">
<EuiFlexItem>
<EuiTitle>
<h4>
<FormattedMessage
id="xpack.fleet.integrations.settings.packageUninstallTitle"
defaultMessage="Uninstall"
/>
</h4>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem>
<FormattedMessage
id="xpack.fleet.integrations.settings.packageUninstallDescription"
defaultMessage="Remove Kibana and Elasticsearch assets that were installed by this integration."
/>
</p>
</div>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<p>
</EuiFlexItem>
<EuiFlexItem>
<div>
<UninstallButton
{...packageInfo}
numOfAssets={numOfAssets}
latestVersion={latestVersion}
disabled={!packagePoliciesData || packageHasUsages}
/>
</p>
</div>
</EuiFlexItem>
{packageHasUsages && (
<EuiFlexItem>
<EuiText color="subdued" size="s">
<FormattedMessage
id="xpack.fleet.integrations.settings.packageUninstallNoteDescription.packageUninstallNoteDetail"
defaultMessage="{strongNote} {title} cannot be uninstalled because there are active agents that use this integration. To uninstall, remove all {title} integrations from your agent policies."
values={{
title,
strongNote: <NoteLabel />,
}}
/>
</EuiText>
</EuiFlexItem>
)}
</EuiFlexGroup>
<EuiSpacer size="l" />
<EuiFlexGroup direction="column" gutterSize="m">
<EuiFlexItem>
<EuiTitle>
<h4>
<FormattedMessage
id="xpack.fleet.integrations.settings.packageReinstallTitle"
defaultMessage="Reinstall"
/>
</h4>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem>
<FormattedMessage
id="xpack.fleet.integrations.settings.packageReinstallDescription"
defaultMessage="Reinstall Kibana and Elasticsearch assets for this integration."
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div>
<ReinstallButton {...packageInfo} />
</div>
</EuiFlexItem>
</EuiFlexGroup>
</>
)}
{packageHasUsages && (
<p>
<EuiText color="subdued">
<FormattedMessage
id="xpack.fleet.integrations.settings.packageUninstallNoteDescription.packageUninstallNoteDetail"
defaultMessage="{strongNote} {title} cannot be uninstalled because there are active agents that use this integration. To uninstall, remove all {title} integrations from your agent policies."
values={{
title,
strongNote: <NoteLabel />,
}}
/>
</EuiText>
</p>
)}
</div>
)}
{hideInstallOptions && isViewingOldPackage && !isUpdating && (
Expand Down
5 changes: 4 additions & 1 deletion x-pack/plugins/fleet/public/hooks/use_request/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,13 @@ export const sendGetFileByPath = (filePath: string) => {
});
};

export const sendInstallPackage = (pkgName: string, pkgVersion: string) => {
export const sendInstallPackage = (pkgName: string, pkgVersion: string, force: boolean = false) => {
return sendRequest<InstallPackageResponse>({
path: epmRouteService.getInstallPath(pkgName, pkgVersion),
method: 'post',
body: {
force,
},
});
};

Expand Down

0 comments on commit a2f538d

Please sign in to comment.