diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts index 19ced885822a4f7..52170f6c302fdfa 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts @@ -24,7 +24,7 @@ import { PackageNotFoundError } from '../../../errors'; import { getSettings } from '../../settings'; -import { getPackageInfo, getPackageUsageStats } from './get'; +import { getPackageInfo, getPackages, getPackageUsageStats } from './get'; const MockRegistry = Registry as jest.Mocked; @@ -186,6 +186,65 @@ describe('When using EPM `get` services', () => { }); }); + describe('getPackages', () => { + beforeEach(() => { + const mockContract = createAppContextStartContractMock(); + appContextService.start(mockContract); + jest.clearAllMocks(); + MockRegistry.fetchList.mockResolvedValue([ + { + name: 'nginx', + version: '1.0.0', + title: 'Nginx', + } as any, + ]); + }); + + it('should return installed package that is not in registry', async () => { + const soClient = savedObjectsClientMock.create(); + soClient.find.mockResolvedValue({ + saved_objects: [ + { + id: 'elasticsearch', + attributes: { + name: 'elasticsearch', + version: '0.0.1', + install_status: 'upload', + }, + }, + ], + } as any); + + await expect( + getPackages({ + savedObjectsClient: soClient, + }) + ).resolves.toMatchObject([ + { + name: 'elasticsearch', + version: '0.0.1', + title: 'Elasticsearch', + status: 'upload', + savedObject: { + id: 'elasticsearch', + attributes: { + name: 'elasticsearch', + version: '0.0.1', + install_status: 'upload', + }, + }, + }, + { + name: 'nginx', + version: '1.0.0', + title: 'Nginx', + id: 'nginx', + status: 'not_installed', + }, + ]); + }); + }); + describe('getPackageInfo', () => { beforeEach(() => { const mockContract = createAppContextStartContractMock(); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index 8776ca6014e55be..1d048e2cf2d2cde 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -14,7 +14,11 @@ import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, } from '../../../../common/constants'; import { isPackageLimited } from '../../../../common/services'; -import type { PackageUsageStats, PackagePolicySOAttributes } from '../../../../common/types'; +import type { + PackageUsageStats, + PackagePolicySOAttributes, + Installable, +} from '../../../../common/types'; import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; import type { ArchivePackage, @@ -68,6 +72,11 @@ export async function getPackages( }); // get the installed packages const packageSavedObjects = await getPackageSavedObjects(savedObjectsClient); + + const packagesNotInRegistry = packageSavedObjects.saved_objects + .filter((pkg) => !registryItems.some((item) => item.name === pkg.id)) + .map((pkg) => createInstallableFrom({ ...pkg.attributes, title: nameAsTitle(pkg.id) }, pkg)); + const packageList = registryItems .map((item) => createInstallableFrom( @@ -75,6 +84,7 @@ export async function getPackages( packageSavedObjects.saved_objects.find(({ id }) => id === item.name) ) ) + .concat(packagesNotInRegistry as Installable) .sort(sortByName); if (!excludeInstallStatus) {