diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index fda07095de95d..9bc935703df9a 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -533,6 +533,10 @@ export type PackageInfo = | Installable> | Installable>; +export interface PackageMetadata { + has_policies: true; +} + export type IntegrationCardReleaseLabel = 'beta' | 'preview' | 'ga' | 'rc'; export type PackageVerificationStatus = 'verified' | 'unverified' | 'unknown'; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts index 58f42b08e0612..b897d84152a3e 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts @@ -19,6 +19,7 @@ import type { SimpleSOAssetType, AssetSOObject, InstallResultStatus, + PackageMetadata, } from '../models/epm'; export interface GetCategoriesRequest { @@ -97,6 +98,7 @@ export interface GetInfoRequest { export interface GetInfoResponse { item: PackageInfo; + metadata?: PackageMetadata; // deprecated in 8.0 response?: PackageInfo; } diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index c688da76819d4..f826b4f5c308e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -210,6 +210,7 @@ export function Detail() { pkgVersion, { prerelease: prereleaseIntegrationsEnabled, + withMetadata: true, }, { enabled: !authz.fleet.readSettings || !isSettingsInitialLoading, // Load only after settings are loaded @@ -785,7 +786,11 @@ export function Detail() { /> - + diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx index 769f979e83bf3..d3df3aa8821e0 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx @@ -29,7 +29,7 @@ import { } from '../../../../../../../components/transform_install_as_current_user_callout'; import type { FleetStartServices } from '../../../../../../../plugin'; -import type { PackageInfo } from '../../../../../types'; +import type { PackageInfo, PackageMetadata } from '../../../../../types'; import { InstallStatus } from '../../../../../types'; import { useGetPackagePoliciesQuery, @@ -117,240 +117,367 @@ const LatestVersionLink = ({ name, version }: { name: string; version: string }) interface Props { packageInfo: PackageInfo; + packageMetadata?: PackageMetadata; startServices: Pick; } -export const SettingsPage: React.FC = memo(({ packageInfo, startServices }: Props) => { - const { name, title, latestVersion, version, keepPoliciesUpToDate } = packageInfo; - const [isUpgradingPackagePolicies, setIsUpgradingPackagePolicies] = useState(false); - const [isChangelogModalOpen, setIsChangelogModalOpen] = useState(false); - - const toggleChangelogModal = useCallback(() => { - setIsChangelogModalOpen(!isChangelogModalOpen); - }, [isChangelogModalOpen]); - const getPackageInstallStatus = useGetPackageInstallStatus(); - - const { data: packagePoliciesData } = useGetPackagePoliciesQuery({ - perPage: SO_SEARCH_LIMIT, - page: 1, - kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${name}`, - }); - - const packagePolicyIds = useMemo( - () => packagePoliciesData?.items.map(({ id }) => id), - [packagePoliciesData] - ); +export const SettingsPage: React.FC = memo( + ({ packageInfo, packageMetadata, startServices }: Props) => { + const { name, title, latestVersion, version, keepPoliciesUpToDate } = packageInfo; + const [isUpgradingPackagePolicies, setIsUpgradingPackagePolicies] = useState(false); + const [isChangelogModalOpen, setIsChangelogModalOpen] = useState(false); + + const toggleChangelogModal = useCallback(() => { + setIsChangelogModalOpen(!isChangelogModalOpen); + }, [isChangelogModalOpen]); + const getPackageInstallStatus = useGetPackageInstallStatus(); + + const { data: packagePoliciesData } = useGetPackagePoliciesQuery({ + perPage: SO_SEARCH_LIMIT, + page: 1, + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${name}`, + }); + + const packagePolicyIds = useMemo( + () => packagePoliciesData?.items.map(({ id }) => id), + [packagePoliciesData] + ); - const agentPolicyIds = useMemo( - () => packagePoliciesData?.items.flatMap((packagePolicy) => packagePolicy.policy_ids) ?? [], - [packagePoliciesData] - ); + const agentPolicyIds = useMemo( + () => packagePoliciesData?.items.flatMap((packagePolicy) => packagePolicy.policy_ids) ?? [], + [packagePoliciesData] + ); - const { data: dryRunData } = useUpgradePackagePolicyDryRunQuery( - packagePolicyIds ?? [], - latestVersion, - { - enabled: packagePolicyIds && packagePolicyIds.length > 0, - } - ); + const { data: dryRunData } = useUpgradePackagePolicyDryRunQuery( + packagePolicyIds ?? [], + latestVersion, + { + enabled: packagePolicyIds && packagePolicyIds.length > 0, + } + ); - const updatePackageMutation = useUpdatePackageMutation(); + const updatePackageMutation = useUpdatePackageMutation(); - const { notifications } = useStartServices(); + const { notifications } = useStartServices(); - const shouldShowKeepPoliciesUpToDateSwitch = useMemo(() => { - return KEEP_POLICIES_UP_TO_DATE_PACKAGES.some((pkg) => pkg.name === name); - }, [name]); + const shouldShowKeepPoliciesUpToDateSwitch = useMemo(() => { + return KEEP_POLICIES_UP_TO_DATE_PACKAGES.some((pkg) => pkg.name === name); + }, [name]); - const isShowKeepPoliciesUpToDateSwitchDisabled = useMemo(() => { - return AUTO_UPGRADE_POLICIES_PACKAGES.some((pkg) => pkg.name === name); - }, [name]); + const isShowKeepPoliciesUpToDateSwitchDisabled = useMemo(() => { + return AUTO_UPGRADE_POLICIES_PACKAGES.some((pkg) => pkg.name === name); + }, [name]); - const [keepPoliciesUpToDateSwitchValue, setKeepPoliciesUpToDateSwitchValue] = useState( - keepPoliciesUpToDate ?? false - ); + const [keepPoliciesUpToDateSwitchValue, setKeepPoliciesUpToDateSwitchValue] = useState( + keepPoliciesUpToDate ?? false + ); - const handleKeepPoliciesUpToDateSwitchChange = useCallback(() => { - setKeepPoliciesUpToDateSwitchValue((prev) => !prev); + const handleKeepPoliciesUpToDateSwitchChange = useCallback(() => { + setKeepPoliciesUpToDateSwitchValue((prev) => !prev); - updatePackageMutation.mutate( - { - pkgName: packageInfo.name, - pkgVersion: packageInfo.version, - body: { - keepPoliciesUpToDate: !keepPoliciesUpToDateSwitchValue, - }, - }, - { - onSuccess: () => { - notifications.toasts.addSuccess({ - title: i18n.translate('xpack.fleet.integrations.integrationSaved', { - defaultMessage: 'Integration settings saved', - }), - text: !keepPoliciesUpToDateSwitchValue - ? i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateEnabledSuccess', { - defaultMessage: - 'Fleet will automatically keep integration policies up to date for {title}', - values: { title }, - }) - : i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateDisabledSuccess', { - defaultMessage: - 'Fleet will not automatically keep integration policies up to date for {title}', - values: { title }, - }), - }); - }, - onError: (error) => { - notifications.toasts.addError(error, { - title: i18n.translate('xpack.fleet.integrations.integrationSavedError', { - defaultMessage: 'Error saving integration settings', - }), - toastMessage: i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateError', { - defaultMessage: 'Error saving integration settings for {title}', - values: { title }, - }), - }); + updatePackageMutation.mutate( + { + pkgName: packageInfo.name, + pkgVersion: packageInfo.version, + body: { + keepPoliciesUpToDate: !keepPoliciesUpToDateSwitchValue, + }, }, - } + { + onSuccess: () => { + notifications.toasts.addSuccess({ + title: i18n.translate('xpack.fleet.integrations.integrationSaved', { + defaultMessage: 'Integration settings saved', + }), + text: !keepPoliciesUpToDateSwitchValue + ? i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateEnabledSuccess', { + defaultMessage: + 'Fleet will automatically keep integration policies up to date for {title}', + values: { title }, + }) + : i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateDisabledSuccess', { + defaultMessage: + 'Fleet will not automatically keep integration policies up to date for {title}', + values: { title }, + }), + }); + }, + onError: (error) => { + notifications.toasts.addError(error, { + title: i18n.translate('xpack.fleet.integrations.integrationSavedError', { + defaultMessage: 'Error saving integration settings', + }), + toastMessage: i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateError', { + defaultMessage: 'Error saving integration settings for {title}', + values: { title }, + }), + }); + }, + } + ); + }, [ + keepPoliciesUpToDateSwitchValue, + notifications.toasts, + packageInfo.name, + packageInfo.version, + title, + updatePackageMutation, + ]); + + const { status: installationStatus, version: installedVersion } = getPackageInstallStatus(name); + + const updateAvailable = + installedVersion && semverLt(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; + + 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] ); - }, [ - keepPoliciesUpToDateSwitchValue, - notifications.toasts, - packageInfo.name, - packageInfo.version, - title, - updatePackageMutation, - ]); - - const { status: installationStatus, version: installedVersion } = getPackageInstallStatus(name); - const packageHasUsages = !!packagePoliciesData?.total; - - const updateAvailable = - installedVersion && semverLt(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; - - 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 ( - <> - - - - - -

- -

-
- - {installedVersion !== null && ( -
- -

- -

-
- - - - - - + + + + + +

+ +

+
+ + {installedVersion !== null && ( +
+ +

+ +

+
+ +
+ + + + + + + + + + + + + + +
+ + {installedVersion} + +
+ + {latestVersion} + +
+ {shouldShowKeepPoliciesUpToDateSwitch && ( + <> + + + + )} + + {(updateAvailable || isUpgradingPackagePolicies) && ( + <> + + +

+ - - - - {installedVersion} - - - - - +

+ + )} +
+ )} + {!hideInstallOptions && !isUpdating && ( +
+ + {installationStatus === InstallStatus.notInstalled || + installationStatus === InstallStatus.installing ? ( +
+ +

+ +

+
+ + + {numTransformAssets > 0 ? ( + <> + + + + ) : null} +

- - - - {latestVersion} - - - - - - {shouldShowKeepPoliciesUpToDateSwitch && ( - <> - - - - )} - - {(updateAvailable || isUpgradingPackagePolicies) && ( - <> - - -

- -

- - )} -
- )} - {!hideInstallOptions && !isUpdating && ( -
- - {installationStatus === InstallStatus.notInstalled || - installationStatus === InstallStatus.installing ? ( +

+ + +

+ +

+
+
+
+ ) : ( + <> + + + +

+ +

+
+
+ + + + +
+ +
+
+ {packageMetadata?.has_policies && ( + + + , + }} + /> + + + )} +
+ + + + +

+ +

+
+
+ + + + +
+ +
+
+
+ + )} +
+ )} + {hideInstallOptions && isViewingOldPackage && !isUpdating && ( +
+

@@ -364,160 +491,37 @@ export const SettingsPage: React.FC = memo(({ packageInfo, startServices

- - {numTransformAssets > 0 ? ( - <> - - - - ) : null}

- -

- - -

- -

-
-
-
- ) : ( - <> - - - -

- -

-
-
- + - - -
- -
-
- {packageHasUsages && ( - - - , - }} - /> - - - )} -
- - - - -

- -

-
-
- - + ), + }} /> - - -
- -
-
-
- - )} -
- )} - {hideInstallOptions && isViewingOldPackage && !isUpdating && ( -
- -
- -

- -

-
- -

- - , - }} - /> - -

+ +

+
- - )} -
-
-
- - {isChangelogModalOpen && ( - - )} - - - ); -}); + )} + + + + + {isChangelogModalOpen && ( + + )} + + + ); + } +); diff --git a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts index bd4bec9be6a1a..1ea668fd8955c 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts @@ -123,6 +123,7 @@ export const useGetPackageInfoByKeyQuery = ( ignoreUnverified?: boolean; prerelease?: boolean; full?: boolean; + withMetadata?: boolean; }, // Additional options for the useQuery hook queryOptions: { diff --git a/x-pack/plugins/fleet/public/types/index.ts b/x-pack/plugins/fleet/public/types/index.ts index 20d94e6d44fa0..a340b7311fdbe 100644 --- a/x-pack/plugins/fleet/public/types/index.ts +++ b/x-pack/plugins/fleet/public/types/index.ts @@ -101,6 +101,7 @@ export type { CategorySummaryItem, CategorySummaryList, PackageInfo, + PackageMetadata, RegistryVarsEntry, RegistryInput, RegistryStream, diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 9ce8c9afa4e6a..560af83619a48 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -11,6 +11,8 @@ import type { HttpResponseOptions } from '@kbn/core/server'; import { pick } from 'lodash'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../common'; + import { HTTPAuthorizationHeader } from '../../../common/http_authorization_header'; import { generateTransformSecondaryAuthHeaders } from '../../services/api_keys/transform_api_keys'; import { handleTransformReauthorizeAndStart } from '../../services/epm/elasticsearch/transform/reauthorize'; @@ -71,7 +73,7 @@ import { FleetError, FleetTooManyRequestsError, } from '../../errors'; -import { appContextService, checkAllowedPackages } from '../../services'; +import { appContextService, checkAllowedPackages, packagePolicyService } from '../../services'; import { getPackageUsageStats } from '../../services/epm/packages/get'; import { updatePackage } from '../../services/epm/packages/update'; import { getGpgKeyIdOrUndefined } from '../../services/epm/packages/package_verification'; @@ -229,8 +231,23 @@ export const getInfoHandler: FleetRequestHandler< }); const flattenedRes = soToInstallationInfo(res) as PackageInfo; + let metadata: any; + if (request.query.withMetadata) { + const allSpaceSoClient = appContextService.getInternalUserSOClientWithoutSpaceExtension(); + const { total } = await packagePolicyService.list(allSpaceSoClient, { + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${pkgName}`, + page: 1, + perPage: 0, + spaceId: '*', + }); + metadata = { + has_policies: total > 0, + }; + } + const body: GetInfoResponse = { item: flattenedRes, + metadata, }; return response.ok({ body }); } catch (error) { diff --git a/x-pack/plugins/fleet/server/types/rest_spec/epm.ts b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts index 4b83c7f7c7c58..c6d3811bd713e 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts @@ -86,6 +86,7 @@ export const GetInfoRequestSchema = { ignoreUnverified: schema.maybe(schema.boolean()), prerelease: schema.maybe(schema.boolean()), full: schema.maybe(schema.boolean()), + withMetadata: schema.boolean({ defaultValue: false }), }), };