Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EPM] Update UI to handle package versions and updates #64689

Merged
merged 10 commits into from
Apr 29, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('Ingest Manager - packageToConfig', () => {
name: 'mock-package',
title: 'Mock package',
version: '0.0.0',
latestVersion: '0.0.0',
description: 'description',
type: 'mock',
categories: [],
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/ingest_manager/common/types/models/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ export interface RegistryVarsEntry {
// internal until we need them
interface PackageAdditions {
title: string;
latestVersion: string;
installedVersion?: string;
assets: AssetsGroupedByServiceByType;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiIcon } from '@elastic/eui';
import React from 'react';
import styled from 'styled-components';

export const StyledAlert = styled(EuiIcon)`
color: ${props => props.theme.eui.euiColorWarning};
padding: 0 5px;
`;

export const UpdateIcon = () => <StyledAlert type="alert" size="l" />;
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@ export function PackageCard({
showInstalledBadge,
status,
icons,
...restProps
}: PackageCardProps) {
const { toDetailView } = useLinks();
const url = toDetailView({ name, version });
let urlVersion = version;
// if this is an installed package, link to the version installed
if ('savedObject' in restProps) {
urlVersion = restProps.savedObject.attributes.version || version;
}
const url = toDetailView({ name, version: urlVersion });

return (
<Card
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { NotificationsStart } from 'src/core/public';
import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public';
import { PackageInfo } from '../../../types';
import { sendInstallPackage, sendRemovePackage } from '../../../hooks';
import { useLinks } from '.';
import { InstallStatus } from '../../../types';

interface PackagesInstall {
Expand All @@ -19,31 +20,55 @@ interface PackagesInstall {

interface PackageInstallItem {
status: InstallStatus;
version: string | null;
}

type InstallPackageProps = Pick<PackageInfo, 'name' | 'version' | 'title'>;
type InstallPackageProps = Pick<PackageInfo, 'name' | 'version' | 'title'> & {
fromUpdate?: boolean;
};
type SetPackageInstallStatusProps = Pick<PackageInfo, 'name'> & PackageInstallItem;

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

const setPackageInstallStatus = useCallback(
({ name, status }: { name: PackageInfo['name']; status: InstallStatus }) => {
({ name, status, version }: SetPackageInstallStatusProps) => {
const packageProps: PackageInstallItem = {
status,
version,
};
setPackage((prev: PackagesInstall) => ({
...prev,
[name]: { status },
[name]: packageProps,
}));
},
[]
);

const getPackageInstallStatus = useCallback(
(pkg: string): PackageInstallItem => {
return packages[pkg];
},
[packages]
);

const installPackage = useCallback(
async ({ name, version, title }: InstallPackageProps) => {
setPackageInstallStatus({ name, status: InstallStatus.installing });
async ({ name, version, title, fromUpdate = false }: InstallPackageProps) => {
const currStatus = getPackageInstallStatus(name);
const newStatus = { ...currStatus, name, status: InstallStatus.installing };
setPackageInstallStatus(newStatus);
const pkgkey = `${name}-${version}`;

const res = await sendInstallPackage(pkgkey);
if (res.error) {
setPackageInstallStatus({ name, status: InstallStatus.notInstalled });
if (fromUpdate) {
// if there is an error during update, set it back to the previous version
// as handling of bad update is not implemented yet
setPackageInstallStatus({ ...currStatus, name });
} else {
setPackageInstallStatus({ name, status: InstallStatus.notInstalled, version });
}
notifications.toasts.addWarning({
title: toMountPoint(
<FormattedMessage
Expand All @@ -61,8 +86,15 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar
iconType: 'alert',
});
} else {
setPackageInstallStatus({ name, status: InstallStatus.installed });

setPackageInstallStatus({ name, status: InstallStatus.installed, version });
if (fromUpdate) {
const settingsUrl = toDetailView({
name,
version,
panel: 'settings',
});
window.location.href = settingsUrl;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use useHistory or something besides window here, but this isn't the only place we do this so not a blocker.

}
notifications.toasts.addSuccess({
title: toMountPoint(
<FormattedMessage
Expand All @@ -81,24 +113,17 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar
});
}
},
[notifications.toasts, setPackageInstallStatus]
);

const getPackageInstallStatus = useCallback(
(pkg: string): InstallStatus => {
return packages[pkg].status;
},
[packages]
[getPackageInstallStatus, notifications.toasts, setPackageInstallStatus, toDetailView]
);

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

const res = await sendRemovePackage(pkgkey);
if (res.error) {
setPackageInstallStatus({ name, status: InstallStatus.installed });
setPackageInstallStatus({ name, status: InstallStatus.installed, version });
notifications.toasts.addWarning({
title: toMountPoint(
<FormattedMessage
Expand All @@ -116,7 +141,7 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar
iconType: 'alert',
});
} else {
setPackageInstallStatus({ name, status: InstallStatus.notInstalled });
setPackageInstallStatus({ name, status: InstallStatus.notInstalled, version: null });

notifications.toasts.addSuccess({
title: toMountPoint(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function Content(props: ContentProps) {

type ContentPanelProps = PackageInfo & Pick<DetailParams, 'panel'>;
export function ContentPanel(props: ContentPanelProps) {
const { panel, name, version, assets, title, removable } = props;
const { panel, name, version, assets, title, removable, latestVersion } = props;
switch (panel) {
case 'settings':
return (
Expand All @@ -60,6 +60,7 @@ export function ContentPanel(props: ContentPanelProps) {
assets={assets}
title={title}
removable={removable}
latestVersion={latestVersion}
/>
);
case 'data-sources':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const DataSourcesPanel = ({ name, version }: DataSourcesPanelProps) => {
const packageInstallStatus = getPackageInstallStatus(name);
// if they arrive at this page and the package is not installed, send them to overview
// this happens if they arrive with a direct url or they uninstall while on this tab
if (packageInstallStatus !== InstallStatus.installed)
if (packageInstallStatus.status !== InstallStatus.installed)
return (
<Redirect
to={toDetailView({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { EPM_PATH } from '../../../../constants';
import { useCapabilities, useLink } from '../../../../hooks';
import { IconPanel } from '../../components/icon_panel';
import { NavButtonBack } from '../../components/nav_button_back';
import { Version } from '../../components/version';
import { useLinks } from '../../hooks';
import { CenterColumn, LeftColumn, RightColumn } from './layout';
import { UpdateIcon } from '../../components/icons';

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

const StyledVersion = styled(Version)`
font-size: ${props => props.theme.eui.euiFontSizeS};
color: ${props => props.theme.eui.euiColorDarkShade};
`;

type HeaderProps = PackageInfo & { iconType?: IconType };

export function Header(props: HeaderProps) {
const { iconType, name, title, version } = props;
const { iconType, name, title, version, installedVersion, latestVersion } = props;
const hasWriteCapabilites = useCapabilities().write;
const { toListView } = useLinks();
const ADD_DATASOURCE_URI = useLink(`${EPM_PATH}/${name}-${version}/add-datasource`);

const updateAvailable = installedVersion && installedVersion < latestVersion ? true : false;
return (
<Fragment>
<FullWidthNavRow>
Expand All @@ -59,7 +54,11 @@ export function Header(props: HeaderProps) {
<EuiTitle size="l">
<h1>
<Text>{title}</Text>
<StyledVersion version={version} />
<EuiTitle size="xs">
<span>
{version} {updateAvailable && <UpdateIcon />}
</span>
</EuiTitle>
</h1>
</EuiTitle>
</CenterColumn>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ export function Detail() {
const packageInfo = response.data?.response;
const title = packageInfo?.title;
const name = packageInfo?.name;
const installedVersion = packageInfo?.installedVersion;
const status: InstallStatus = packageInfo?.status as any;

// track install status state
if (name) {
setPackageInstallStatus({ name, status });
setPackageInstallStatus({ name, status, version: installedVersion || null });
}
if (packageInfo) {
setInfo({ ...packageInfo, title: title || '' });
Expand Down Expand Up @@ -64,7 +65,6 @@ type LayoutProps = PackageInfo & Pick<DetailParams, 'panel'> & Pick<EuiPageProps
export function DetailLayout(props: LayoutProps) {
const { name: packageName, version, icons, restrictWidth } = props;
const iconType = usePackageIconType({ packageName, version, icons });

return (
<Fragment>
<FullWidthHeader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,21 @@ import { ConfirmPackageUninstall } from './confirm_package_uninstall';
import { ConfirmPackageInstall } from './confirm_package_install';

type InstallationButtonProps = Pick<PackageInfo, 'assets' | 'name' | 'title' | 'version'> & {
disabled: boolean;
disabled?: boolean;
isUpdate?: boolean;
};
export function InstallationButton(props: InstallationButtonProps) {
const { assets, name, title, version, disabled = true } = props;
const { assets, name, title, version, disabled = true, isUpdate = false } = props;
const hasWriteCapabilites = useCapabilities().write;
const installPackage = useInstallPackage();
const uninstallPackage = useUninstallPackage();
const getPackageInstallStatus = useGetPackageInstallStatus();
const installationStatus = getPackageInstallStatus(name);
const { status: installationStatus } = getPackageInstallStatus(name);

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

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

const handleClickUninstall = useCallback(() => {
uninstallPackage({ name, version, title });
toggleModal();
Expand Down Expand Up @@ -78,6 +84,15 @@ export function InstallationButton(props: InstallationButtonProps) {
</EuiButton>
);

const updateButton = (
<EuiButton iconType={'refresh'} isLoading={isInstalling} onClick={handleClickUpdate}>
<FormattedMessage
id="xpack.ingestManager.integrations.updatePackage.updatePackageButtonLabel"
defaultMessage="Update to latest version"
/>
</EuiButton>
);

const uninstallButton = (
<EuiButton
iconType={'trash'}
Expand Down Expand Up @@ -129,7 +144,7 @@ export function InstallationButton(props: InstallationButtonProps) {

return hasWriteCapabilites ? (
<Fragment>
{isInstalled || isRemoving ? uninstallButton : installButton}
{isUpdate ? updateButton : showUninstallButton ? uninstallButton : installButton}
{isModalVisible && (isInstalled ? uninstallModal : installModal)}
</Fragment>
) : null;
Expand Down
Loading