Skip to content

Commit

Permalink
[Fleet] Stop fetching all full or with agentCount agent policies (#19…
Browse files Browse the repository at this point in the history
  • Loading branch information
nchaulet authored Aug 30, 2024
1 parent 74a551e commit e433dc2
Show file tree
Hide file tree
Showing 13 changed files with 307 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
sortField: sorting?.field,
sortOrder: sorting?.direction,
kuery: search,
noAgentCount: false, // Explicitly fetch agent count
full: true,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ jest.mock('../../../../hooks', () => ({
...jest.requireActual('../../../../hooks'),
sendGetAgents: jest.fn().mockResolvedValue({
data: {
statusSummary: {},
items: [
{
id: 'agent123',
policy_id: 'agent-policy-1',
},
],
total: 5,
},
}),
Expand All @@ -32,6 +39,19 @@ jest.mock('../../../../hooks', () => ({
totalInactive: 2,
},
}),
sendBulkGetAgentPolicies: jest.fn().mockReturnValue({
data: {
items: [
{ id: 'agent-policy-1', name: 'Agent policy 1', namespace: 'default' },
{
id: 'agent-policy-managed',
name: 'Managed Agent policy',
namespace: 'default',
managed: true,
},
],
},
}),
sendGetAgentPolicies: jest.fn().mockReturnValue({
data: {
items: [
Expand Down Expand Up @@ -104,7 +124,7 @@ describe('useFetchAgentsData', () => {
});

expect(result?.current.selectedStatus).toEqual(['healthy', 'unhealthy', 'updating', 'offline']);
expect(result?.current.agentPolicies).toEqual([
expect(result?.current.allAgentPolicies).toEqual([
{
id: 'agent-policy-1',
name: 'Agent policy 1',
Expand All @@ -124,17 +144,11 @@ describe('useFetchAgentsData', () => {
name: 'Agent policy 1',
namespace: 'default',
},
'agent-policy-managed': {
id: 'agent-policy-managed',
managed: true,
name: 'Managed Agent policy',
namespace: 'default',
},
});
expect(result?.current.kuery).toEqual(
'status:online or (status:error or status:degraded) or (status:updating or status:unenrolling or status:enrolling) or status:offline'
);
expect(result?.current.currentRequestRef).toEqual({ current: 1 });
expect(result?.current.currentRequestRef).toEqual({ current: 2 });
expect(result?.current.pagination).toEqual({ currentPage: 1, pageSize: 5 });
expect(result?.current.pageSizeOptions).toEqual([5, 20, 50]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
sendGetAgentPolicies,
useAuthz,
sendGetActionStatus,
sendBulkGetAgentPolicies,
} from '../../../../hooks';
import { AgentStatusKueryHelper, ExperimentalFeaturesService } from '../../../../services';
import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../../../constants';
Expand All @@ -32,8 +33,59 @@ import { getKuery } from '../utils/get_kuery';
const REFRESH_INTERVAL_MS = 30000;
const MAX_AGENT_ACTIONS = 100;

export function useFetchAgentsData() {
/** Allow to fetch full agent policy using a cache */
function useFullAgentPolicyFetcher() {
const authz = useAuthz();
const fetchedAgentPoliciesRef = useRef<{
[k: string]: AgentPolicy;
}>({});

const fetchPolicies = useCallback(
async (policiesIds: string[]) => {
const policiesToFetchIds = policiesIds.reduce((acc, policyId) => {
if (!fetchedAgentPoliciesRef.current[policyId]) {
acc.push(policyId);
}
return acc;
}, [] as string[]);

if (policiesToFetchIds.length) {
const bulkGetAgentPoliciesResponse = await sendBulkGetAgentPolicies(policiesToFetchIds, {
full: authz.fleet.readAgentPolicies,
});

if (bulkGetAgentPoliciesResponse.error) {
throw bulkGetAgentPoliciesResponse.error;
}

if (!bulkGetAgentPoliciesResponse.data) {
throw new Error('Invalid bulk GET agent policies response');
}
bulkGetAgentPoliciesResponse.data.items.forEach((agentPolicy) => {
fetchedAgentPoliciesRef.current[agentPolicy.id] = agentPolicy;
});
}

return policiesIds.reduce((acc, policyId) => {
if (fetchedAgentPoliciesRef.current[policyId]) {
acc.push(fetchedAgentPoliciesRef.current[policyId]);
}
return acc;
}, [] as AgentPolicy[]);
},
[authz.fleet.readAgentPolicies]
);

return useMemo(
() => ({
fetchPolicies,
}),
[fetchPolicies]
);
}

export function useFetchAgentsData() {
const fullAgentPolicyFecher = useFullAgentPolicyFetcher();
const { displayAgentMetrics } = ExperimentalFeaturesService.get();

const { notifications } = useStartServices();
Expand Down Expand Up @@ -117,6 +169,9 @@ export function useFetchAgentsData() {
const [totalInactiveAgents, setTotalInactiveAgents] = useState(0);
const [totalManagedAgentIds, setTotalManagedAgentIds] = useState<string[]>([]);
const [managedAgentsOnCurrentPage, setManagedAgentsOnCurrentPage] = useState(0);
const [agentPoliciesIndexedById, setAgentPoliciesIndexedByIds] = useState<{
[k: string]: AgentPolicy;
}>({});

const [latestAgentActionErrors, setLatestAgentActionErrors] = useState<string[]>([]);

Expand Down Expand Up @@ -180,7 +235,6 @@ export function useFetchAgentsData() {
}),
]);

isLoadingVar.current = false;
// Return if a newer request has been triggered
if (currentRequestRef.current !== currentRequest) {
return;
Expand Down Expand Up @@ -211,6 +265,25 @@ export function useFetchAgentsData() {
if (!statusSummary) {
throw new Error('Invalid GET /agents response - no status summary');
}
// Fetch agent policies, use a local cache
const policyIds = agentsResponse.data.items.map((agent) => agent.policy_id as string);

const policies = await fullAgentPolicyFecher.fetchPolicies(policyIds);

isLoadingVar.current = false;
// Return if a newe request has been triggerd
if (currentRequestRef.current !== currentRequest) {
return;
}

setAgentPoliciesIndexedByIds(
policies.reduce((acc, agentPolicy) => {
acc[agentPolicy.id] = agentPolicy;

return acc;
}, {} as { [k: string]: AgentPolicy })
);

setAgentsStatus(agentStatusesToSummary(statusSummary));

const newAllTags = agentTagsResponse.data.items;
Expand Down Expand Up @@ -264,6 +337,7 @@ export function useFetchAgentsData() {
setLatestAgentActionErrors(allRecentActionErrors);
}
} catch (error) {
isLoadingVar.current = false;
notifications.toasts.addError(error, {
title: i18n.translate('xpack.fleet.agentList.errorFetchingDataTitle', {
defaultMessage: 'Error fetching agents',
Expand All @@ -275,6 +349,7 @@ export function useFetchAgentsData() {
fetchDataAsync();
},
[
fullAgentPolicyFecher,
pagination.currentPage,
pagination.pageSize,
kuery,
Expand Down Expand Up @@ -302,20 +377,12 @@ export function useFetchAgentsData() {
const agentPoliciesRequest = useGetAgentPolicies({
page: 1,
perPage: SO_SEARCH_LIMIT,
full: authz.fleet.readAgentPolicies,
});

const agentPolicies = useMemo(
() => (agentPoliciesRequest.data ? agentPoliciesRequest.data.items : []),
[agentPoliciesRequest]
const allAgentPolicies = useMemo(
() => agentPoliciesRequest.data?.items || [],
[agentPoliciesRequest.data]
);
const agentPoliciesIndexedById = useMemo(() => {
return agentPolicies.reduce((acc, agentPolicy) => {
acc[agentPolicy.id] = agentPolicy;

return acc;
}, {} as { [k: string]: AgentPolicy });
}, [agentPolicies]);

return {
allTags,
Expand All @@ -341,7 +408,7 @@ export function useFetchAgentsData() {
setSelectedStatus,
selectedTags,
setSelectedTags,
agentPolicies,
allAgentPolicies,
agentPoliciesRequest,
agentPoliciesIndexedById,
pagination,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ jest.mock('../../../hooks', () => ({
},
useFleetStatus: jest.fn().mockReturnValue({}),
sendGetAgentStatus: jest.fn(),
sendBulkGetAgentPolicies: jest.fn().mockResolvedValue({
data: {
items: [
{ id: 'policy1', is_managed: false },
{ id: 'managed_policy', is_managed: true },
],
},
}),
sendGetAgentPolicies: jest.fn().mockResolvedValue({ data: { items: [] } }),
sendGetAgentTags: jest.fn().mockReturnValue({ data: { items: ['tag1', 'tag2'] } }),
useAuthz: jest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
setSelectedStatus,
selectedTags,
setSelectedTags,
agentPolicies,
allAgentPolicies,
agentPoliciesRequest,
agentPoliciesIndexedById,
pagination,
Expand Down Expand Up @@ -286,14 +286,14 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
refreshAgentActivity={isLoading}
setSearch={setSearch}
setSelectedStatus={setSelectedStatus}
agentPolicies={agentPolicies}
agentPolicies={allAgentPolicies}
/>
</EuiPortal>
) : null}
{enrollmentFlyout.isOpen ? (
<EuiPortal>
<AgentEnrollmentFlyout
agentPolicy={agentPolicies.find((p) => p.id === enrollmentFlyout.selectedPolicyId)}
agentPolicy={allAgentPolicies.find((p) => p.id === enrollmentFlyout.selectedPolicyId)}
onClose={() => {
setEnrollmentFlyoutState({ isOpen: false });
fetchData();
Expand Down Expand Up @@ -401,7 +401,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
)}
{/* Search and filter bar */}
<SearchAndFilterBar
agentPolicies={agentPolicies}
agentPolicies={allAgentPolicies}
draftKuery={draftKuery}
onDraftKueryChange={setDraftKuery}
onSubmitSearch={onSubmitSearch}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
* 2.0.
*/

import { useAuthz, useGetAgentPoliciesQuery, useGetAgentsQuery } from '../../../../../hooks';
import { policyHasFleetServer } from '../../../../../services';
import {
FLEET_SERVER_PACKAGE,
LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE,
} from '../../../../../../../../common/constants';
import { useAuthz, useGetAgentsQuery, useGetPackagePoliciesQuery } from '../../../../../hooks';

interface UseIsFirstTimeAgentUserResponse {
isFirstTimeAgentUser?: boolean;
Expand All @@ -16,23 +19,22 @@ interface UseIsFirstTimeAgentUserResponse {
export const useIsFirstTimeAgentUserQuery = (): UseIsFirstTimeAgentUserResponse => {
const authz = useAuthz();
const {
data: agentPolicies,
data: packagePolicies,
isLoading: areAgentPoliciesLoading,
isFetched: areAgentsFetched,
} = useGetAgentPoliciesQuery(
} = useGetPackagePoliciesQuery(
{
full: true,
kuery: `${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${FLEET_SERVER_PACKAGE}`,
},
{
enabled: authz.fleet.readAgentPolicies,
}
);

const policyIds = [...new Set(packagePolicies?.items.flatMap((item) => item.policy_ids) ?? [])];

// now get all agents that are NOT part of a fleet server policy
const serverPolicyIdsQuery = (agentPolicies?.items || [])
.filter((item) => policyHasFleetServer(item))
.map((p) => `policy_id:${p.id}`)
.join(' or ');
const serverPolicyIdsQuery = policyIds.map((policyId) => `policy_id:${policyId}`).join(' or ');

// get agents that are not unenrolled and not fleet server
const kuery =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
useAuthz,
} from '../../hooks';
import { FLEET_SERVER_PACKAGE, MAX_FLYOUT_WIDTH } from '../../constants';
import type { PackagePolicy, AgentPolicy } from '../../types';
import type { PackagePolicy } from '../../types';

import { Loading } from '..';

Expand All @@ -57,11 +57,6 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<FlyOutProps> = ({
isIntegrationFlow,
installedPackagePolicy,
}) => {
const findPolicyById = (policies: AgentPolicy[], id: string | undefined) => {
if (!id) return undefined;
return policies.find((p) => p.id === id);
};

const authz = useAuthz();

const fleetStatus = useFleetStatus();
Expand All @@ -87,7 +82,7 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<FlyOutProps> = ({

const selectedPolicy = agentPolicyWithPackagePolicies
? agentPolicyWithPackagePolicies
: findPolicyById(agentPolicies, selectedPolicyId);
: undefined;

const hasNoFleetServerHost = fleetStatus.isReady && !fleetServerHost;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { useMemo } from 'react';
import type { AgentPolicy } from '../types';
import { SO_SEARCH_LIMIT } from '../constants';

import { useAuthz } from './use_authz';

import { useGetAgentPolicies, useGetEnrollmentSettings } from './use_request';

interface AgentEnrollmentFlyoutData {
Expand All @@ -22,7 +20,6 @@ interface AgentEnrollmentFlyoutData {
}

export function useAgentEnrollmentFlyoutData(): AgentEnrollmentFlyoutData {
const authz = useAuthz();
const {
data: agentPoliciesData,
isInitialRequest: isInitialAgentPolicyRequest,
Expand All @@ -31,7 +28,6 @@ export function useAgentEnrollmentFlyoutData(): AgentEnrollmentFlyoutData {
} = useGetAgentPolicies({
page: 1,
perPage: SO_SEARCH_LIMIT,
full: authz.fleet.readAgentPolicies,
});

const {
Expand Down
Loading

0 comments on commit e433dc2

Please sign in to comment.