Skip to content

Commit

Permalink
[8.x] [UII] Support content packages in UI (#195831) (#196398)
Browse files Browse the repository at this point in the history
# Backport

This will backport the following commits from `main` to `8.x`:
- [[UII] Support content packages in UI
(#195831)](#195831)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Jen
Huang","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-10-15T17:18:41Z","message":"[UII]
Support content packages in UI (#195831)\n\n## Summary\r\n\r\nResolves
#192484. This PR adds support for content packages in UI. When\r\na
package is of `type: content`:\r\n\r\n- `Content only` badge is shown on
its card in Integrations list, and on\r\nheader of its details page\r\n-
`Add integration` button is replaced by `Install assets` button
in\r\nheader\r\n- References to agent policies are hidden\r\n- Package
policy service throws error if attempting to create or bulk\r\ncreate
policies for content packages\r\n\r\n<img width=\"1403\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/a82c310a-f849-4b68-b56c-ff6bb31cd6bf\">\r\n\r\n<img
width=\"1401\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/63eb3982-9ec9-494f-a95a-2b8992a408ba\">\r\n\r\n##
How to test\r\nThe only current content package is `kubernetes_otel`.
You will need to\r\nbump up the max allowed spec version and search with
beta (prerelease)\r\npackages enabled to find
it:\r\n```\r\nxpack.fleet.internal.registry.spec.max:
'3.4'\r\n```\r\n\r\nTest UI scenarios as above. The API can be tested by
running:\r\n```\r\nPOST kbn:/api/fleet/package_policies\r\n{\r\n
\"policy_ids\": [\r\n \"\"\r\n ],\r\n \"package\": {\r\n \"name\":
\"kubernetes_otel\",\r\n \"version\": \"0.0.2\"\r\n },\r\n \"name\":
\"kubernetes_otel-1\",\r\n \"description\": \"\",\r\n \"namespace\":
\"\",\r\n \"inputs\": {}\r\n}\r\n```\r\n\r\n### Checklist\r\n\r\nDelete
any items that are not applicable to this PR.\r\n\r\n- [x] Any text
added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"9512f6c26fbac59b8b8d7390dc28da930e42f181","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Fleet","v9.0.0","release_note:feature","backport:prev-minor"],"number":195831,"url":"https://github.com/elastic/kibana/pull/195831","mergeCommit":{"message":"[UII]
Support content packages in UI (#195831)\n\n## Summary\r\n\r\nResolves
#192484. This PR adds support for content packages in UI. When\r\na
package is of `type: content`:\r\n\r\n- `Content only` badge is shown on
its card in Integrations list, and on\r\nheader of its details page\r\n-
`Add integration` button is replaced by `Install assets` button
in\r\nheader\r\n- References to agent policies are hidden\r\n- Package
policy service throws error if attempting to create or bulk\r\ncreate
policies for content packages\r\n\r\n<img width=\"1403\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/a82c310a-f849-4b68-b56c-ff6bb31cd6bf\">\r\n\r\n<img
width=\"1401\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/63eb3982-9ec9-494f-a95a-2b8992a408ba\">\r\n\r\n##
How to test\r\nThe only current content package is `kubernetes_otel`.
You will need to\r\nbump up the max allowed spec version and search with
beta (prerelease)\r\npackages enabled to find
it:\r\n```\r\nxpack.fleet.internal.registry.spec.max:
'3.4'\r\n```\r\n\r\nTest UI scenarios as above. The API can be tested by
running:\r\n```\r\nPOST kbn:/api/fleet/package_policies\r\n{\r\n
\"policy_ids\": [\r\n \"\"\r\n ],\r\n \"package\": {\r\n \"name\":
\"kubernetes_otel\",\r\n \"version\": \"0.0.2\"\r\n },\r\n \"name\":
\"kubernetes_otel-1\",\r\n \"description\": \"\",\r\n \"namespace\":
\"\",\r\n \"inputs\": {}\r\n}\r\n```\r\n\r\n### Checklist\r\n\r\nDelete
any items that are not applicable to this PR.\r\n\r\n- [x] Any text
added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"9512f6c26fbac59b8b8d7390dc28da930e42f181"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/195831","number":195831,"mergeCommit":{"message":"[UII]
Support content packages in UI (#195831)\n\n## Summary\r\n\r\nResolves
#192484. This PR adds support for content packages in UI. When\r\na
package is of `type: content`:\r\n\r\n- `Content only` badge is shown on
its card in Integrations list, and on\r\nheader of its details page\r\n-
`Add integration` button is replaced by `Install assets` button
in\r\nheader\r\n- References to agent policies are hidden\r\n- Package
policy service throws error if attempting to create or bulk\r\ncreate
policies for content packages\r\n\r\n<img width=\"1403\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/a82c310a-f849-4b68-b56c-ff6bb31cd6bf\">\r\n\r\n<img
width=\"1401\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/63eb3982-9ec9-494f-a95a-2b8992a408ba\">\r\n\r\n##
How to test\r\nThe only current content package is `kubernetes_otel`.
You will need to\r\nbump up the max allowed spec version and search with
beta (prerelease)\r\npackages enabled to find
it:\r\n```\r\nxpack.fleet.internal.registry.spec.max:
'3.4'\r\n```\r\n\r\nTest UI scenarios as above. The API can be tested by
running:\r\n```\r\nPOST kbn:/api/fleet/package_policies\r\n{\r\n
\"policy_ids\": [\r\n \"\"\r\n ],\r\n \"package\": {\r\n \"name\":
\"kubernetes_otel\",\r\n \"version\": \"0.0.2\"\r\n },\r\n \"name\":
\"kubernetes_otel-1\",\r\n \"description\": \"\",\r\n \"namespace\":
\"\",\r\n \"inputs\": {}\r\n}\r\n```\r\n\r\n### Checklist\r\n\r\nDelete
any items that are not applicable to this PR.\r\n\r\n- [x] Any text
added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"9512f6c26fbac59b8b8d7390dc28da930e42f181"}}]}]
BACKPORT-->
  • Loading branch information
jen-huang authored Oct 15, 2024
1 parent a35203b commit f952c2d
Show file tree
Hide file tree
Showing 20 changed files with 252 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ export function useOnSubmit({

// Check if agentless is configured in ESS and Serverless until Agentless API migrates to Serverless
const isAgentlessConfigured =
isAgentlessAgentPolicy(createdPolicy) || isAgentlessPackagePolicy(data!.item);
isAgentlessAgentPolicy(createdPolicy) || (data && isAgentlessPackagePolicy(data.item));

// Removing this code will disabled the Save and Continue button. We need code below update form state and trigger correct modal depending on agent count
if (hasFleetAddAgentsPrivileges && !isAgentlessConfigured) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export function PackageCard({
name,
title,
version,
type,
icons,
integration,
url,
Expand All @@ -78,7 +79,6 @@ export function PackageCard({
maxCardHeight,
}: PackageCardProps) {
let releaseBadge: React.ReactNode | null = null;

if (release && release !== 'ga') {
releaseBadge = (
<EuiFlexItem grow={false}>
Expand Down Expand Up @@ -108,7 +108,6 @@ export function PackageCard({
}

let hasDeferredInstallationsBadge: React.ReactNode | null = null;

if (isReauthorizationRequired && showLabels) {
hasDeferredInstallationsBadge = (
<EuiFlexItem grow={false}>
Expand All @@ -127,7 +126,6 @@ export function PackageCard({
}

let updateAvailableBadge: React.ReactNode | null = null;

if (isUpdateAvailable && showLabels) {
updateAvailableBadge = (
<EuiFlexItem grow={false}>
Expand All @@ -145,7 +143,6 @@ export function PackageCard({
}

let collectionButton: React.ReactNode | null = null;

if (isCollectionCard) {
collectionButton = (
<EuiFlexItem>
Expand All @@ -163,6 +160,23 @@ export function PackageCard({
);
}

let contentBadge: React.ReactNode | null = null;
if (type === 'content') {
contentBadge = (
<EuiFlexItem grow={false}>
<EuiSpacer size="xs" />
<span>
<EuiBadge color="hollow">
<FormattedMessage
id="xpack.fleet.packageCard.contentPackageLabel"
defaultMessage="Content only"
/>
</EuiBadge>
</span>
</EuiFlexItem>
);
}

const { application } = useStartServices();
const isGuidedOnboardingActive = useIsGuidedOnboardingActive(name);

Expand Down Expand Up @@ -235,6 +249,7 @@ export function PackageCard({
{showLabels && extraLabelsBadges ? extraLabelsBadges : null}
{verifiedBadge}
{updateAvailableBadge}
{contentBadge}
{releaseBadge}
{hasDeferredInstallationsBadge}
{collectionButton}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import { Configs } from './configs';

import './index.scss';
import type { InstallPkgRouteOptions } from './utils/get_install_route_options';
import { InstallButton } from './settings/install_button';

export type DetailViewPanelName =
| 'overview'
Expand Down Expand Up @@ -362,13 +363,23 @@ export function Detail() {
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem grow={false}>
<EuiBadge color="default">
{i18n.translate('xpack.fleet.epm.elasticAgentBadgeLabel', {
defaultMessage: 'Elastic Agent',
})}
</EuiBadge>
</EuiFlexItem>
{packageInfo?.type === 'content' ? (
<EuiFlexItem grow={false}>
<EuiBadge color="default">
{i18n.translate('xpack.fleet.epm.contentPackageBadgeLabel', {
defaultMessage: 'Content only',
})}
</EuiBadge>
</EuiFlexItem>
) : (
<EuiFlexItem grow={false}>
<EuiBadge color="default">
{i18n.translate('xpack.fleet.epm.elasticAgentBadgeLabel', {
defaultMessage: 'Elastic Agent',
})}
</EuiBadge>
</EuiFlexItem>
)}
{packageInfo?.release && packageInfo.release !== 'ga' ? (
<EuiFlexItem grow={false}>
<HeaderReleaseBadge release={getPackageReleaseLabel(packageInfo.version)} />
Expand Down Expand Up @@ -520,7 +531,7 @@ export function Detail() {
</EuiFlexGroup>
),
},
...(isInstalled
...(isInstalled && packageInfo.type !== 'content'
? [
{ isDivider: true },
{
Expand All @@ -532,31 +543,37 @@ export function Detail() {
},
]
: []),
{ isDivider: true },
{
content: (
<WithGuidedOnboardingTour
packageKey={pkgkey}
tourType={'addIntegrationButton'}
isTourVisible={isOverviewPage && isGuidedOnboardingActive}
tourOffset={10}
>
<AddIntegrationButton
userCanInstallPackages={userCanInstallPackages}
href={getHref('add_integration_to_policy', {
pkgkey,
...(integration ? { integration } : {}),
...(agentPolicyIdFromContext
? { agentPolicyId: agentPolicyIdFromContext }
: {}),
})}
missingSecurityConfiguration={missingSecurityConfiguration}
packageName={integrationInfo?.title || packageInfo.title}
onClick={handleAddIntegrationPolicyClick}
/>
</WithGuidedOnboardingTour>
),
},
...(packageInfo.type === 'content'
? !isInstalled
? [{ isDivider: true }, { content: <InstallButton {...packageInfo} /> }]
: [] // if content package is already installed, don't show install button in header
: [
{ isDivider: true },
{
content: (
<WithGuidedOnboardingTour
packageKey={pkgkey}
tourType={'addIntegrationButton'}
isTourVisible={isOverviewPage && isGuidedOnboardingActive}
tourOffset={10}
>
<AddIntegrationButton
userCanInstallPackages={userCanInstallPackages}
href={getHref('add_integration_to_policy', {
pkgkey,
...(integration ? { integration } : {}),
...(agentPolicyIdFromContext
? { agentPolicyId: agentPolicyIdFromContext }
: {}),
})}
missingSecurityConfiguration={missingSecurityConfiguration}
packageName={integrationInfo?.title || packageInfo.title}
onClick={handleAddIntegrationPolicyClick}
/>
</WithGuidedOnboardingTour>
),
},
]),
].map((item, index) => (
<EuiFlexItem grow={false} key={index} data-test-subj={item['data-test-subj']}>
{item.isDivider ?? false ? (
Expand Down Expand Up @@ -619,7 +636,7 @@ export function Detail() {
},
];

if (canReadIntegrationPolicies && isInstalled) {
if (canReadIntegrationPolicies && isInstalled && packageInfo.type !== 'content') {
tabs.push({
id: 'policies',
name: (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ interface ConfirmPackageInstallProps {
onConfirm: () => void;
packageName: string;
numOfAssets: number;
numOfTransformAssets: number;
}

import { TransformInstallWithCurrentUserPermissionCallout } from '../../../../../../../components/transform_install_as_current_user_callout';

export const ConfirmPackageInstall = (props: ConfirmPackageInstallProps) => {
const { onCancel, onConfirm, packageName, numOfAssets } = props;
const { onCancel, onConfirm, packageName, numOfAssets, numOfTransformAssets } = props;
return (
<EuiConfirmModal
title={
Expand Down Expand Up @@ -53,6 +57,12 @@ export const ConfirmPackageInstall = (props: ConfirmPackageInstallProps) => {
/>
}
/>
{numOfTransformAssets > 0 ? (
<>
<EuiSpacer size="m" />
<TransformInstallWithCurrentUserPermissionCallout count={numOfTransformAssets} />
</>
) : null}
<EuiSpacer size="l" />
<p>
<FormattedMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,37 @@ import type { PackageInfo, UpgradePackagePolicyDryRunResponse } from '../../../.
import { InstallStatus } from '../../../../../types';
import { useAuthz, useGetPackageInstallStatus, useInstallPackage } from '../../../../../hooks';

import { getNumTransformAssets } from '../../../../../../../components/transform_install_as_current_user_callout';

import { ConfirmPackageInstall } from './confirm_package_install';
type InstallationButtonProps = Pick<PackageInfo, 'name' | 'title' | 'version'> & {

type InstallationButtonProps = Pick<PackageInfo, 'name' | 'title' | 'version' | 'assets'> & {
disabled?: boolean;
dryRunData?: UpgradePackagePolicyDryRunResponse | null;
isUpgradingPackagePolicies?: boolean;
latestVersion?: string;
numOfAssets: number;
packagePolicyIds?: string[];
setIsUpgradingPackagePolicies?: React.Dispatch<React.SetStateAction<boolean>>;
};
export function InstallButton(props: InstallationButtonProps) {
const { name, numOfAssets, title, version } = props;
const { name, title, version, assets } = props;

const canInstallPackages = useAuthz().integrations.installPackages;
const installPackage = useInstallPackage();
const getPackageInstallStatus = useGetPackageInstallStatus();
const { status: installationStatus } = getPackageInstallStatus(name);

const numOfAssets = Object.entries(assets).reduce(
(acc, [serviceName, serviceNameValue]) =>
acc +
Object.entries(serviceNameValue || {}).reduce(
(acc2, [assetName, assetNameValue]) => acc2 + assetNameValue.length,
0
),
0
);
const numOfTransformAssets = getNumTransformAssets(assets);

const isInstalling = installationStatus === InstallStatus.installing;
const [isInstallModalVisible, setIsInstallModalVisible] = useState<boolean>(false);
const toggleInstallModal = useCallback(() => {
Expand All @@ -44,6 +58,7 @@ export function InstallButton(props: InstallationButtonProps) {
const installModal = (
<ConfirmPackageInstall
numOfAssets={numOfAssets}
numOfTransformAssets={numOfTransformAssets}
packageName={title}
onCancel={toggleInstallModal}
onConfirm={handleClickInstall}
Expand All @@ -61,15 +76,15 @@ export function InstallButton(props: InstallationButtonProps) {
{isInstalling ? (
<FormattedMessage
id="xpack.fleet.integrations.installPackage.installingPackageButtonLabel"
defaultMessage="Installing {title} assets"
defaultMessage="Installing {title}"
values={{
title,
}}
/>
) : (
<FormattedMessage
id="xpack.fleet.integrations.installPackage.installPackageButtonLabel"
defaultMessage="Install {title} assets"
defaultMessage="Install {title}"
values={{
title,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ import {

import { i18n } from '@kbn/i18n';

import {
getNumTransformAssets,
TransformInstallWithCurrentUserPermissionCallout,
} from '../../../../../../../components/transform_install_as_current_user_callout';

import type { FleetStartServices } from '../../../../../../../plugin';
import type { PackageInfo, PackageMetadata } from '../../../../../types';
import { InstallStatus } from '../../../../../types';
Expand Down Expand Up @@ -238,22 +233,6 @@ export const SettingsPage: React.FC<Props> = memo(

const isUpdating = installationStatus === InstallStatus.installing && installedVersion;

const { numOfAssets, numTransformAssets } = useMemo(
() => ({
numTransformAssets: getNumTransformAssets(packageInfo.assets),
numOfAssets: Object.entries(packageInfo.assets).reduce(
(acc, [serviceName, serviceNameValue]) =>
acc +
Object.entries(serviceNameValue).reduce(
(acc2, [assetName, assetNameValue]) => acc2 + assetNameValue.length,
0
),
0
),
}),
[packageInfo.assets]
);

return (
<>
<EuiFlexGroup alignItems="flexStart">
Expand Down Expand Up @@ -365,15 +344,6 @@ export const SettingsPage: React.FC<Props> = memo(
</h4>
</EuiTitle>
<EuiSpacer size="s" />

{numTransformAssets > 0 ? (
<>
<TransformInstallWithCurrentUserPermissionCallout
count={numTransformAssets}
/>
<EuiSpacer size="s" />
</>
) : null}
<p>
<FormattedMessage
id="xpack.fleet.integrations.settings.packageInstallDescription"
Expand All @@ -388,7 +358,6 @@ export const SettingsPage: React.FC<Props> = memo(
<p>
<InstallButton
{...packageInfo}
numOfAssets={numOfAssets}
disabled={packageMetadata?.has_policies}
/>
</p>
Expand Down Expand Up @@ -418,7 +387,6 @@ export const SettingsPage: React.FC<Props> = memo(
<div>
<UninstallButton
{...packageInfo}
numOfAssets={numOfAssets}
latestVersion={latestVersion}
disabled={packageMetadata?.has_policies}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,16 @@ import { useAuthz, useGetPackageInstallStatus, useUninstallPackage } from '../..

import { ConfirmPackageUninstall } from './confirm_package_uninstall';

interface UninstallButtonProps extends Pick<PackageInfo, 'name' | 'title' | 'version'> {
interface UninstallButtonProps extends Pick<PackageInfo, 'name' | 'title' | 'version' | 'assets'> {
disabled?: boolean;
latestVersion?: string;
numOfAssets: number;
}

export const UninstallButton: React.FunctionComponent<UninstallButtonProps> = ({
disabled = false,
latestVersion,
name,
numOfAssets,
assets,
title,
version,
}) => {
Expand All @@ -38,6 +37,16 @@ export const UninstallButton: React.FunctionComponent<UninstallButtonProps> = ({

const [isUninstallModalVisible, setIsUninstallModalVisible] = useState<boolean>(false);

const numOfAssets = Object.entries(assets).reduce(
(acc, [serviceName, serviceNameValue]) =>
acc +
Object.entries(serviceNameValue || {}).reduce(
(acc2, [assetName, assetNameValue]) => acc2 + assetNameValue.length,
0
),
0
);

const handleClickUninstall = useCallback(() => {
uninstallPackage({ name, version, title, redirectToVersion: latestVersion ?? version });
setIsUninstallModalVisible(false);
Expand Down
Loading

0 comments on commit f952c2d

Please sign in to comment.