diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx index 8965ae9bbc44f5..7c9f7232dbf1f4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx @@ -67,6 +67,9 @@ export const SearchAndFilterBar: React.FunctionComponent<{ onSelectedStatusChange: (selectedStatus: string[]) => void; showUpgradeable: boolean; onShowUpgradeableChange: (showUpgradeable: boolean) => void; + tags: string[]; + selectedTags: string[]; + onSelectedTagsChange: (selectedTags: string[]) => void; }> = ({ agentPolicies, draftKuery, @@ -78,6 +81,9 @@ export const SearchAndFilterBar: React.FunctionComponent<{ onSelectedStatusChange, showUpgradeable, onShowUpgradeableChange, + tags, + selectedTags, + onSelectedTagsChange, }) => { const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); @@ -85,7 +91,9 @@ export const SearchAndFilterBar: React.FunctionComponent<{ const [isAgentPoliciesFilterOpen, setIsAgentPoliciesFilterOpen] = useState(false); // Status for filtering - const [isStatusFilterOpen, setIsStatutsFilterOpen] = useState(false); + const [isStatusFilterOpen, setIsStatusFilterOpen] = useState(false); + + const [isTagsFilterOpen, setIsTagsFilterOpen] = useState(false); // Add a agent policy id to current search const addAgentPolicyFilter = (policyId: string) => { @@ -99,6 +107,14 @@ export const SearchAndFilterBar: React.FunctionComponent<{ ); }; + const addTagsFilter = (tag: string) => { + onSelectedTagsChange([...selectedTags, tag]); + }; + + const removeTagsFilter = (tag: string) => { + onSelectedTagsChange(selectedTags.filter((t) => t !== tag)); + }; + return ( <> {isEnrollmentFlyoutOpen ? ( @@ -131,7 +147,7 @@ export const SearchAndFilterBar: React.FunctionComponent<{ button={ setIsStatutsFilterOpen(!isStatusFilterOpen)} + onClick={() => setIsStatusFilterOpen(!isStatusFilterOpen)} isSelected={isStatusFilterOpen} hasActiveFilters={selectedStatus.length > 0} disabled={agentPolicies.length === 0} @@ -144,7 +160,7 @@ export const SearchAndFilterBar: React.FunctionComponent<{ } isOpen={isStatusFilterOpen} - closePopover={() => setIsStatutsFilterOpen(false)} + closePopover={() => setIsStatusFilterOpen(false)} panelPaddingSize="none" >
@@ -165,6 +181,45 @@ export const SearchAndFilterBar: React.FunctionComponent<{ ))}
+ setIsTagsFilterOpen(!isTagsFilterOpen)} + isSelected={isTagsFilterOpen} + hasActiveFilters={selectedTags.length > 0} + disabled={tags.length === 0} + data-test-subj="agentList.tagsFilter" + > + + + } + isOpen={isTagsFilterOpen} + closePopover={() => setIsTagsFilterOpen(false)} + panelPaddingSize="none" + > +
+ {tags.map((tag, index) => ( + { + if (selectedTags.includes(tag)) { + removeTagsFilter(tag); + } else { + addTagsFilter(tag); + } + }} + > + {tag} + + ))} +
+
= ({ tags }) => { +export const Tags: React.FunctionComponent = ({ tags }) => { return ( <> {tags.length > MAX_TAGS_TO_DISPLAY ? ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index 96a675ff9dccbf..b7851c93e8624b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -59,7 +59,7 @@ import { agentFlyoutContext } from '..'; import { AgentTableHeader } from './components/table_header'; import type { SelectionMode } from './components/bulk_actions'; import { SearchAndFilterBar } from './components/search_and_filter_bar'; -import { Labels } from './components/labels'; +import { Tags } from './components/tags'; const MOCK_TAGS = [ 'linux', @@ -200,14 +200,21 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { // Status for filtering const [selectedStatus, setSelectedStatus] = useState([]); + const [selectedTags, setSelectedTags] = useState([]); + const isUsingFilter = - search.trim() || selectedAgentPolicies.length || selectedStatus.length || showUpgradeable; + search.trim() || + selectedAgentPolicies.length || + selectedStatus.length || + selectedTags.length || + showUpgradeable; const clearFilters = useCallback(() => { setDraftKuery(''); setSearch(''); setSelectedAgentPolicies([]); setSelectedStatus([]); + setSelectedTags([]); setShowUpgradeable(false); }, [setSearch, setDraftKuery, setSelectedAgentPolicies, setSelectedStatus, setShowUpgradeable]); @@ -237,6 +244,11 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { .map((agentPolicy) => `"${agentPolicy}"`) .join(' or ')})`; } + + if (selectedTags.length) { + kueryBuilder = `${kueryBuilder} and ${AGENTS_PREFIX}.tags : (${selectedTags.join(' or ')})`; + } + if (selectedStatus.length) { const kueryStatus = selectedStatus .map((status) => { @@ -266,7 +278,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { } return kueryBuilder; - }, [selectedStatus, selectedAgentPolicies, search]); + }, [search, selectedAgentPolicies, selectedTags, selectedStatus]); const showInactive = useMemo(() => { return selectedStatus.includes('inactive'); @@ -280,6 +292,8 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { const [totalAgents, setTotalAgents] = useState(0); const [totalInactiveAgents, setTotalInactiveAgents] = useState(0); + const allTags = Array.from(new Set(agents.flatMap((agent) => agent.tags ?? []))); + // Request to fetch agents and agent status const currentRequestRef = useRef(0); const fetchData = useCallback(() => { @@ -328,7 +342,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { setAgents( agentsRequest.data.items.map((item) => { - // TEMP: Mock tags data for building out label UI + // TEMP: Mock tags data for building out tags UI item.tags = sampleSize(MOCK_TAGS, Math.floor(Math.random() * MOCK_TAGS.length)); return item; }) @@ -430,10 +444,10 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { { field: 'tags', width: '240px', - name: i18n.translate('xpack.fleet.agentList.labelColumnTitle', { - defaultMessage: 'Label', + name: i18n.translate('xpack.fleet.agentList.tagsColumnTitle', { + defaultMessage: 'Tags', }), - render: (tags: string[], agent: any) => , + render: (tags: string[], agent: any) => , }, { field: 'policy_id', @@ -622,6 +636,9 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { onSelectedStatusChange={setSelectedStatus} showUpgradeable={showUpgradeable} onShowUpgradeableChange={setShowUpgradeable} + tags={allTags} + selectedTags={selectedTags} + onSelectedTagsChange={setSelectedTags} />