[Fleet] fix upgradeable agents filter (#119338) (#119711)

* fix upgradeable

* added unit tests

* fix review comments

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Julia Bardi <90178898+juliaElastic@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2021-11-25 06:22:37 -05:00 committed by GitHub
parent 214b790b20
commit 3ebb780e52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 226 additions and 15 deletions

View file

@ -0,0 +1,195 @@
/*
* 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 type { ElasticsearchClient } from 'kibana/server';
import type { Agent } from '../../types';
import { getAgentsByKuery } from './crud';
jest.mock('../../../common', () => ({
...jest.requireActual('../../../common'),
isAgentUpgradeable: jest.fn().mockImplementation((agent: Agent) => agent.id.includes('up')),
}));
describe('Agents CRUD test', () => {
let esClientMock: ElasticsearchClient;
let searchMock: jest.Mock;
describe('getAgentsByKuery', () => {
beforeEach(() => {
searchMock = jest.fn();
esClientMock = {
search: searchMock,
} as unknown as ElasticsearchClient;
});
function getEsResponse(ids: string[], total: number) {
return {
body: {
hits: {
total: { value: total },
hits: ids.map((id: string) => ({
_id: id,
_source: {},
})),
},
},
};
}
it('should return upgradeable on first page', async () => {
searchMock
.mockImplementationOnce(() => Promise.resolve(getEsResponse(['1', '2', '3', '4', '5'], 7)))
.mockImplementationOnce(() =>
Promise.resolve(getEsResponse(['1', '2', '3', '4', '5', 'up', '7'], 7))
);
const result = await getAgentsByKuery(esClientMock, {
showUpgradeable: true,
showInactive: false,
page: 1,
perPage: 5,
});
expect(result).toEqual({
agents: [
{
access_api_key: undefined,
id: 'up',
packages: [],
policy_revision: undefined,
status: 'inactive',
},
],
page: 1,
perPage: 5,
total: 1,
});
});
it('should return upgradeable from all pages', async () => {
searchMock
.mockImplementationOnce(() => Promise.resolve(getEsResponse(['1', '2', '3', 'up', '5'], 7)))
.mockImplementationOnce(() =>
Promise.resolve(getEsResponse(['1', '2', '3', 'up', '5', 'up2', '7'], 7))
);
const result = await getAgentsByKuery(esClientMock, {
showUpgradeable: true,
showInactive: false,
page: 1,
perPage: 5,
});
expect(result).toEqual({
agents: [
{
access_api_key: undefined,
id: 'up',
packages: [],
policy_revision: undefined,
status: 'inactive',
},
{
access_api_key: undefined,
id: 'up2',
packages: [],
policy_revision: undefined,
status: 'inactive',
},
],
page: 1,
perPage: 5,
total: 2,
});
});
it('should return upgradeable on second page', async () => {
searchMock
.mockImplementationOnce(() => Promise.resolve(getEsResponse(['up6', '7'], 7)))
.mockImplementationOnce(() =>
Promise.resolve(getEsResponse(['up1', 'up2', 'up3', 'up4', 'up5', 'up6', '7'], 7))
);
const result = await getAgentsByKuery(esClientMock, {
showUpgradeable: true,
showInactive: false,
page: 2,
perPage: 5,
});
expect(result).toEqual({
agents: [
{
access_api_key: undefined,
id: 'up6',
packages: [],
policy_revision: undefined,
status: 'inactive',
},
],
page: 2,
perPage: 5,
total: 6,
});
});
it('should return upgradeable from one page when total is more than limit', async () => {
searchMock.mockImplementationOnce(() =>
Promise.resolve(getEsResponse(['1', '2', '3', 'up', '5'], 10001))
);
const result = await getAgentsByKuery(esClientMock, {
showUpgradeable: true,
showInactive: false,
page: 1,
perPage: 5,
});
expect(result).toEqual({
agents: [
{
access_api_key: undefined,
id: 'up',
packages: [],
policy_revision: undefined,
status: 'inactive',
},
],
page: 1,
perPage: 5,
total: 10001,
});
});
it('should return second page', async () => {
searchMock.mockImplementationOnce(() => Promise.resolve(getEsResponse(['6', '7'], 7)));
const result = await getAgentsByKuery(esClientMock, {
showUpgradeable: false,
showInactive: false,
page: 2,
perPage: 5,
});
expect(result).toEqual({
agents: [
{
access_api_key: undefined,
id: '6',
packages: [],
policy_revision: undefined,
status: 'inactive',
},
{
access_api_key: undefined,
id: '7',
packages: [],
policy_revision: undefined,
status: 'inactive',
},
],
page: 2,
perPage: 5,
total: 7,
});
});
});
});

View file

@ -122,30 +122,46 @@ export async function getAgentsByKuery(
const kueryNode = _joinFilters(filters);
const body = kueryNode ? { query: toElasticsearchQuery(kueryNode) } : {};
const res = await esClient.search<FleetServerAgent, {}>({
index: AGENTS_INDEX,
from: (page - 1) * perPage,
size: perPage,
track_total_hits: true,
ignore_unavailable: true,
body: {
...body,
sort: [{ [sortField]: { order: sortOrder } }],
},
});
const queryAgents = async (from: number, size: number) =>
esClient.search<FleetServerAgent, {}>({
index: AGENTS_INDEX,
from,
size,
track_total_hits: true,
ignore_unavailable: true,
body: {
...body,
sort: [{ [sortField]: { order: sortOrder } }],
},
});
const res = await queryAgents((page - 1) * perPage, perPage);
let agents = res.body.hits.hits.map(searchHitToAgent);
let total = (res.body.hits.total as estypes.SearchTotalHits).value;
// filtering for a range on the version string will not work,
// nor does filtering on a flattened field (local_metadata), so filter here
if (showUpgradeable) {
agents = agents.filter((agent) =>
isAgentUpgradeable(agent, appContextService.getKibanaVersion())
);
// fixing a bug where upgradeable filter was not returning right results https://github.com/elastic/kibana/issues/117329
// query all agents, then filter upgradeable, and return the requested page and correct total
// if there are more than SO_SEARCH_LIMIT agents, the logic falls back to same as before
if (total < SO_SEARCH_LIMIT) {
const response = await queryAgents(0, SO_SEARCH_LIMIT);
agents = response.body.hits.hits
.map(searchHitToAgent)
.filter((agent) => isAgentUpgradeable(agent, appContextService.getKibanaVersion()));
total = agents.length;
const start = (page - 1) * perPage;
agents = agents.slice(start, start + perPage);
} else {
agents = agents.filter((agent) =>
isAgentUpgradeable(agent, appContextService.getKibanaVersion())
);
}
}
return {
agents,
total: (res.body.hits.total as estypes.SearchTotalHits).value,
total,
page,
perPage,
};