-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Fleet] Display package update errors (#172065)
- Loading branch information
Showing
6 changed files
with
416 additions
and
132 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
...gins/fleet/public/applications/integrations/sections/epm/screens/home/card_utils.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
* 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 React from 'react'; | ||
|
||
import { createIntegrationsTestRendererMock } from '../../../../../../mock'; | ||
import type { PackageListItem } from '../../../../types'; | ||
|
||
import { getIntegrationLabels } from './card_utils'; | ||
|
||
function renderIntegrationLabels(item: Partial<PackageListItem>) { | ||
const renderer = createIntegrationsTestRendererMock(); | ||
|
||
return renderer.render(<>{getIntegrationLabels(item as any)}</>); | ||
} | ||
|
||
describe('Card utils', () => { | ||
describe('getIntegrationLabels', () => { | ||
it('should return an empty list for an integration without errors', () => { | ||
const res = renderIntegrationLabels({ | ||
installationInfo: { | ||
install_status: 'installed', | ||
} as any, | ||
}); | ||
const badges = res.container.querySelectorAll('.euiBadge'); | ||
expect(badges).toHaveLength(0); | ||
}); | ||
|
||
it('should return a badge for install_failed for an integration with status:install_failled', () => { | ||
const res = renderIntegrationLabels({ | ||
installationInfo: { | ||
install_status: 'install_failed', | ||
} as any, | ||
}); | ||
const badges = res.container.querySelectorAll('.euiBadge'); | ||
expect(badges).toHaveLength(1); | ||
expect(res.queryByText('Install failed')).not.toBeNull(); | ||
}); | ||
|
||
it('should return a badge if there is an upgrade failed in the last_attempt_errors', () => { | ||
const res = renderIntegrationLabels({ | ||
installationInfo: { | ||
version: '1.0.0', | ||
install_status: 'installed', | ||
latest_install_failed_attempts: [ | ||
{ | ||
created_at: new Date().toISOString(), | ||
error: { | ||
name: 'Test', | ||
message: 'test error 123', | ||
}, | ||
target_version: '2.0.0', | ||
}, | ||
], | ||
} as any, | ||
}); | ||
const badges = res.container.querySelectorAll('.euiBadge'); | ||
expect(badges).toHaveLength(1); | ||
expect(res.queryByText('Update failed')).not.toBeNull(); | ||
}); | ||
}); | ||
}); |
232 changes: 232 additions & 0 deletions
232
...k/plugins/fleet/public/applications/integrations/sections/epm/screens/home/card_utils.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
/* | ||
* 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 React from 'react'; | ||
import { FormattedMessage, FormattedDate, FormattedTime } from '@kbn/i18n-react'; | ||
import { EuiBadge, EuiFlexItem, EuiSpacer, EuiToolTip } from '@elastic/eui'; | ||
import semverLt from 'semver/functions/lt'; | ||
|
||
import type { | ||
CustomIntegration, | ||
CustomIntegrationIcon, | ||
} from '@kbn/custom-integrations-plugin/common'; | ||
|
||
import { hasDeferredInstallations } from '../../../../../../services/has_deferred_installations'; | ||
import { getPackageReleaseLabel } from '../../../../../../../common/services'; | ||
|
||
import { installationStatuses } from '../../../../../../../common/constants'; | ||
import type { | ||
InstallFailedAttempt, | ||
IntegrationCardReleaseLabel, | ||
PackageSpecIcon, | ||
} from '../../../../../../../common/types'; | ||
|
||
import type { DynamicPage, DynamicPagePathValues, StaticPage } from '../../../../constants'; | ||
import { isPackageUnverified, isPackageUpdatable } from '../../../../services'; | ||
|
||
import type { PackageListItem } from '../../../../types'; | ||
|
||
export interface IntegrationCardItem { | ||
url: string; | ||
release?: IntegrationCardReleaseLabel; | ||
description: string; | ||
name: string; | ||
title: string; | ||
version: string; | ||
icons: Array<PackageSpecIcon | CustomIntegrationIcon>; | ||
integration: string; | ||
id: string; | ||
categories: string[]; | ||
fromIntegrations?: string; | ||
isReauthorizationRequired?: boolean; | ||
isUnverified?: boolean; | ||
isUpdateAvailable?: boolean; | ||
showLabels?: boolean; | ||
extraLabelsBadges?: React.ReactNode[]; | ||
} | ||
|
||
export const mapToCard = ({ | ||
getAbsolutePath, | ||
getHref, | ||
item, | ||
addBasePath, | ||
packageVerificationKeyId, | ||
selectedCategory, | ||
}: { | ||
getAbsolutePath: (p: string) => string; | ||
getHref: (page: StaticPage | DynamicPage, values?: DynamicPagePathValues) => string; | ||
addBasePath: (url: string) => string; | ||
item: CustomIntegration | PackageListItem; | ||
packageVerificationKeyId?: string; | ||
selectedCategory?: string; | ||
}): IntegrationCardItem => { | ||
let uiInternalPathUrl: string; | ||
|
||
let isUnverified = false; | ||
|
||
const version = 'version' in item ? item.version || '' : ''; | ||
|
||
let isUpdateAvailable = false; | ||
let isReauthorizationRequired = false; | ||
if (item.type === 'ui_link') { | ||
uiInternalPathUrl = item.id.includes('language_client.') | ||
? addBasePath(item.uiInternalPath) | ||
: item.uiExternalLink || getAbsolutePath(item.uiInternalPath); | ||
} else { | ||
let urlVersion = item.version; | ||
if (item?.installationInfo?.version) { | ||
urlVersion = item.installationInfo.version || item.version; | ||
isUnverified = isPackageUnverified(item, packageVerificationKeyId); | ||
isUpdateAvailable = isPackageUpdatable(item); | ||
|
||
isReauthorizationRequired = hasDeferredInstallations(item); | ||
} | ||
|
||
const url = getHref('integration_details_overview', { | ||
pkgkey: `${item.name}-${urlVersion}`, | ||
...(item.integration ? { integration: item.integration } : {}), | ||
}); | ||
|
||
uiInternalPathUrl = url; | ||
} | ||
|
||
const release: IntegrationCardReleaseLabel = getPackageReleaseLabel(version); | ||
|
||
let extraLabelsBadges: React.ReactNode[] | undefined; | ||
if (item.type === 'integration') { | ||
extraLabelsBadges = getIntegrationLabels(item); | ||
} | ||
|
||
return { | ||
id: `${item.type === 'ui_link' ? 'ui_link' : 'epr'}:${item.id}`, | ||
description: item.description, | ||
icons: !item.icons || !item.icons.length ? [] : item.icons, | ||
title: item.title, | ||
url: uiInternalPathUrl, | ||
fromIntegrations: selectedCategory, | ||
integration: 'integration' in item ? item.integration || '' : '', | ||
name: 'name' in item ? item.name : item.id, | ||
version, | ||
release, | ||
categories: ((item.categories || []) as string[]).filter((c: string) => !!c), | ||
isReauthorizationRequired, | ||
isUnverified, | ||
isUpdateAvailable, | ||
extraLabelsBadges, | ||
}; | ||
}; | ||
|
||
export function getIntegrationLabels(item: PackageListItem): React.ReactNode[] { | ||
const extraLabelsBadges: React.ReactNode[] = []; | ||
|
||
if ( | ||
item?.installationInfo?.latest_install_failed_attempts?.some( | ||
(attempt) => | ||
item.installationInfo && semverLt(item.installationInfo.version, attempt.target_version) | ||
) | ||
) { | ||
const updateFailedAttempt = item.installationInfo?.latest_install_failed_attempts?.find( | ||
(attempt) => | ||
item.installationInfo && semverLt(item.installationInfo.version, attempt.target_version) | ||
); | ||
extraLabelsBadges.push( | ||
<EuiFlexItem key="update_failed_badge" grow={false}> | ||
<EuiSpacer size="xs" /> | ||
<span> | ||
<EuiToolTip | ||
title={ | ||
<FormattedMessage | ||
id="xpack.fleet.packageCard.updateFailedTooltipTitle" | ||
defaultMessage="Update failed" | ||
/> | ||
} | ||
content={updateFailedAttempt ? formatAttempt(updateFailedAttempt) : undefined} | ||
> | ||
<EuiBadge color="danger" iconType="error"> | ||
<FormattedMessage | ||
id="xpack.fleet.packageCard.updateFailed" | ||
defaultMessage="Update failed" | ||
/> | ||
</EuiBadge> | ||
</EuiToolTip> | ||
</span> | ||
</EuiFlexItem> | ||
); | ||
} | ||
|
||
if (item.installationInfo?.install_status === installationStatuses.InstallFailed) { | ||
const installFailedAttempt = item.installationInfo?.latest_install_failed_attempts?.find( | ||
(attempt) => attempt.target_version === item.installationInfo?.version | ||
); | ||
|
||
extraLabelsBadges.push( | ||
<EuiFlexItem key="install_failed_badge" grow={false}> | ||
<EuiSpacer size="xs" /> | ||
<span> | ||
<EuiToolTip | ||
title={ | ||
<FormattedMessage | ||
id="xpack.fleet.packageCard.installFailedTooltipTitle" | ||
defaultMessage="Install failed" | ||
/> | ||
} | ||
content={installFailedAttempt ? formatAttempt(installFailedAttempt) : undefined} | ||
> | ||
<EuiBadge color="danger" iconType="error"> | ||
<FormattedMessage | ||
id="xpack.fleet.packageCard.installFailed" | ||
defaultMessage="Install failed" | ||
/> | ||
</EuiBadge> | ||
</EuiToolTip> | ||
</span> | ||
</EuiFlexItem> | ||
); | ||
} | ||
|
||
return extraLabelsBadges; | ||
} | ||
|
||
function formatAttempt(attempt: InstallFailedAttempt): React.ReactNode { | ||
return ( | ||
<> | ||
<FormattedMessage | ||
id="xpack.fleet.packageCard.faileAttemptDescription" | ||
defaultMessage="Failed at {attemptDate}." | ||
values={{ | ||
attemptDate: ( | ||
<> | ||
<FormattedDate | ||
value={attempt.created_at} | ||
year="numeric" | ||
month="short" | ||
day="numeric" | ||
/> | ||
<> @ </> | ||
<FormattedTime | ||
value={attempt.created_at} | ||
hour="numeric" | ||
minute="numeric" | ||
second="numeric" | ||
/> | ||
</> | ||
), | ||
}} | ||
/> | ||
<p> | ||
{attempt.error?.name || ''} : {attempt.error?.message || ''} | ||
</p> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.