[Fleet] Add 'No Tags' filter to agent table (#216944)

## Summary

Closes #213933 

Adds a `No Tags` filter to the filter select on the agents table so that
users can filter for agents without tags.


https://github.com/user-attachments/assets/1223d049-1f21-4565-854c-75776ae4609b



### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This was checked for breaking HTTP API changes, and any breaking
changes have been approved by the breaking-change committee. The
`release_note:breaking` label should be applied in these situations.
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

N/A
This commit is contained in:
Mason Herron 2025-04-08 11:47:27 -06:00 committed by GitHub
parent fb961ec880
commit b0c0917fa7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 31 additions and 5 deletions

View file

@ -32,7 +32,9 @@ import { getKuery } from '../utils/get_kuery';
const REFRESH_INTERVAL_MS = 30000;
const MAX_AGENT_ACTIONS = 100;
const NO_TAGS_VALUE = i18n.translate('xpack.fleet.noTagsValue', {
defaultMessage: 'No Tags',
});
/** Allow to fetch full agent policy using a cache */
function useFullAgentPolicyFetcher() {
const authz = useAuthz();
@ -287,7 +289,7 @@ export function useFetchAgentsData() {
setAgentsStatus(agentStatusesToSummary(statusSummary));
const newAllTags = agentTagsResponse.data.items;
const newAllTags = [...agentTagsResponse.data.items, NO_TAGS_VALUE];
// We only want to update the list of available tags if
// - We haven't set any tags yet
// - We've received the "refreshTags" flag which will force a refresh of the tags list when an agent is unenrolled

View file

@ -10,6 +10,7 @@ import { getKuery } from './get_kuery';
describe('getKuery', () => {
const search = 'base search';
const selectedTags = ['tag_1', 'tag_2', 'tag_3'];
const noTags = ['No Tags'];
const selectedAgentIds = ['agent_id1', 'agent_id2'];
const selectedAgentPolicies = ['policy1', 'policy2', 'policy3'];
const selectedStatus = ['healthy', 'unhealthy'];
@ -25,6 +26,14 @@ describe('getKuery', () => {
'fleet-agents.tags : ("tag_1" or "tag_2" or "tag_3")'
);
});
it('should return a kuery for no tags if selected', () => {
expect(getKuery({ selectedTags: noTags })).toEqual('((NOT fleet-agents.tags : (*)))');
});
it('should return a kuery for no tags and other tags when multiple are selected', () => {
expect(getKuery({ selectedTags: [...noTags, ...selectedTags] })).toEqual(
'((NOT fleet-agents.tags : (*)) or fleet-agents.tags : ("tag_1" or "tag_2" or "tag_3"))'
);
});
it('should return a kuery with selected agent policies', () => {
expect(getKuery({ selectedAgentPolicies })).toEqual(

View file

@ -4,10 +4,14 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import { AgentStatusKueryHelper } from '../../../../services';
import { AGENTS_PREFIX } from '../../../../constants';
const NO_TAGS_VALUE = i18n.translate('xpack.fleet.noTagsValue', {
defaultMessage: 'No Tags',
});
export const getKuery = ({
search,
selectedAgentPolicies,
@ -39,9 +43,20 @@ export const getKuery = ({
if (kueryBuilder) {
kueryBuilder = `(${kueryBuilder}) and`;
}
kueryBuilder = `${kueryBuilder} ${AGENTS_PREFIX}.tags : (${selectedTags
.map((tag) => `"${tag}"`)
.join(' or ')})`;
// if the user selects no tags as one of the items, we should remove it from the initial array and have its own condition
if (selectedTags.includes(NO_TAGS_VALUE)) {
const filteredTags = selectedTags.filter((tag) => tag !== NO_TAGS_VALUE);
// add the no tags condition, and if there are remaining tags, also add them, otherwise add nothing else but the closing parentheses
kueryBuilder = `${kueryBuilder} ((NOT ${AGENTS_PREFIX}.tags : (*))${
filteredTags.length > 0
? ` or ${AGENTS_PREFIX}.tags : (${filteredTags.map((tag) => `"${tag}"`).join(' or ')}))`
: ')'
}`;
} else {
kueryBuilder = `${kueryBuilder} ${AGENTS_PREFIX}.tags : (${selectedTags
.map((tag) => `"${tag}"`)
.join(' or ')})`;
}
}
if (selectedAgentIds?.length) {