Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,14 @@ describe('When using EPM `get` services', () => {
});
});

it('should query and paginate SO using package name as filter', async () => {
it('should query and paginate SO using package name and NOT latest_revision:false filter', async () => {
await getPackageUsageStats({ savedObjectsClient: soClient, pkgName: 'system' });
expect(soClient.find).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
type: PACKAGE_POLICY_SAVED_OBJECT_TYPE,
perPage: 10000,
filter: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.package.name: system`,
filter: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.package.name: system AND NOT ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.latest_revision: false`,
})
);
});
Expand All @@ -218,6 +218,71 @@ describe('When using EPM `get` services', () => {
package_policy_count: 4,
});
});

it('should exclude :prev (latest_revision:false) policies from the count', async () => {
const soClientPrev = savedObjectsClientMock.create();
// Mix of current policies and a :prev policy that would be returned before
// the filter fix — the filter now excludes it, so mock returns only current ones.
soClientPrev.find.mockResolvedValue({
page: 1,
per_page: 10000,
total: 2,
saved_objects: [
{
type: 'ingest-package-policies',
id: 'policy-1',
attributes: {
name: 'system-1',
namespace: 'default',
package: { name: 'system', title: 'System', version: '1.0.0' },
enabled: true,
policy_id: 'ap-1',
policy_ids: ['ap-1'],
inputs: [],
revision: 2,
created_at: '2020-01-01T00:00:00.000Z',
created_by: 'elastic',
updated_at: '2020-01-01T00:00:00.000Z',
updated_by: 'elastic',
},
references: [],
score: 0,
},
{
type: 'ingest-package-policies',
id: 'policy-2',
attributes: {
name: 'system-2',
namespace: 'default',
package: { name: 'system', title: 'System', version: '1.0.0' },
enabled: true,
policy_id: 'ap-2',
policy_ids: ['ap-2'],
inputs: [],
revision: 2,
created_at: '2020-01-01T00:00:00.000Z',
created_by: 'elastic',
updated_at: '2020-01-01T00:00:00.000Z',
updated_by: 'elastic',
},
references: [],
score: 0,
},
],
});

const result = await getPackageUsageStats({
savedObjectsClient: soClientPrev,
pkgName: 'system',
});

// 2 current policies, not 3 (the :prev one is excluded by the filter)
expect(result.package_policy_count).toBe(2);
// Verify the filter contains the NOT latest_revision:false clause
const [[callArgs]] = soClientPrev.find.mock.calls;
expect(callArgs.filter).toContain('NOT');
expect(callArgs.filter).toContain('latest_revision');
});
});

describe('getPackages', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ export const getPackageUsageStats = async ({

const filter = normalizeKuery(
packagePolicySavedObjectType,
`${packagePolicySavedObjectType}.package.name: ${pkgName}`
`${packagePolicySavedObjectType}.package.name: ${pkgName} AND NOT ${packagePolicySavedObjectType}.latest_revision: false`
);
const agentPolicyCount = new Set<string>();
// using saved Objects client directly, instead of the `list()` method of `package_policy` service
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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 { savedObjectsClientMock } from '@kbn/core/server/mocks';

import { getPackagePoliciesCountByPackageName } from './package_policies_aggregation';

// The mock resolves to the legacy (non-space-aware) type, matching a self-managed deployment.
const MOCKED_SO_TYPE = 'ingest-package-policies';

jest.mock('../package_policy', () => ({
getPackagePolicySavedObjectType: jest.fn().mockResolvedValue('ingest-package-policies'),
}));

describe('getPackagePoliciesCountByPackageName', () => {
it('uses NOT latest_revision:false filter so policies without the field are included', async () => {
const soClient = savedObjectsClientMock.create();
soClient.find.mockResolvedValue({
page: 1,
per_page: 0,
total: 0,
saved_objects: [],
aggregations: {
count_by_package_name: { buckets: [] },
},
});

await getPackagePoliciesCountByPackageName(soClient);

expect(soClient.find).toHaveBeenCalledWith(
expect.objectContaining({
filter: `NOT ${MOCKED_SO_TYPE}.attributes.latest_revision:false`,
})
);
});

it('includes a size parameter in the terms aggregation to avoid ES default truncation at 10 buckets', async () => {
const soClient = savedObjectsClientMock.create();
soClient.find.mockResolvedValue({
page: 1,
per_page: 0,
total: 0,
saved_objects: [],
aggregations: {
count_by_package_name: { buckets: [] },
},
});

await getPackagePoliciesCountByPackageName(soClient);

expect(soClient.find).toHaveBeenCalledWith(
expect.objectContaining({
aggs: expect.objectContaining({
count_by_package_name: expect.objectContaining({
terms: expect.objectContaining({
size: expect.any(Number),
}),
}),
}),
})
);
const [[callArgs]] = soClient.find.mock.calls;
const termsSize = (callArgs.aggs as any)?.count_by_package_name?.terms?.size;
expect(termsSize).toBeGreaterThan(10);
});

it('returns a map of package name to count', async () => {
const soClient = savedObjectsClientMock.create();
soClient.find.mockResolvedValue({
page: 1,
per_page: 0,
total: 3,
saved_objects: [],
aggregations: {
count_by_package_name: {
buckets: [
{ key: 'nginx', doc_count: 2 },
{ key: 'system', doc_count: 1 },
],
},
},
});

const result = await getPackagePoliciesCountByPackageName(soClient);

expect(result).toEqual({ nginx: 2, system: 1 });
});

it('returns an empty object when there are no buckets', async () => {
const soClient = savedObjectsClientMock.create();
soClient.find.mockResolvedValue({
page: 1,
per_page: 0,
total: 0,
saved_objects: [],
aggregations: {
count_by_package_name: { buckets: [] },
},
});

const result = await getPackagePoliciesCountByPackageName(soClient);

expect(result).toEqual({});
});

it('returns an empty object when aggregations are absent', async () => {
const soClient = savedObjectsClientMock.create();
soClient.find.mockResolvedValue({
page: 1,
per_page: 0,
total: 0,
saved_objects: [],
});

const result = await getPackagePoliciesCountByPackageName(soClient);

expect(result).toEqual({});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import type { SavedObjectsClientContract } from '@kbn/core/server';

import { SO_SEARCH_LIMIT } from '../../../common';

import { getPackagePolicySavedObjectType } from '../package_policy';

export async function getPackagePoliciesCountByPackageName(soClient: SavedObjectsClientContract) {
Expand All @@ -18,11 +20,17 @@ export async function getPackagePoliciesCountByPackageName(soClient: SavedObject
>({
type: savedObjectType,
perPage: 0,
filter: `${savedObjectType}.attributes.latest_revision:true`,
// Use NOT false instead of :true so that policies without the field
// (8.x policies where latest_revision was never persisted to ES) are
// treated as current revisions and included in the count.
filter: `NOT ${savedObjectType}.attributes.latest_revision:false`,
aggs: {
count_by_package_name: {
terms: {
field: `${savedObjectType}.attributes.package.name`,
// Without an explicit size, ES defaults to 10 buckets and silently
// truncates results when more than 10 distinct package names exist.
size: SO_SEARCH_LIMIT,
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1994,6 +1994,42 @@ describe('Package policy service', () => {
});

describe('list', () => {
it('should use NOT latest_revision:false filter to include 8.x policies without the field', async () => {
const soClient = createSavedObjectClientMock();
soClient.find.mockResolvedValueOnce({
total: 0,
page: 1,
per_page: 20,
saved_objects: [],
});

await packagePolicyService.list(soClient, {});

expect(soClient.find).toHaveBeenCalledWith(
expect.objectContaining({
filter: `NOT ${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.latest_revision:false`,
})
);
});

it('should combine user kuery with NOT latest_revision:false filter', async () => {
const soClient = createSavedObjectClientMock();
soClient.find.mockResolvedValueOnce({
total: 0,
page: 1,
per_page: 20,
saved_objects: [],
});

await packagePolicyService.list(soClient, { kuery: 'ingest-package-policies.name: nginx' });

expect(soClient.find).toHaveBeenCalledWith(
expect.objectContaining({
filter: `NOT ${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.latest_revision:false AND (ingest-package-policies.attributes.name: nginx)`,
})
);
});

it('should call audit logger', async () => {
const soClient = createSavedObjectClientMock();
soClient.find.mockResolvedValueOnce({
Expand Down Expand Up @@ -10702,7 +10738,7 @@ describe('Package policy service', () => {
sortField: 'created_at',
sortOrder: 'asc',
fields: [],
filter: `${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.latest_revision:true`,
filter: `NOT ${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.latest_revision:false`,
})
);
});
Expand All @@ -10722,7 +10758,7 @@ describe('Package policy service', () => {
sortField: 'created_at',
sortOrder: 'asc',
fields: [],
filter: `${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.latest_revision:true AND (one=two)`,
filter: `NOT ${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.latest_revision:false AND (one=two)`,
})
);
});
Expand Down Expand Up @@ -10776,7 +10812,7 @@ describe('Package policy service', () => {
sortField: 'created_at',
sortOrder: 'asc',
fields: [],
filter: `${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.latest_revision:true`,
filter: `NOT ${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.latest_revision:false`,
})
);
});
Expand All @@ -10794,7 +10830,7 @@ describe('Package policy service', () => {
sortField: 'created_at',
sortOrder: 'asc',
fields: [],
filter: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.latest_revision:true`,
filter: `NOT ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.latest_revision:false`,
})
);
});
Expand All @@ -10815,7 +10851,7 @@ describe('Package policy service', () => {
perPage: 12,
sortField: 'updated_by',
sortOrder: 'desc',
filter: `${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.latest_revision:true AND (one=two)`,
filter: `NOT ${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.latest_revision:false AND (one=two)`,
})
);
});
Expand Down Expand Up @@ -12177,6 +12213,29 @@ describe('getCompiledVersionsForAgentPolicy()', () => {
expect(soClient.find).not.toHaveBeenCalled();
});

it('uses NOT latest_revision:false filter so 8.x policies are included', async () => {
const soClient = savedObjectsClientMock.create();
soClient.find.mockResolvedValue({
saved_objects: [],
total: 0,
per_page: 10000,
page: 1,
});

await getCompiledVersionsForAgentPolicy(soClient, 'agent-policy-1');

expect(soClient.find).toHaveBeenCalledWith(
expect.objectContaining({
filter: expect.stringContaining('NOT'),
})
);
expect(soClient.find).toHaveBeenCalledWith(
expect.objectContaining({
filter: expect.not.stringContaining('latest_revision:true'),
})
);
});

it('returns empty array when no package policies have inputs_for_versions', async () => {
const soClient = savedObjectsClientMock.create();
soClient.find.mockResolvedValue({
Expand Down
Loading
Loading