[Fleet] Show Count of Agent Policies on Integration Details (#86916)
* component to show count of agent policies for integration * API route and service to return stats of package usage
This commit is contained in:
parent
31e66979b9
commit
02695ef5ad
|
@ -29,6 +29,7 @@ export const EPM_API_ROUTES = {
|
|||
DELETE_PATTERN: EPM_PACKAGES_ONE,
|
||||
FILEPATH_PATTERN: `${EPM_PACKAGES_FILE}/{filePath*}`,
|
||||
CATEGORIES_PATTERN: `${EPM_API_ROOT}/categories`,
|
||||
STATS_PATTERN: `${EPM_PACKAGES_MANY}/{pkgName}/stats`,
|
||||
};
|
||||
|
||||
// Data stream API routes
|
||||
|
|
|
@ -35,6 +35,10 @@ export const epmRouteService = {
|
|||
return EPM_API_ROUTES.INFO_PATTERN.replace('{pkgkey}', pkgkey);
|
||||
},
|
||||
|
||||
getStatsPath: (pkgName: string) => {
|
||||
return EPM_API_ROUTES.STATS_PATTERN.replace('{pkgName}', pkgName);
|
||||
},
|
||||
|
||||
getFilePath: (filePath: string) => {
|
||||
return `${EPM_API_ROOT}${filePath.replace('/package', '/packages')}`;
|
||||
},
|
||||
|
|
|
@ -279,6 +279,10 @@ export interface Installation extends SavedObjectAttributes {
|
|||
install_source: InstallSource;
|
||||
}
|
||||
|
||||
export interface PackageUsageStats {
|
||||
agent_policy_count: number;
|
||||
}
|
||||
|
||||
export type Installable<T> = Installed<T> | NotInstalled<T>;
|
||||
|
||||
export type Installed<T = {}> = T & {
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
Installable,
|
||||
RegistrySearchResult,
|
||||
PackageInfo,
|
||||
PackageUsageStats,
|
||||
} from '../models/epm';
|
||||
|
||||
export interface GetCategoriesRequest {
|
||||
|
@ -54,6 +55,16 @@ export interface GetInfoResponse {
|
|||
response: PackageInfo;
|
||||
}
|
||||
|
||||
export interface GetStatsRequest {
|
||||
params: {
|
||||
pkgname: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GetStatsResponse {
|
||||
response: PackageUsageStats;
|
||||
}
|
||||
|
||||
export interface InstallPackageRequest {
|
||||
params: {
|
||||
pkgkey: string;
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
InstallPackageResponse,
|
||||
DeletePackageResponse,
|
||||
} from '../../types';
|
||||
import { GetStatsResponse } from '../../../../../common';
|
||||
|
||||
export const useGetCategories = (query: GetCategoriesRequest['query'] = {}) => {
|
||||
return useRequest<GetCategoriesResponse>({
|
||||
|
@ -47,6 +48,13 @@ export const useGetPackageInfoByKey = (pkgkey: string) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const useGetPackageStats = (pkgName: string) => {
|
||||
return useRequest<GetStatsResponse>({
|
||||
path: epmRouteService.getStatsPath(pkgName),
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
export const sendGetPackageInfoByKey = (pkgkey: string) => {
|
||||
return sendRequest<GetInfoResponse>({
|
||||
path: epmRouteService.getInfoPath(pkgkey),
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
GetFleetStatusResponse,
|
||||
GetInfoResponse,
|
||||
GetPackagePoliciesResponse,
|
||||
GetStatsResponse,
|
||||
} from '../../../../../../../common/types/rest_spec';
|
||||
import { DetailViewPanelName, KibanaAssetType } from '../../../../../../../common/types/models';
|
||||
import {
|
||||
|
@ -29,7 +30,7 @@ describe('when on integration detail', () => {
|
|||
const detailPageUrlPath = pagePathGetters.integration_details({ pkgkey });
|
||||
let testRenderer: TestRenderer;
|
||||
let renderResult: ReturnType<typeof testRenderer.render>;
|
||||
let mockedApi: MockedApi;
|
||||
let mockedApi: MockedApi<EpmPackageDetailsResponseProvidersMock>;
|
||||
const render = () =>
|
||||
(renderResult = testRenderer.render(
|
||||
<Route path={PAGE_ROUTING_PATHS.integration_details}>
|
||||
|
@ -48,6 +49,39 @@ describe('when on integration detail', () => {
|
|||
window.location.hash = '#/';
|
||||
});
|
||||
|
||||
describe('and the package is installed', () => {
|
||||
beforeEach(() => render());
|
||||
|
||||
it('should display agent policy usage count', async () => {
|
||||
await mockedApi.waitForApi();
|
||||
expect(renderResult.queryByTestId('agentPolicyCount')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show the Policies tab', async () => {
|
||||
await mockedApi.waitForApi();
|
||||
expect(renderResult.queryByTestId('tab-policies')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('and the package is not installed', () => {
|
||||
beforeEach(() => {
|
||||
const unInstalledPackage = mockedApi.responseProvider.epmGetInfo();
|
||||
unInstalledPackage.response.status = 'not_installed';
|
||||
mockedApi.responseProvider.epmGetInfo.mockReturnValue(unInstalledPackage);
|
||||
render();
|
||||
});
|
||||
|
||||
it('should NOT display agent policy usage count', async () => {
|
||||
await mockedApi.waitForApi();
|
||||
expect(renderResult.queryByTestId('agentPolicyCount')).toBeNull();
|
||||
});
|
||||
|
||||
it('should NOT the Policies tab', async () => {
|
||||
await mockedApi.waitForApi();
|
||||
expect(renderResult.queryByTestId('tab-policies')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('and a custom UI extension is NOT registered', () => {
|
||||
beforeEach(() => render());
|
||||
|
||||
|
@ -190,12 +224,27 @@ describe('when on integration detail', () => {
|
|||
});
|
||||
});
|
||||
|
||||
interface MockedApi {
|
||||
interface MockedApi<
|
||||
R extends Record<string, jest.MockedFunction<any>> = Record<string, jest.MockedFunction<any>>
|
||||
> {
|
||||
/** Will return a promise that resolves when triggered APIs are complete */
|
||||
waitForApi: () => Promise<void>;
|
||||
/** A object containing the list of API response provider functions that are used by the mocked API */
|
||||
responseProvider: R;
|
||||
}
|
||||
|
||||
const mockApiCalls = (http: MockedFleetStartServices['http']): MockedApi => {
|
||||
interface EpmPackageDetailsResponseProvidersMock {
|
||||
epmGetInfo: jest.MockedFunction<() => GetInfoResponse>;
|
||||
epmGetFile: jest.MockedFunction<() => string>;
|
||||
epmGetStats: jest.MockedFunction<() => GetStatsResponse>;
|
||||
fleetSetup: jest.MockedFunction<() => GetFleetStatusResponse>;
|
||||
packagePolicyList: jest.MockedFunction<() => GetPackagePoliciesResponse>;
|
||||
agentPolicyList: jest.MockedFunction<() => GetAgentPoliciesResponse>;
|
||||
}
|
||||
|
||||
const mockApiCalls = (
|
||||
http: MockedFleetStartServices['http']
|
||||
): MockedApi<EpmPackageDetailsResponseProvidersMock> => {
|
||||
let inflightApiCalls = 0;
|
||||
const apiDoneListeners: Array<() => void> = [];
|
||||
const markApiCallAsHandled = async () => {
|
||||
|
@ -663,41 +712,13 @@ On Windows, the module was tested with Nginx installed from the Chocolatey repos
|
|||
perPage: 100,
|
||||
};
|
||||
|
||||
http.get.mockImplementation(async (path) => {
|
||||
if (typeof path === 'string') {
|
||||
if (path === epmRouteService.getInfoPath(`nginx-0.3.7`)) {
|
||||
markApiCallAsHandled();
|
||||
return epmPackageResponse;
|
||||
}
|
||||
const epmGetStatsResponse: GetStatsResponse = {
|
||||
response: {
|
||||
agent_policy_count: 2,
|
||||
},
|
||||
};
|
||||
|
||||
if (path === epmRouteService.getFilePath('/package/nginx/0.3.7/docs/README.md')) {
|
||||
markApiCallAsHandled();
|
||||
return packageReadMe;
|
||||
}
|
||||
|
||||
if (path === fleetSetupRouteService.getFleetSetupPath()) {
|
||||
markApiCallAsHandled();
|
||||
return agentsSetupResponse;
|
||||
}
|
||||
|
||||
if (path === packagePolicyRouteService.getListPath()) {
|
||||
markApiCallAsHandled();
|
||||
return packagePoliciesResponse;
|
||||
}
|
||||
|
||||
if (path === agentPolicyRouteService.getListPath()) {
|
||||
markApiCallAsHandled();
|
||||
return agentPoliciesResponse;
|
||||
}
|
||||
|
||||
const err = new Error(`API [GET ${path}] is not MOCKED!`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
const mockedApiInterface: MockedApi<EpmPackageDetailsResponseProvidersMock> = {
|
||||
waitForApi() {
|
||||
return new Promise((resolve) => {
|
||||
if (inflightApiCalls > 0) {
|
||||
|
@ -707,5 +728,54 @@ On Windows, the module was tested with Nginx installed from the Chocolatey repos
|
|||
}
|
||||
});
|
||||
},
|
||||
responseProvider: {
|
||||
epmGetInfo: jest.fn().mockReturnValue(epmPackageResponse),
|
||||
epmGetFile: jest.fn().mockReturnValue(packageReadMe),
|
||||
epmGetStats: jest.fn().mockReturnValue(epmGetStatsResponse),
|
||||
fleetSetup: jest.fn().mockReturnValue(agentsSetupResponse),
|
||||
packagePolicyList: jest.fn().mockReturnValue(packagePoliciesResponse),
|
||||
agentPolicyList: jest.fn().mockReturnValue(agentPoliciesResponse),
|
||||
},
|
||||
};
|
||||
|
||||
http.get.mockImplementation(async (path) => {
|
||||
if (typeof path === 'string') {
|
||||
if (path === epmRouteService.getInfoPath(`nginx-0.3.7`)) {
|
||||
markApiCallAsHandled();
|
||||
return mockedApiInterface.responseProvider.epmGetInfo();
|
||||
}
|
||||
|
||||
if (path === epmRouteService.getFilePath('/package/nginx/0.3.7/docs/README.md')) {
|
||||
markApiCallAsHandled();
|
||||
return mockedApiInterface.responseProvider.epmGetFile();
|
||||
}
|
||||
|
||||
if (path === fleetSetupRouteService.getFleetSetupPath()) {
|
||||
markApiCallAsHandled();
|
||||
return mockedApiInterface.responseProvider.fleetSetup();
|
||||
}
|
||||
|
||||
if (path === packagePolicyRouteService.getListPath()) {
|
||||
markApiCallAsHandled();
|
||||
return mockedApiInterface.responseProvider.packagePolicyList();
|
||||
}
|
||||
|
||||
if (path === agentPolicyRouteService.getListPath()) {
|
||||
markApiCallAsHandled();
|
||||
return mockedApiInterface.responseProvider.agentPolicyList();
|
||||
}
|
||||
|
||||
if (path === epmRouteService.getStatsPath('nginx')) {
|
||||
markApiCallAsHandled();
|
||||
return mockedApiInterface.responseProvider.epmGetStats();
|
||||
}
|
||||
|
||||
const err = new Error(`API [GET ${path}] is not MOCKED!`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
return mockedApiInterface;
|
||||
};
|
||||
|
|
|
@ -44,6 +44,7 @@ import './index.scss';
|
|||
import { useUIExtension } from '../../../../hooks/use_ui_extension';
|
||||
import { PLUGIN_ID } from '../../../../../../../common/constants';
|
||||
import { pkgKeyFromPackageInfo } from '../../../../services/pkg_key_from_package_info';
|
||||
import { IntegrationAgentPolicyCount } from './integration_agent_policy_count';
|
||||
|
||||
export const DEFAULT_PANEL: DetailViewPanelName = 'overview';
|
||||
|
||||
|
@ -239,6 +240,18 @@ export function Detail() {
|
|||
</EuiFlexGroup>
|
||||
),
|
||||
},
|
||||
...(packageInstallStatus === 'installed'
|
||||
? [
|
||||
{ isDivider: true },
|
||||
{
|
||||
label: i18n.translate('xpack.fleet.epm.usedByLabel', {
|
||||
defaultMessage: 'Agent Policies',
|
||||
}),
|
||||
'data-test-subj': 'agentPolicyCount',
|
||||
content: <IntegrationAgentPolicyCount packageName={packageInfo.name} />,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ isDivider: true },
|
||||
{
|
||||
content: (
|
||||
|
@ -264,7 +277,7 @@ export function Detail() {
|
|||
),
|
||||
},
|
||||
].map((item, index) => (
|
||||
<EuiFlexItem grow={false} key={index}>
|
||||
<EuiFlexItem grow={false} key={index} data-test-subj={item['data-test-subj']}>
|
||||
{item.isDivider ?? false ? (
|
||||
<Divider />
|
||||
) : item.label ? (
|
||||
|
@ -285,6 +298,7 @@ export function Detail() {
|
|||
handleAddIntegrationPolicyClick,
|
||||
hasWriteCapabilites,
|
||||
packageInfo,
|
||||
packageInstallStatus,
|
||||
pkgkey,
|
||||
updateAvailable,
|
||||
]
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 React, { memo } from 'react';
|
||||
import { useGetPackageStats } from '../../../../hooks';
|
||||
|
||||
/**
|
||||
* Displays a count of Agent Policies that are using the given integration
|
||||
*/
|
||||
export const IntegrationAgentPolicyCount = memo<{ packageName: string }>(({ packageName }) => {
|
||||
const { data } = useGetPackageStats(packageName);
|
||||
|
||||
return <>{data?.response.agent_policy_count ?? 0}</>;
|
||||
});
|
|
@ -17,6 +17,7 @@ import {
|
|||
BulkInstallPackageInfo,
|
||||
BulkInstallPackagesResponse,
|
||||
IBulkInstallPackageHTTPError,
|
||||
GetStatsResponse,
|
||||
} from '../../../common';
|
||||
import {
|
||||
GetCategoriesRequestSchema,
|
||||
|
@ -27,6 +28,7 @@ import {
|
|||
InstallPackageByUploadRequestSchema,
|
||||
DeletePackageRequestSchema,
|
||||
BulkUpgradePackagesFromRegistryRequestSchema,
|
||||
GetStatsRequestSchema,
|
||||
} from '../../types';
|
||||
import {
|
||||
BulkInstallResponse,
|
||||
|
@ -48,6 +50,7 @@ import { splitPkgKey } from '../../services/epm/registry';
|
|||
import { licenseService } from '../../services';
|
||||
import { getArchiveEntry } from '../../services/epm/archive/cache';
|
||||
import { getAsset } from '../../services/epm/archive/storage';
|
||||
import { getPackageUsageStats } from '../../services/epm/packages/get';
|
||||
|
||||
export const getCategoriesHandler: RequestHandler<
|
||||
undefined,
|
||||
|
@ -196,6 +199,23 @@ export const getInfoHandler: RequestHandler<TypeOf<typeof GetInfoRequestSchema.p
|
|||
}
|
||||
};
|
||||
|
||||
export const getStatsHandler: RequestHandler<TypeOf<typeof GetStatsRequestSchema.params>> = async (
|
||||
context,
|
||||
request,
|
||||
response
|
||||
) => {
|
||||
try {
|
||||
const { pkgName } = request.params;
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
const body: GetStatsResponse = {
|
||||
response: await getPackageUsageStats({ savedObjectsClient, pkgName }),
|
||||
};
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
||||
export const installPackageFromRegistryHandler: RequestHandler<
|
||||
TypeOf<typeof InstallPackageFromRegistryRequestSchema.params>,
|
||||
undefined,
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
installPackageByUploadHandler,
|
||||
deletePackageHandler,
|
||||
bulkInstallPackagesFromRegistryHandler,
|
||||
getStatsHandler,
|
||||
} from './handlers';
|
||||
import {
|
||||
GetCategoriesRequestSchema,
|
||||
|
@ -25,6 +26,7 @@ import {
|
|||
InstallPackageByUploadRequestSchema,
|
||||
DeletePackageRequestSchema,
|
||||
BulkUpgradePackagesFromRegistryRequestSchema,
|
||||
GetStatsRequestSchema,
|
||||
} from '../../types';
|
||||
|
||||
const MAX_FILE_SIZE_BYTES = 104857600; // 100MB
|
||||
|
@ -57,6 +59,15 @@ export const registerRoutes = (router: IRouter) => {
|
|||
getLimitedListHandler
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: EPM_API_ROUTES.STATS_PATTERN,
|
||||
validate: GetStatsRequestSchema,
|
||||
options: { tags: [`access:${PLUGIN_ID}`] },
|
||||
},
|
||||
getStatsHandler
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: EPM_API_ROUTES.FILEPATH_PATTERN,
|
||||
|
|
171
x-pack/plugins/fleet/server/services/epm/packages/get.test.ts
Normal file
171
x-pack/plugins/fleet/server/services/epm/packages/get.test.ts
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* 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 { SavedObjectsClientContract, SavedObjectsFindResult } from 'kibana/server';
|
||||
import { savedObjectsClientMock } from '../../../../../../../src/core/server/mocks';
|
||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, PackagePolicySOAttributes } from '../../../../common';
|
||||
import { getPackageUsageStats } from './get';
|
||||
|
||||
describe('When using EPM `get` services', () => {
|
||||
let soClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
|
||||
beforeEach(() => {
|
||||
soClient = savedObjectsClientMock.create();
|
||||
});
|
||||
|
||||
describe('and invoking getPackageUsageStats()', () => {
|
||||
beforeEach(() => {
|
||||
const savedObjects: Array<SavedObjectsFindResult<PackagePolicySOAttributes>> = [
|
||||
{
|
||||
type: 'ingest-package-policies',
|
||||
id: 'dcf83172-c38e-4501-b236-9f479da8a7d6',
|
||||
attributes: {
|
||||
name: 'system-3',
|
||||
description: '',
|
||||
namespace: 'default',
|
||||
policy_id: '22222-22222-2222-2222',
|
||||
enabled: true,
|
||||
output_id: '',
|
||||
inputs: [],
|
||||
package: { name: 'system', title: 'System', version: '0.10.4' },
|
||||
revision: 1,
|
||||
created_at: '2020-12-22T21:28:05.380Z',
|
||||
created_by: 'elastic',
|
||||
updated_at: '2020-12-22T21:28:05.380Z',
|
||||
updated_by: 'elastic',
|
||||
},
|
||||
references: [],
|
||||
migrationVersion: { 'ingest-package-policies': '7.11.0' },
|
||||
updated_at: '2020-12-22T21:28:05.383Z',
|
||||
version: 'WzE1NTAsMV0=',
|
||||
score: 0,
|
||||
},
|
||||
{
|
||||
type: 'ingest-package-policies',
|
||||
id: '5b61eb5c-d94c-48a6-a17c-b0d1f7c65336',
|
||||
attributes: {
|
||||
name: 'system-1',
|
||||
namespace: 'default',
|
||||
package: { name: 'system', title: 'System', version: '0.10.4' },
|
||||
enabled: true,
|
||||
policy_id: '11111-111111-11111-11111', // << duplicate id with plicy below
|
||||
output_id: 'ca111b80-43c1-11eb-84bf-7177b74381c5',
|
||||
inputs: [],
|
||||
revision: 1,
|
||||
created_at: '2020-12-21T19:22:04.902Z',
|
||||
created_by: 'system',
|
||||
updated_at: '2020-12-21T19:22:04.902Z',
|
||||
updated_by: 'system',
|
||||
},
|
||||
references: [],
|
||||
migrationVersion: { 'ingest-package-policies': '7.11.0' },
|
||||
updated_at: '2020-12-21T19:22:04.905Z',
|
||||
version: 'WzIxNSwxXQ==',
|
||||
score: 0,
|
||||
},
|
||||
{
|
||||
type: 'ingest-package-policies',
|
||||
id: 'dcf83172-c38e-4501-b236-9f479da8a7d6',
|
||||
attributes: {
|
||||
name: 'system-2',
|
||||
description: '',
|
||||
namespace: 'default',
|
||||
policy_id: '11111-111111-11111-11111',
|
||||
enabled: true,
|
||||
output_id: '',
|
||||
inputs: [],
|
||||
package: { name: 'system', title: 'System', version: '0.10.4' },
|
||||
revision: 1,
|
||||
created_at: '2020-12-22T21:28:05.380Z',
|
||||
created_by: 'elastic',
|
||||
updated_at: '2020-12-22T21:28:05.380Z',
|
||||
updated_by: 'elastic',
|
||||
},
|
||||
references: [],
|
||||
migrationVersion: { 'ingest-package-policies': '7.11.0' },
|
||||
updated_at: '2020-12-22T21:28:05.383Z',
|
||||
version: 'WzE1NTAsMV0=',
|
||||
score: 0,
|
||||
},
|
||||
{
|
||||
type: 'ingest-package-policies',
|
||||
id: 'dcf83172-c38e-4501-b236-9f479da8a7d6',
|
||||
attributes: {
|
||||
name: 'system-4',
|
||||
description: '',
|
||||
namespace: 'default',
|
||||
policy_id: '33333-33333-333333-333333',
|
||||
enabled: true,
|
||||
output_id: '',
|
||||
inputs: [],
|
||||
package: { name: 'system', title: 'System', version: '0.10.4' },
|
||||
revision: 1,
|
||||
created_at: '2020-12-22T21:28:05.380Z',
|
||||
created_by: 'elastic',
|
||||
updated_at: '2020-12-22T21:28:05.380Z',
|
||||
updated_by: 'elastic',
|
||||
},
|
||||
references: [],
|
||||
migrationVersion: { 'ingest-package-policies': '7.11.0' },
|
||||
updated_at: '2020-12-22T21:28:05.383Z',
|
||||
version: 'WzE1NTAsMV0=',
|
||||
score: 0,
|
||||
},
|
||||
];
|
||||
soClient.find.mockImplementation(async ({ page = 1, perPage = 20 }) => {
|
||||
let savedObjectsResponse: typeof savedObjects;
|
||||
|
||||
switch (page) {
|
||||
case 1:
|
||||
savedObjectsResponse = [savedObjects[0]];
|
||||
break;
|
||||
case 2:
|
||||
savedObjectsResponse = savedObjects.slice(1);
|
||||
break;
|
||||
default:
|
||||
savedObjectsResponse = [];
|
||||
}
|
||||
|
||||
return {
|
||||
page,
|
||||
per_page: perPage,
|
||||
total: 1500,
|
||||
saved_objects: savedObjectsResponse,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should query and paginate SO using package name as filter', async () => {
|
||||
await getPackageUsageStats({ savedObjectsClient: soClient, pkgName: 'system' });
|
||||
expect(soClient.find).toHaveBeenNthCalledWith(1, {
|
||||
type: PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
perPage: 1000,
|
||||
page: 1,
|
||||
filter: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.package.name: system`,
|
||||
});
|
||||
expect(soClient.find).toHaveBeenNthCalledWith(2, {
|
||||
type: PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
perPage: 1000,
|
||||
page: 2,
|
||||
filter: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.package.name: system`,
|
||||
});
|
||||
expect(soClient.find).toHaveBeenNthCalledWith(3, {
|
||||
type: PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
perPage: 1000,
|
||||
page: 3,
|
||||
filter: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.package.name: system`,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return count of unique agent policies', async () => {
|
||||
expect(
|
||||
await getPackageUsageStats({ savedObjectsClient: soClient, pkgName: 'system' })
|
||||
).toEqual({
|
||||
agent_policy_count: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,7 +5,13 @@
|
|||
*/
|
||||
|
||||
import { SavedObjectsClientContract, SavedObjectsFindOptions } from 'src/core/server';
|
||||
import { isPackageLimited, installationStatuses } from '../../../../common';
|
||||
import {
|
||||
isPackageLimited,
|
||||
installationStatuses,
|
||||
PackageUsageStats,
|
||||
PackagePolicySOAttributes,
|
||||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
} from '../../../../common';
|
||||
import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants';
|
||||
import { ArchivePackage, RegistryPackage, EpmPackageAdditions } from '../../../../common/types';
|
||||
import { Installation, PackageInfo, KibanaAssetType } from '../../../types';
|
||||
|
@ -13,6 +19,7 @@ import * as Registry from '../registry';
|
|||
import { createInstallableFrom, isRequiredPackage } from './index';
|
||||
import { getEsPackage } from '../archive/storage';
|
||||
import { getArchivePackage } from '../archive';
|
||||
import { normalizeKuery } from '../../saved_object';
|
||||
|
||||
export { getFile, SearchParams } from '../registry';
|
||||
|
||||
|
@ -116,6 +123,43 @@ export async function getPackageInfo(options: {
|
|||
return createInstallableFrom(updated, savedObject);
|
||||
}
|
||||
|
||||
export const getPackageUsageStats = async ({
|
||||
savedObjectsClient,
|
||||
pkgName,
|
||||
}: {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
pkgName: string;
|
||||
}): Promise<PackageUsageStats> => {
|
||||
const filter = normalizeKuery(
|
||||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
`${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: ${pkgName}`
|
||||
);
|
||||
const agentPolicyCount = new Set<string>();
|
||||
let page = 1;
|
||||
let hasMore = true;
|
||||
|
||||
while (hasMore) {
|
||||
// using saved Objects client directly, instead of the `list()` method of `package_policy` service
|
||||
// in order to not cause a circular dependency (package policy service imports from this module)
|
||||
const packagePolicies = await savedObjectsClient.find<PackagePolicySOAttributes>({
|
||||
type: PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
perPage: 1000,
|
||||
page: page++,
|
||||
filter,
|
||||
});
|
||||
|
||||
for (let index = 0, total = packagePolicies.saved_objects.length; index < total; index++) {
|
||||
agentPolicyCount.add(packagePolicies.saved_objects[index].attributes.policy_id);
|
||||
}
|
||||
|
||||
hasMore = packagePolicies.saved_objects.length > 0;
|
||||
}
|
||||
|
||||
return {
|
||||
agent_policy_count: agentPolicyCount.size,
|
||||
};
|
||||
};
|
||||
|
||||
interface PackageResponse {
|
||||
paths: string[];
|
||||
packageInfo: ArchivePackage | RegistryPackage;
|
||||
|
|
|
@ -32,6 +32,12 @@ export const GetInfoRequestSchema = {
|
|||
}),
|
||||
};
|
||||
|
||||
export const GetStatsRequestSchema = {
|
||||
params: schema.object({
|
||||
pkgName: schema.string(),
|
||||
}),
|
||||
};
|
||||
|
||||
export const InstallPackageFromRegistryRequestSchema = {
|
||||
params: schema.object({
|
||||
pkgkey: schema.string(),
|
||||
|
|
Loading…
Reference in a new issue