[EDR Workflows] Fix agent count for single agent policies (#194294)

https://github.com/user-attachments/assets/2b64c1e0-0e6d-4ef5-952d-e4364b4403c4



The PR #193705 introduced an issue when counting active agents for
integration policies with only one agent policy assigned. In such cases,
`query.policyIds` was treated as a single string instead of an array of
strings (as expected with multiple agent policy ids like
`/?policyIds=x&policyIds=y`). This PR resolves the issue by ensuring
consistent handling of policyIds, regardless of the number of associated
agent policies.
This commit is contained in:
Konrad Szwarc 2024-09-27 23:22:28 +02:00 committed by GitHub
parent 878ba134e9
commit 847285ba71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 69 additions and 14 deletions

View file

@ -7,7 +7,9 @@
import { coreMock, httpServerMock } from '@kbn/core/server/mocks';
import { getAvailableVersionsHandler } from './handlers';
import { getAgentStatusForAgentPolicy } from '../../services/agents/status';
import { getAgentStatusForAgentPolicyHandler, getAvailableVersionsHandler } from './handlers';
jest.mock('../../services/agents/versions', () => {
return {
@ -24,16 +26,60 @@ jest.mock('../../services/app_context', () => {
};
});
describe('getAvailableVersionsHandler', () => {
it('should return the value from getAvailableVersions', async () => {
const ctx = coreMock.createCustomRequestHandlerContext(coreMock.createRequestHandlerContext());
const response = httpServerMock.createResponseFactory();
jest.mock('../../services/agents/status', () => ({
getAgentStatusForAgentPolicy: jest.fn(),
}));
await getAvailableVersionsHandler(ctx, httpServerMock.createKibanaRequest(), response);
describe('Handlers', () => {
describe('getAgentStatusForAgentPolicyHandler', () => {
it.each([
{ requested: 'policy-id-1', called: ['policy-id-1'] },
{ requested: ['policy-id-2'], called: ['policy-id-2'] },
{ requested: ['policy-id-3', 'policy-id-4'], called: ['policy-id-3', 'policy-id-4'] },
...[undefined, '', []].map((requested) => ({ requested, called: undefined })),
])('calls getAgentStatusForAgentPolicy with correct parameters', async (item) => {
const request = {
query: {
policyId: 'policy-id',
kuery: 'kuery',
policyIds: item.requested,
},
};
const response = httpServerMock.createResponseFactory();
expect(response.ok).toBeCalled();
expect(response.ok.mock.calls[0][0]?.body).toEqual({
items: ['8.1.0', '8.0.0', '7.17.0'],
await getAgentStatusForAgentPolicyHandler(
{
core: coreMock.createRequestHandlerContext(),
fleet: { internalSoClient: {} },
} as any,
request as any,
response
);
expect(getAgentStatusForAgentPolicy).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
'policy-id',
'kuery',
undefined,
item.called
);
});
});
describe('getAvailableVersionsHandler', () => {
it('should return the value from getAvailableVersions', async () => {
const ctx = coreMock.createCustomRequestHandlerContext(
coreMock.createRequestHandlerContext()
);
const response = httpServerMock.createResponseFactory();
await getAvailableVersionsHandler(ctx, httpServerMock.createKibanaRequest(), response);
expect(response.ok).toBeCalled();
expect(response.ok.mock.calls[0][0]?.body).toEqual({
items: ['8.1.0', '8.0.0', '7.17.0'],
});
});
});
});

View file

@ -323,13 +323,22 @@ export const getAgentStatusForAgentPolicyHandler: FleetRequestHandler<
const [coreContext, fleetContext] = await Promise.all([context.core, context.fleet]);
const esClient = coreContext.elasticsearch.client.asInternalUser;
const soClient = fleetContext.internalSoClient;
const parsePolicyIds = (policyIds: string | string[] | undefined): string[] | undefined => {
if (!policyIds || !policyIds.length) {
return undefined;
}
return Array.isArray(policyIds) ? policyIds : [policyIds];
};
const results = await getAgentStatusForAgentPolicy(
esClient,
soClient,
request.query.policyId,
request.query.kuery,
coreContext.savedObjects.client.getCurrentNamespace(),
request.query.policyIds
parsePolicyIds(request.query.policyIds)
);
const body: GetAgentStatusResponse = { results };

View file

@ -241,7 +241,7 @@ export const PostBulkUpdateAgentTagsRequestSchema = {
export const GetAgentStatusRequestSchema = {
query: schema.object({
policyId: schema.maybe(schema.string()),
policyIds: schema.maybe(schema.arrayOf(schema.string())),
policyIds: schema.maybe(schema.oneOf([schema.arrayOf(schema.string()), schema.string()])),
kuery: schema.maybe(
schema.string({
validate: (value: string) => {

View file

@ -96,7 +96,7 @@ export const policySettingsMiddlewareRunner: MiddlewareRunner = async (
// Agent summary is secondary data, so its ok for it to come after the details
// page is populated with the main content
if (policyItem.policy_id) {
if (policyItem.policy_ids?.length) {
const { results } = await sendGetFleetAgentStatusForPolicy(http, policyItem.policy_ids);
dispatch({
type: 'serverReturnedPolicyDetailsAgentSummaryData',

View file

@ -83,7 +83,7 @@ export const sendPutPackagePolicy = (
};
/**
* Get a status summary for all Agents that are currently assigned to a given agent policy
* Get a status summary for all Agents that are currently assigned to a given agent policies
*
* @param http
* @param policyIds
@ -91,7 +91,7 @@ export const sendPutPackagePolicy = (
*/
export const sendGetFleetAgentStatusForPolicy = (
http: HttpStart,
/** the Agent (fleet) policy id */
/** the Agent (fleet) policy ids */
policyIds: string[],
options: Exclude<HttpFetchOptions, 'query'> = {}
): Promise<GetAgentStatusResponse> => {