Skip to content

Commit ad5450f

Browse files
[EPM] Update UI to handle package versions and updates (elastic#64689) (elastic#64819)
* link to installed version of detail page * add latestVersion property to EPM get package endpoint * add updates available notices * add update package button * handle various states and send installedVersion from package endpoint * fix type errors * fix install error because not returning promises * track version in state * handle unsuccessful update attempt * remove unused variable Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent 26c7b79 commit ad5450f

File tree

16 files changed

+277
-114
lines changed

16 files changed

+277
-114
lines changed

x-pack/plugins/ingest_manager/common/services/package_to_config.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ describe('Ingest Manager - packageToConfig', () => {
1111
name: 'mock-package',
1212
title: 'Mock package',
1313
version: '0.0.0',
14+
latestVersion: '0.0.0',
1415
description: 'description',
1516
type: 'mock',
1617
categories: [],

x-pack/plugins/ingest_manager/common/types/models/epm.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ export interface RegistryVarsEntry {
204204
// internal until we need them
205205
interface PackageAdditions {
206206
title: string;
207+
latestVersion: string;
208+
installedVersion?: string;
207209
assets: AssetsGroupedByServiceByType;
208210
}
209211

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import { EuiIcon } from '@elastic/eui';
7+
import React from 'react';
8+
import styled from 'styled-components';
9+
10+
export const StyledAlert = styled(EuiIcon)`
11+
color: ${props => props.theme.eui.euiColorWarning};
12+
padding: 0 5px;
13+
`;
14+
15+
export const UpdateIcon = () => <StyledAlert type="alert" size="l" />;

x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_card.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,15 @@ export function PackageCard({
3030
showInstalledBadge,
3131
status,
3232
icons,
33+
...restProps
3334
}: PackageCardProps) {
3435
const { toDetailView } = useLinks();
35-
const url = toDetailView({ name, version });
36+
let urlVersion = version;
37+
// if this is an installed package, link to the version installed
38+
if ('savedObject' in restProps) {
39+
urlVersion = restProps.savedObject.attributes.version || version;
40+
}
41+
const url = toDetailView({ name, version: urlVersion });
3642

3743
return (
3844
<Card

x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_package_install.tsx

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { NotificationsStart } from 'src/core/public';
1111
import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public';
1212
import { PackageInfo } from '../../../types';
1313
import { sendInstallPackage, sendRemovePackage } from '../../../hooks';
14+
import { useLinks } from '.';
1415
import { InstallStatus } from '../../../types';
1516

1617
interface PackagesInstall {
@@ -19,31 +20,55 @@ interface PackagesInstall {
1920

2021
interface PackageInstallItem {
2122
status: InstallStatus;
23+
version: string | null;
2224
}
2325

24-
type InstallPackageProps = Pick<PackageInfo, 'name' | 'version' | 'title'>;
26+
type InstallPackageProps = Pick<PackageInfo, 'name' | 'version' | 'title'> & {
27+
fromUpdate?: boolean;
28+
};
29+
type SetPackageInstallStatusProps = Pick<PackageInfo, 'name'> & PackageInstallItem;
2530

2631
function usePackageInstall({ notifications }: { notifications: NotificationsStart }) {
32+
const { toDetailView } = useLinks();
2733
const [packages, setPackage] = useState<PackagesInstall>({});
2834

2935
const setPackageInstallStatus = useCallback(
30-
({ name, status }: { name: PackageInfo['name']; status: InstallStatus }) => {
36+
({ name, status, version }: SetPackageInstallStatusProps) => {
37+
const packageProps: PackageInstallItem = {
38+
status,
39+
version,
40+
};
3141
setPackage((prev: PackagesInstall) => ({
3242
...prev,
33-
[name]: { status },
43+
[name]: packageProps,
3444
}));
3545
},
3646
[]
3747
);
3848

49+
const getPackageInstallStatus = useCallback(
50+
(pkg: string): PackageInstallItem => {
51+
return packages[pkg];
52+
},
53+
[packages]
54+
);
55+
3956
const installPackage = useCallback(
40-
async ({ name, version, title }: InstallPackageProps) => {
41-
setPackageInstallStatus({ name, status: InstallStatus.installing });
57+
async ({ name, version, title, fromUpdate = false }: InstallPackageProps) => {
58+
const currStatus = getPackageInstallStatus(name);
59+
const newStatus = { ...currStatus, name, status: InstallStatus.installing };
60+
setPackageInstallStatus(newStatus);
4261
const pkgkey = `${name}-${version}`;
4362

4463
const res = await sendInstallPackage(pkgkey);
4564
if (res.error) {
46-
setPackageInstallStatus({ name, status: InstallStatus.notInstalled });
65+
if (fromUpdate) {
66+
// if there is an error during update, set it back to the previous version
67+
// as handling of bad update is not implemented yet
68+
setPackageInstallStatus({ ...currStatus, name });
69+
} else {
70+
setPackageInstallStatus({ name, status: InstallStatus.notInstalled, version });
71+
}
4772
notifications.toasts.addWarning({
4873
title: toMountPoint(
4974
<FormattedMessage
@@ -61,8 +86,15 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar
6186
iconType: 'alert',
6287
});
6388
} else {
64-
setPackageInstallStatus({ name, status: InstallStatus.installed });
65-
89+
setPackageInstallStatus({ name, status: InstallStatus.installed, version });
90+
if (fromUpdate) {
91+
const settingsUrl = toDetailView({
92+
name,
93+
version,
94+
panel: 'settings',
95+
});
96+
window.location.href = settingsUrl;
97+
}
6698
notifications.toasts.addSuccess({
6799
title: toMountPoint(
68100
<FormattedMessage
@@ -81,24 +113,17 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar
81113
});
82114
}
83115
},
84-
[notifications.toasts, setPackageInstallStatus]
85-
);
86-
87-
const getPackageInstallStatus = useCallback(
88-
(pkg: string): InstallStatus => {
89-
return packages[pkg].status;
90-
},
91-
[packages]
116+
[getPackageInstallStatus, notifications.toasts, setPackageInstallStatus, toDetailView]
92117
);
93118

94119
const uninstallPackage = useCallback(
95120
async ({ name, version, title }: Pick<PackageInfo, 'name' | 'version' | 'title'>) => {
96-
setPackageInstallStatus({ name, status: InstallStatus.uninstalling });
121+
setPackageInstallStatus({ name, status: InstallStatus.uninstalling, version });
97122
const pkgkey = `${name}-${version}`;
98123

99124
const res = await sendRemovePackage(pkgkey);
100125
if (res.error) {
101-
setPackageInstallStatus({ name, status: InstallStatus.installed });
126+
setPackageInstallStatus({ name, status: InstallStatus.installed, version });
102127
notifications.toasts.addWarning({
103128
title: toMountPoint(
104129
<FormattedMessage
@@ -116,7 +141,7 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar
116141
iconType: 'alert',
117142
});
118143
} else {
119-
setPackageInstallStatus({ name, status: InstallStatus.notInstalled });
144+
setPackageInstallStatus({ name, status: InstallStatus.notInstalled, version: null });
120145

121146
notifications.toasts.addSuccess({
122147
title: toMountPoint(

x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function Content(props: ContentProps) {
5050

5151
type ContentPanelProps = PackageInfo & Pick<DetailParams, 'panel'>;
5252
export function ContentPanel(props: ContentPanelProps) {
53-
const { panel, name, version, assets, title, removable } = props;
53+
const { panel, name, version, assets, title, removable, latestVersion } = props;
5454
switch (panel) {
5555
case 'settings':
5656
return (
@@ -60,6 +60,7 @@ export function ContentPanel(props: ContentPanelProps) {
6060
assets={assets}
6161
title={title}
6262
removable={removable}
63+
latestVersion={latestVersion}
6364
/>
6465
);
6566
case 'data-sources':

x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/data_sources_panel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const DataSourcesPanel = ({ name, version }: DataSourcesPanelProps) => {
2020
const packageInstallStatus = getPackageInstallStatus(name);
2121
// if they arrive at this page and the package is not installed, send them to overview
2222
// this happens if they arrive with a direct url or they uninstall while on this tab
23-
if (packageInstallStatus !== InstallStatus.installed)
23+
if (packageInstallStatus.status !== InstallStatus.installed)
2424
return (
2525
<Redirect
2626
to={toDetailView({

x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import { EPM_PATH } from '../../../../constants';
1313
import { useCapabilities, useLink } from '../../../../hooks';
1414
import { IconPanel } from '../../components/icon_panel';
1515
import { NavButtonBack } from '../../components/nav_button_back';
16-
import { Version } from '../../components/version';
1716
import { useLinks } from '../../hooks';
1817
import { CenterColumn, LeftColumn, RightColumn } from './layout';
18+
import { UpdateIcon } from '../../components/icons';
1919

2020
const FullWidthNavRow = styled(EuiPage)`
2121
/* no left padding so link is against column left edge */
@@ -26,19 +26,14 @@ const Text = styled.span`
2626
margin-right: ${props => props.theme.eui.euiSizeM};
2727
`;
2828

29-
const StyledVersion = styled(Version)`
30-
font-size: ${props => props.theme.eui.euiFontSizeS};
31-
color: ${props => props.theme.eui.euiColorDarkShade};
32-
`;
33-
3429
type HeaderProps = PackageInfo & { iconType?: IconType };
3530

3631
export function Header(props: HeaderProps) {
37-
const { iconType, name, title, version } = props;
32+
const { iconType, name, title, version, installedVersion, latestVersion } = props;
3833
const hasWriteCapabilites = useCapabilities().write;
3934
const { toListView } = useLinks();
4035
const ADD_DATASOURCE_URI = useLink(`${EPM_PATH}/${name}-${version}/add-datasource`);
41-
36+
const updateAvailable = installedVersion && installedVersion < latestVersion ? true : false;
4237
return (
4338
<Fragment>
4439
<FullWidthNavRow>
@@ -59,7 +54,11 @@ export function Header(props: HeaderProps) {
5954
<EuiTitle size="l">
6055
<h1>
6156
<Text>{title}</Text>
62-
<StyledVersion version={version} />
57+
<EuiTitle size="xs">
58+
<span>
59+
{version} {updateAvailable && <UpdateIcon />}
60+
</span>
61+
</EuiTitle>
6362
</h1>
6463
</EuiTitle>
6564
</CenterColumn>

x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ export function Detail() {
3232
const packageInfo = response.data?.response;
3333
const title = packageInfo?.title;
3434
const name = packageInfo?.name;
35+
const installedVersion = packageInfo?.installedVersion;
3536
const status: InstallStatus = packageInfo?.status as any;
3637

3738
// track install status state
3839
if (name) {
39-
setPackageInstallStatus({ name, status });
40+
setPackageInstallStatus({ name, status, version: installedVersion || null });
4041
}
4142
if (packageInfo) {
4243
setInfo({ ...packageInfo, title: title || '' });
@@ -64,7 +65,6 @@ type LayoutProps = PackageInfo & Pick<DetailParams, 'panel'> & Pick<EuiPageProps
6465
export function DetailLayout(props: LayoutProps) {
6566
const { name: packageName, version, icons, restrictWidth } = props;
6667
const iconType = usePackageIconType({ packageName, version, icons });
67-
6868
return (
6969
<Fragment>
7070
<FullWidthHeader>

x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,21 @@ import { ConfirmPackageUninstall } from './confirm_package_uninstall';
1313
import { ConfirmPackageInstall } from './confirm_package_install';
1414

1515
type InstallationButtonProps = Pick<PackageInfo, 'assets' | 'name' | 'title' | 'version'> & {
16-
disabled: boolean;
16+
disabled?: boolean;
17+
isUpdate?: boolean;
1718
};
1819
export function InstallationButton(props: InstallationButtonProps) {
19-
const { assets, name, title, version, disabled = true } = props;
20+
const { assets, name, title, version, disabled = true, isUpdate = false } = props;
2021
const hasWriteCapabilites = useCapabilities().write;
2122
const installPackage = useInstallPackage();
2223
const uninstallPackage = useUninstallPackage();
2324
const getPackageInstallStatus = useGetPackageInstallStatus();
24-
const installationStatus = getPackageInstallStatus(name);
25+
const { status: installationStatus } = getPackageInstallStatus(name);
2526

2627
const isInstalling = installationStatus === InstallStatus.installing;
2728
const isRemoving = installationStatus === InstallStatus.uninstalling;
2829
const isInstalled = installationStatus === InstallStatus.installed;
30+
const showUninstallButton = isInstalled || isRemoving;
2931
const [isModalVisible, setModalVisible] = useState<boolean>(false);
3032
const toggleModal = useCallback(() => {
3133
setModalVisible(!isModalVisible);
@@ -36,6 +38,10 @@ export function InstallationButton(props: InstallationButtonProps) {
3638
toggleModal();
3739
}, [installPackage, name, title, toggleModal, version]);
3840

41+
const handleClickUpdate = useCallback(() => {
42+
installPackage({ name, version, title, fromUpdate: true });
43+
}, [installPackage, name, title, version]);
44+
3945
const handleClickUninstall = useCallback(() => {
4046
uninstallPackage({ name, version, title });
4147
toggleModal();
@@ -78,6 +84,15 @@ export function InstallationButton(props: InstallationButtonProps) {
7884
</EuiButton>
7985
);
8086

87+
const updateButton = (
88+
<EuiButton iconType={'refresh'} isLoading={isInstalling} onClick={handleClickUpdate}>
89+
<FormattedMessage
90+
id="xpack.ingestManager.integrations.updatePackage.updatePackageButtonLabel"
91+
defaultMessage="Update to latest version"
92+
/>
93+
</EuiButton>
94+
);
95+
8196
const uninstallButton = (
8297
<EuiButton
8398
iconType={'trash'}
@@ -129,7 +144,7 @@ export function InstallationButton(props: InstallationButtonProps) {
129144

130145
return hasWriteCapabilites ? (
131146
<Fragment>
132-
{isInstalled || isRemoving ? uninstallButton : installButton}
147+
{isUpdate ? updateButton : showUninstallButton ? uninstallButton : installButton}
133148
{isModalVisible && (isInstalled ? uninstallModal : installModal)}
134149
</Fragment>
135150
) : null;

0 commit comments

Comments
 (0)