[EPM] Update UI to handle package versions and updates (#64689)
* 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
This commit is contained in:
parent
3adab85138
commit
53ff22997a
|
@ -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: [],
|
||||
|
|
|
@ -204,6 +204,8 @@ export interface RegistryVarsEntry {
|
|||
// internal until we need them
|
||||
interface PackageAdditions {
|
||||
title: string;
|
||||
latestVersion: string;
|
||||
installedVersion?: string;
|
||||
assets: AssetsGroupedByServiceByType;
|
||||
}
|
||||
|
||||
|
|
|
@ -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" />;
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
notifications.toasts.addSuccess({
|
||||
title: toMountPoint(
|
||||
<FormattedMessage
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
|
|
@ -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 (
|
||||
|
@ -60,6 +60,7 @@ export function ContentPanel(props: ContentPanelProps) {
|
|||
assets={assets}
|
||||
title={title}
|
||||
removable={removable}
|
||||
latestVersion={latestVersion}
|
||||
/>
|
||||
);
|
||||
case 'data-sources':
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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 */
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
|
|
@ -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 || '' });
|
||||
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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'}
|
||||
|
@ -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;
|
||||
|
|
|
@ -8,11 +8,22 @@ import React from 'react';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { InstallStatus, PackageInfo } from '../../../../types';
|
||||
import { useGetDatasources } from '../../../../hooks';
|
||||
import { DATASOURCE_SAVED_OBJECT_TYPE } from '../../../../constants';
|
||||
import { useGetPackageInstallStatus } from '../../hooks';
|
||||
import { InstallationButton } from './installation_button';
|
||||
import { UpdateIcon } from '../../components/icons';
|
||||
|
||||
const SettingsTitleCell = styled.td`
|
||||
padding-right: ${props => props.theme.eui.spacerSizes.xl};
|
||||
padding-bottom: ${props => props.theme.eui.spacerSizes.m};
|
||||
`;
|
||||
|
||||
const UpdatesAvailableMsgContainer = styled.span`
|
||||
padding-left: ${props => props.theme.eui.spacerSizes.s};
|
||||
`;
|
||||
|
||||
const NoteLabel = () => (
|
||||
<FormattedMessage
|
||||
|
@ -20,18 +31,37 @@ const NoteLabel = () => (
|
|||
defaultMessage="Note:"
|
||||
/>
|
||||
);
|
||||
const UpdatesAvailableMsg = () => (
|
||||
<UpdatesAvailableMsgContainer>
|
||||
<UpdateIcon />
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.versionInfo.updatesAvailable"
|
||||
defaultMessage="Updates are available"
|
||||
/>
|
||||
</UpdatesAvailableMsgContainer>
|
||||
);
|
||||
|
||||
export const SettingsPanel = (
|
||||
props: Pick<PackageInfo, 'assets' | 'name' | 'title' | 'version' | 'removable'>
|
||||
props: Pick<PackageInfo, 'assets' | 'name' | 'title' | 'version' | 'removable' | 'latestVersion'>
|
||||
) => {
|
||||
const { name, title, removable, latestVersion, version } = props;
|
||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||
const { data: datasourcesData } = useGetDatasources({
|
||||
perPage: 0,
|
||||
page: 1,
|
||||
kuery: `${DATASOURCE_SAVED_OBJECT_TYPE}.package.name:${props.name}`,
|
||||
});
|
||||
const { name, title, removable } = props;
|
||||
const packageInstallStatus = getPackageInstallStatus(name);
|
||||
const { status: installationStatus, version: installedVersion } = getPackageInstallStatus(name);
|
||||
const packageHasDatasources = !!datasourcesData?.total;
|
||||
const updateAvailable = installedVersion && installedVersion < latestVersion ? true : false;
|
||||
const isViewingOldPackage = version < latestVersion;
|
||||
// hide install/remove options if the user has version of the package is installed
|
||||
// and this package is out of date or if they do have a version installed but it's not this one
|
||||
const hideInstallOptions =
|
||||
(installationStatus === InstallStatus.notInstalled && isViewingOldPackage) ||
|
||||
(installationStatus === InstallStatus.installed && installedVersion !== version);
|
||||
|
||||
const isUpdating = installationStatus === InstallStatus.installing && installedVersion;
|
||||
return (
|
||||
<EuiText>
|
||||
<EuiTitle>
|
||||
|
@ -43,14 +73,13 @@ export const SettingsPanel = (
|
|||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
{packageInstallStatus === InstallStatus.notInstalled ||
|
||||
packageInstallStatus === InstallStatus.installing ? (
|
||||
{installedVersion !== null && (
|
||||
<div>
|
||||
<EuiTitle>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.packageInstallTitle"
|
||||
defaultMessage="Install {title}"
|
||||
id="xpack.ingestManager.integrations.settings.packageVersionTitle"
|
||||
defaultMessage="{title} version"
|
||||
values={{
|
||||
title,
|
||||
}}
|
||||
|
@ -58,80 +87,143 @@ export const SettingsPanel = (
|
|||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.packageInstallDescription"
|
||||
defaultMessage="Install this integration to setup Kibana and Elasticsearch assets designed for {title} data."
|
||||
values={{
|
||||
title,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<SettingsTitleCell>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.versionInfo.installedVersion"
|
||||
defaultMessage="Installed version"
|
||||
/>
|
||||
</SettingsTitleCell>
|
||||
<td>
|
||||
<EuiTitle size="xs">
|
||||
<span>{installedVersion}</span>
|
||||
</EuiTitle>
|
||||
{updateAvailable && <UpdatesAvailableMsg />}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<SettingsTitleCell>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.versionInfo.latestVersion"
|
||||
defaultMessage="Latest version"
|
||||
/>
|
||||
</SettingsTitleCell>
|
||||
<td>
|
||||
<EuiTitle size="xs">
|
||||
<span>{latestVersion}</span>
|
||||
</EuiTitle>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{updateAvailable && (
|
||||
<p>
|
||||
<InstallationButton
|
||||
{...props}
|
||||
version={latestVersion}
|
||||
disabled={false}
|
||||
isUpdate={true}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
)}
|
||||
{!hideInstallOptions && !isUpdating && (
|
||||
<div>
|
||||
<EuiTitle>
|
||||
<h4>
|
||||
<EuiSpacer size="s" />
|
||||
{installationStatus === InstallStatus.notInstalled ||
|
||||
installationStatus === InstallStatus.installing ? (
|
||||
<div>
|
||||
<EuiTitle>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.packageInstallTitle"
|
||||
defaultMessage="Install {title}"
|
||||
values={{
|
||||
title,
|
||||
}}
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.packageInstallDescription"
|
||||
defaultMessage="Install this integration to setup Kibana and Elasticsearch assets designed for {title} data."
|
||||
values={{
|
||||
title,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<EuiTitle>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.packageUninstallTitle"
|
||||
defaultMessage="Uninstall {title}"
|
||||
values={{
|
||||
title,
|
||||
}}
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.packageUninstallDescription"
|
||||
defaultMessage="Remove Kibana and Elasticsearch assets that were installed by this Integration."
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<p>
|
||||
<InstallationButton
|
||||
{...props}
|
||||
disabled={!datasourcesData || removable === false ? true : packageHasDatasources}
|
||||
/>
|
||||
</p>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{packageHasDatasources && removable === true && (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.packageUninstallTitle"
|
||||
defaultMessage="Uninstall {title}"
|
||||
id="xpack.ingestManager.integrations.settings.packageUninstallNoteDescription.packageUninstallNoteDetail"
|
||||
defaultMessage="{strongNote} {title} cannot be uninstalled because there are active agents that use this integration. To uninstall, remove all {title} data sources from your agent configurations."
|
||||
values={{
|
||||
title,
|
||||
strongNote: (
|
||||
<strong>
|
||||
<NoteLabel />
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.packageUninstallDescription"
|
||||
defaultMessage="Remove Kibana and Elasticsearch assets that were installed by this Integration."
|
||||
/>
|
||||
</p>
|
||||
</p>
|
||||
)}
|
||||
{removable === false && (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.packageUninstallNoteDescription.packageUninstallUninstallableNoteDetail"
|
||||
defaultMessage="{strongNote} The {title} integration is installed by default and cannot be removed."
|
||||
values={{
|
||||
title,
|
||||
strongNote: (
|
||||
<strong>
|
||||
<NoteLabel />
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<p>
|
||||
<InstallationButton
|
||||
{...props}
|
||||
disabled={!datasourcesData || removable === false ? true : packageHasDatasources}
|
||||
/>
|
||||
</p>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{packageHasDatasources && removable === true && (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.packageUninstallNoteDescription.packageUninstallNoteDetail"
|
||||
defaultMessage="{strongNote} {title} cannot be uninstalled because there are active agents that use this integration. To uninstall, remove all {title} data sources from your agent configurations."
|
||||
values={{
|
||||
title,
|
||||
strongNote: (
|
||||
<strong>
|
||||
<NoteLabel />
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
{removable === false && (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.integrations.settings.packageUninstallNoteDescription.packageUninstallUninstallableNoteDetail"
|
||||
defaultMessage="{strongNote} The {title} integration is installed by default and cannot be removed."
|
||||
values={{
|
||||
title,
|
||||
strongNote: (
|
||||
<strong>
|
||||
<NoteLabel />
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</EuiText>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -37,7 +37,7 @@ export function SideNavLinks({ name, version, active }: NavLinkProps) {
|
|||
: p.theme.eui.euiFontWeightRegular};
|
||||
`;
|
||||
// don't display Data Sources tab if the package is not installed
|
||||
if (packageInstallStatus !== InstallStatus.installed && panel === 'data-sources')
|
||||
if (packageInstallStatus.status !== InstallStatus.installed && panel === 'data-sources')
|
||||
return null;
|
||||
|
||||
return (
|
||||
|
|
|
@ -67,9 +67,10 @@ export async function getPackageInfo(options: {
|
|||
pkgVersion: string;
|
||||
}): Promise<PackageInfo> {
|
||||
const { savedObjectsClient, pkgName, pkgVersion } = options;
|
||||
const [item, savedObject, assets] = await Promise.all([
|
||||
const [item, savedObject, latestPackage, assets] = await Promise.all([
|
||||
Registry.fetchInfo(pkgName, pkgVersion),
|
||||
getInstallationObject({ savedObjectsClient, pkgName }),
|
||||
Registry.fetchFindLatestPackage(pkgName),
|
||||
Registry.getArchiveInfo(pkgName, pkgVersion),
|
||||
] as const);
|
||||
// adding `as const` due to regression in TS 3.7.2
|
||||
|
@ -79,6 +80,7 @@ export async function getPackageInfo(options: {
|
|||
// add properties that aren't (or aren't yet) on Registry response
|
||||
const updated = {
|
||||
...item,
|
||||
latestVersion: latestPackage.version,
|
||||
title: item.title || nameAsTitle(item.name),
|
||||
assets: Registry.groupPathsByService(assets || []),
|
||||
};
|
||||
|
|
|
@ -43,6 +43,7 @@ export function createInstallableFrom<T>(
|
|||
? {
|
||||
...from,
|
||||
status: InstallationStatus.installed,
|
||||
installedVersion: savedObject.attributes.version,
|
||||
savedObject,
|
||||
}
|
||||
: {
|
||||
|
|
|
@ -106,7 +106,7 @@ export async function installPackage(options: {
|
|||
try {
|
||||
await deleteKibanaSavedObjectsAssets(savedObjectsClient, installedPkg.attributes.installed);
|
||||
} catch (err) {
|
||||
// some assets may not exist if deleting during a failed update
|
||||
// log these errors, some assets may not exist if deleted during a failed update
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -121,8 +121,12 @@ export async function deleteKibanaSavedObjectsAssets(
|
|||
const deletePromises = installedObjects.map(({ id, type }) => {
|
||||
const assetType = type as AssetType;
|
||||
if (savedObjectTypes.includes(assetType)) {
|
||||
savedObjectsClient.delete(assetType, id);
|
||||
return savedObjectsClient.delete(assetType, id);
|
||||
}
|
||||
});
|
||||
await Promise.all(deletePromises);
|
||||
try {
|
||||
await Promise.all(deletePromises);
|
||||
} catch (err) {
|
||||
throw new Error('error deleting saved object asset');
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue