From 5549f7b2c55ef5fbaf94b8880a1e3ec00a172aee Mon Sep 17 00:00:00 2001 From: criamico Date: Mon, 7 Mar 2022 15:25:07 +0100 Subject: [PATCH 1/8] [Fleet] Create endpoint to check if agent has incoming data --- .../plugins/fleet/common/constants/routes.ts | 1 + .../fleet/server/routes/agent/handlers.ts | 17 ++++++ .../fleet/server/routes/agent/index.ts | 13 +++++ .../fleet/server/services/agents/status.ts | 58 +++++++++++++++++++ .../fleet/server/types/rest_spec/agent.ts | 6 ++ 5 files changed, 95 insertions(+) diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index 61df3b78282ca..f2d170a35b0a8 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -105,6 +105,7 @@ export const AGENT_API_ROUTES = { REASSIGN_PATTERN: `${API_ROOT}/agents/{agentId}/reassign`, BULK_REASSIGN_PATTERN: `${API_ROOT}/agents/bulk_reassign`, STATUS_PATTERN: `${API_ROOT}/agent_status`, + DATA_PATTERN: `${API_ROOT}/agent_status/data`, // deprecated since 8.0 STATUS_PATTERN_DEPRECATED: `${API_ROOT}/agent-status`, UPGRADE_PATTERN: `${API_ROOT}/agents/{agentId}/upgrade`, diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 1473b508f1354..18d2f58915ed9 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -21,6 +21,7 @@ import type { UpdateAgentRequestSchema, DeleteAgentRequestSchema, GetAgentStatusRequestSchema, + GetAgentDataRequestSchema, PutAgentReassignRequestSchema, PostBulkAgentReassignRequestSchema, } from '../../types'; @@ -218,3 +219,19 @@ export const getAgentStatusForAgentPolicyHandler: RequestHandler< return defaultIngestErrorHandler({ error, response }); } }; + +export const getAgentDataHandler: RequestHandler< + undefined, + TypeOf +> = async (context, request, response) => { + const esClient = context.core.elasticsearch.client.asCurrentUser; + try { + const results = await AgentService.getIncomingDataByAgentsId(esClient, request.query.agentsId); + + const body = { results }; + + return response.ok({ body }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); + } +}; diff --git a/x-pack/plugins/fleet/server/routes/agent/index.ts b/x-pack/plugins/fleet/server/routes/agent/index.ts index 83f0c17c82c37..535bb780abe57 100644 --- a/x-pack/plugins/fleet/server/routes/agent/index.ts +++ b/x-pack/plugins/fleet/server/routes/agent/index.ts @@ -14,6 +14,7 @@ import { PostAgentUnenrollRequestSchema, PostBulkAgentUnenrollRequestSchema, GetAgentStatusRequestSchema, + GetAgentDataRequestSchema, PostNewAgentActionRequestSchema, PutAgentReassignRequestSchema, PostBulkAgentReassignRequestSchema, @@ -32,6 +33,7 @@ import { getAgentStatusForAgentPolicyHandler, putAgentsReassignHandler, postBulkAgentsReassignHandler, + getAgentDataHandler, } from './handlers'; import { postNewAgentActionHandlerBuilder } from './actions_handlers'; import { postAgentUnenrollHandler, postBulkAgentsUnenrollHandler } from './unenroll_handler'; @@ -141,6 +143,17 @@ export const registerAPIRoutes = (router: FleetAuthzRouter, config: FleetConfigT }, getAgentStatusForAgentPolicyHandler ); + // Agent data + router.get( + { + path: AGENT_API_ROUTES.DATA_PATTERN, + validate: GetAgentDataRequestSchema, + fleetAuthz: { + fleet: { all: true }, + }, + }, + getAgentDataHandler + ); // upgrade agent router.post( diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index 5c5176ec41352..1dcbc8a0af16b 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -17,6 +17,8 @@ import { AgentStatusKueryHelper } from '../../../common/services'; import { getAgentById, getAgentsByKuery, removeSOAttributes } from './crud'; +const DATA_STREAM_INDEX_PATTERN = 'logs-*-*,metrics-*-*,traces-*-*,synthetics-*-*'; + export async function getAgentStatusById( esClient: ElasticsearchClient, agentId: string @@ -92,3 +94,59 @@ export async function getAgentStatusForAgentPolicy( events: 0, }; } +export async function getIncomingDataByAgentsId(esClient: ElasticsearchClient, agentsId: string[]) { + try { + const searchResult = await esClient.search({ + index: DATA_STREAM_INDEX_PATTERN, + allow_partial_search_results: true, + _source: false, + timeout: '10s', + body: { + query: { + bool: { + must: [ + { + terms: { + 'agent.id': agentsId, + }, + }, + { + range: { + '@timestamp': { + gte: 'now-10m', + lte: 'now', + }, + }, + }, + ], + }, + }, + aggs: { + agent_ids: { + terms: { + field: 'agent.id', + size: 5, + }, + }, + }, + }, + }); + + if (!searchResult.aggregations?.agent_ids) { + return agentsId.map((id) => { + return { [id]: { data: false } }; + }); + } + + // @ts-expect-error aggregation type is not specified + const agentIdsWithData: string[] = searchResult.aggregations.agent_ids.buckets.map( + (bucket: any) => bucket.key as string + ); + + return agentsId.map((id) => + agentIdsWithData.includes(id) ? { [id]: { data: true } } : { [id]: { data: false } } + ); + } catch (e) { + throw new Error(e); + } +} diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index 3f66c8159562a..1551023752228 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -111,3 +111,9 @@ export const GetAgentStatusRequestSchema = { kuery: schema.maybe(schema.string()), }), }; + +export const GetAgentDataRequestSchema = { + query: schema.object({ + agentsId: schema.arrayOf(schema.string()), + }), +}; From 2288887f9cbb35f2a34987ac8e4bc8f3e57d6289 Mon Sep 17 00:00:00 2001 From: criamico Date: Mon, 7 Mar 2022 17:40:26 +0100 Subject: [PATCH 2/8] Document new endpoint --- .../plugins/fleet/common/openapi/bundled.json | 45 +++++++++++++++++++ .../plugins/fleet/common/openapi/bundled.yaml | 28 ++++++++++++ .../fleet/common/openapi/entrypoint.yaml | 2 + .../openapi/paths/agent_status@data.yaml | 27 +++++++++++ .../fleet/server/routes/agent/handlers.ts | 4 +- 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/fleet/common/openapi/paths/agent_status@data.yaml diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 6db1459d90c64..88fc292a86f1e 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -1119,6 +1119,51 @@ ] } }, + "/agent_status/data": { + "get": { + "summary": "Agents - Get incoming data", + "tags": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "data": { + "type": "boolean" + } + } + } + } + } + } + } + } + } + } + }, + "operationId": "get-agent-data", + "parameters": [ + { + "schema": { + "type": "array" + }, + "name": "agentsId", + "in": "query", + "required": true + } + ] + } + }, "/agents": { "get": { "summary": "Agents - List", diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 6aaeaeaf16081..9bc6f1777ae39 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -686,6 +686,34 @@ paths: name: kuery in: query required: false + /agent_status/data: + get: + summary: Agents - Get incoming data + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + items: + type: array + items: + type: object + additionalProperties: + type: object + properties: + data: + type: boolean + operationId: get-agent-data + parameters: + - schema: + type: array + name: agentsId + in: query + required: true /agents: get: summary: Agents - List diff --git a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml index 4d51beeb40a2e..e54b270b94b7c 100644 --- a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml +++ b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml @@ -49,6 +49,8 @@ paths: $ref: paths/agent_status_deprecated.yaml /agent_status: $ref: paths/agent_status.yaml + /agent_status/data: + $ref: paths/agent_status@data.yaml /agents: $ref: paths/agents.yaml /agents/bulk_upgrade: diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_status@data.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_status@data.yaml new file mode 100644 index 0000000000000..cde8139383065 --- /dev/null +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_status@data.yaml @@ -0,0 +1,27 @@ +get: + summary: Agents - Get incoming data + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + items: + type: array + items: + type: object + additionalProperties: + type: object + properties: + data: + type: boolean + operationId: get-agent-data + parameters: + - schema: + type: array + name: agentsId + in: query + required: true diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 18d2f58915ed9..d5e14a095fa13 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -226,9 +226,9 @@ export const getAgentDataHandler: RequestHandler< > = async (context, request, response) => { const esClient = context.core.elasticsearch.client.asCurrentUser; try { - const results = await AgentService.getIncomingDataByAgentsId(esClient, request.query.agentsId); + const items = await AgentService.getIncomingDataByAgentsId(esClient, request.query.agentsId); - const body = { results }; + const body = { items }; return response.ok({ body }); } catch (error) { From 901b3a72183dd314ce92d1097940d2f5bcea41f3 Mon Sep 17 00:00:00 2001 From: criamico Date: Tue, 8 Mar 2022 17:52:49 +0100 Subject: [PATCH 3/8] Improvements to component --- .../plugins/fleet/common/openapi/bundled.json | 2 +- .../plugins/fleet/common/services/routes.ts | 1 + .../fleet/common/types/rest_spec/agent.ts | 13 +++ .../confirm_incoming_data.tsx | 88 +++++++++++++++++++ .../fleet/public/hooks/use_request/agents.ts | 9 ++ x-pack/plugins/fleet/public/types/index.ts | 3 + .../fleet/server/routes/agent/handlers.ts | 2 +- .../fleet/server/services/agents/status.ts | 17 ++-- .../fleet/server/types/rest_spec/agent.ts | 2 +- 9 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 88fc292a86f1e..19b1403771ccd 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -1157,7 +1157,7 @@ "schema": { "type": "array" }, - "name": "agentsId", + "name": "agentsIds", "in": "query", "required": true } diff --git a/x-pack/plugins/fleet/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts index b3a53cd05da4e..4336b77be2a89 100644 --- a/x-pack/plugins/fleet/common/services/routes.ts +++ b/x-pack/plugins/fleet/common/services/routes.ts @@ -164,6 +164,7 @@ export const agentRouteService = { getBulkUpgradePath: () => AGENT_API_ROUTES.BULK_UPGRADE_PATTERN, getListPath: () => AGENT_API_ROUTES.LIST_PATTERN, getStatusPath: () => AGENT_API_ROUTES.STATUS_PATTERN, + getIncomingDataPath: () => AGENT_API_ROUTES.DATA_PATTERN, getCreateActionPath: (agentId: string) => AGENT_API_ROUTES.ACTIONS_PATTERN.replace('{agentId}', agentId), }; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index 5e091b9c543f2..40570bc599053 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -161,3 +161,16 @@ export interface GetAgentStatusResponse { updating: number; }; } + +export interface GetAgentIncomingDataRequest { + query: { + agentsIds: string[]; + }; +} + +export interface IncomingDataList { + [key: string]: { data: boolean }; +} +export interface GetAgentIncomingDataResponse { + items: IncomingDataList[]; +} diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx new file mode 100644 index 0000000000000..861b5740e5a00 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx @@ -0,0 +1,88 @@ +/* + * 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 React, { useEffect, useState } from 'react'; +import { EuiCallOut, EuiButton, EuiText, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import type { IncomingDataList } from '../../../public/applications/fleet/types'; +import { sendGetAgentIncomingData } from '../../hooks'; +interface Props { + agentsIds: string[]; +} + +export const ConfirmIncomingData: React.FunctionComponent = ({ agentsIds }) => { + const [isLoading, setIsLoading] = useState(true); + const [incomingData, setIncomingData] = useState([]); + + useEffect(() => { + const getIncomingData = async () => { + const { data } = await sendGetAgentIncomingData({ agentsIds }); + if (data?.items) { + setIncomingData(data?.items); + setIsLoading(false); + } + }; + if (agentsIds) { + getIncomingData(); + } + }, [agentsIds]); + + const enrolledAgents = incomingData.length; + const agentsWithData = incomingData.reduce((acc, curr) => { + const agentData = Object.values(curr)[0]; + return !!agentData.data ? acc + 1 : acc; + }, 0); + + return ( + <> + {isLoading ? ( + + {i18n.translate('xpack.fleet.confirmIncomingData.title', { + defaultMessage: + 'It may take a few minutes for data to arrive in Elasticsearch. If the system is not generating data, it may help to generate some to ensure data is being collected correctly. If you’re having trouble, see our troubleshooting guide. You may close this dialog and check later by viewing our integration assets.', + })} + + ) : ( + <> + + + + {i18n.translate('xpack.fleet.confirmIncomingData.title', { + defaultMessage: 'Your agent is enrolled successfully and your data is received.', + })} + + + + )} + {/* build link to logs */} + + {i18n.translate('xpack.fleet.confirmIncomingData.button', { + defaultMessage: `View incoming data`, + })} + + + ); +}; diff --git a/x-pack/plugins/fleet/public/hooks/use_request/agents.ts b/x-pack/plugins/fleet/public/hooks/use_request/agents.ts index 0fbe59f3f48ee..9bfba13052c35 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/agents.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/agents.ts @@ -21,6 +21,8 @@ import type { GetAgentsResponse, GetAgentStatusRequest, GetAgentStatusResponse, + GetAgentIncomingDataRequest, + GetAgentIncomingDataResponse, PostAgentUpgradeRequest, PostBulkAgentUpgradeRequest, PostAgentUpgradeResponse, @@ -68,6 +70,13 @@ export function useGetAgentStatus(query: GetAgentStatusRequest['query'], options ...options, }); } +export function sendGetAgentIncomingData(query: GetAgentIncomingDataRequest['query']) { + return sendRequest({ + method: 'get', + path: agentRouteService.getIncomingDataPath(), + query, + }); +} export function sendGetAgentStatus( query: GetAgentStatusRequest['query'], diff --git a/x-pack/plugins/fleet/public/types/index.ts b/x-pack/plugins/fleet/public/types/index.ts index ead44a798cfc7..de8d9b63da416 100644 --- a/x-pack/plugins/fleet/public/types/index.ts +++ b/x-pack/plugins/fleet/public/types/index.ts @@ -61,6 +61,9 @@ export type { PostBulkAgentUpgradeResponse, GetAgentStatusRequest, GetAgentStatusResponse, + GetAgentIncomingDataRequest, + IncomingDataList, + GetAgentIncomingDataResponse, PutAgentReassignRequest, PutAgentReassignResponse, PostBulkAgentReassignRequest, diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index d5e14a095fa13..31cb7116c6974 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -226,7 +226,7 @@ export const getAgentDataHandler: RequestHandler< > = async (context, request, response) => { const esClient = context.core.elasticsearch.client.asCurrentUser; try { - const items = await AgentService.getIncomingDataByAgentsId(esClient, request.query.agentsId); + const items = await AgentService.getIncomingDataByAgentsId(esClient, request.query.agentsIds); const body = { items }; diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index 1dcbc8a0af16b..2db7306761766 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -94,26 +94,29 @@ export async function getAgentStatusForAgentPolicy( events: 0, }; } -export async function getIncomingDataByAgentsId(esClient: ElasticsearchClient, agentsId: string[]) { +export async function getIncomingDataByAgentsId( + esClient: ElasticsearchClient, + agentsIds: string[] +) { try { const searchResult = await esClient.search({ index: DATA_STREAM_INDEX_PATTERN, allow_partial_search_results: true, _source: false, - timeout: '10s', + timeout: '5s', body: { query: { bool: { must: [ { terms: { - 'agent.id': agentsId, + 'agent.id': agentsIds, }, }, { range: { '@timestamp': { - gte: 'now-10m', + gte: 'now-5m', lte: 'now', }, }, @@ -125,7 +128,7 @@ export async function getIncomingDataByAgentsId(esClient: ElasticsearchClient, a agent_ids: { terms: { field: 'agent.id', - size: 5, + size: 10, }, }, }, @@ -133,7 +136,7 @@ export async function getIncomingDataByAgentsId(esClient: ElasticsearchClient, a }); if (!searchResult.aggregations?.agent_ids) { - return agentsId.map((id) => { + return agentsIds.map((id) => { return { [id]: { data: false } }; }); } @@ -143,7 +146,7 @@ export async function getIncomingDataByAgentsId(esClient: ElasticsearchClient, a (bucket: any) => bucket.key as string ); - return agentsId.map((id) => + return agentsIds.map((id) => agentIdsWithData.includes(id) ? { [id]: { data: true } } : { [id]: { data: false } } ); } catch (e) { diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index 1551023752228..9f3196f256672 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -114,6 +114,6 @@ export const GetAgentStatusRequestSchema = { export const GetAgentDataRequestSchema = { query: schema.object({ - agentsId: schema.arrayOf(schema.string()), + agentsIds: schema.arrayOf(schema.string()), }), }; From 288dd072a76225f1049a2b6cd25ca07937a61a5f Mon Sep 17 00:00:00 2001 From: criamico Date: Wed, 9 Mar 2022 17:15:34 +0100 Subject: [PATCH 4/8] Update endpoint schema --- .../confirm_incoming_data.tsx | 18 ++++++++++-------- .../fleet/server/routes/agent/handlers.ts | 12 +++++++++++- .../fleet/server/types/rest_spec/agent.ts | 2 +- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx index 861b5740e5a00..eb505846eda3c 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx @@ -41,12 +41,15 @@ export const ConfirmIncomingData: React.FunctionComponent = ({ agentsIds return ( <> {isLoading ? ( - - {i18n.translate('xpack.fleet.confirmIncomingData.title', { - defaultMessage: - 'It may take a few minutes for data to arrive in Elasticsearch. If the system is not generating data, it may help to generate some to ensure data is being collected correctly. If you’re having trouble, see our troubleshooting guide. You may close this dialog and check later by viewing our integration assets.', - })} - + <> + + {i18n.translate('xpack.fleet.confirmIncomingData.loading', { + defaultMessage: + 'It may take a few minutes for data to arrive in Elasticsearch. If the system is not generating data, it may help to generate some to ensure data is being collected correctly. If you’re having trouble, see our troubleshooting guide. You may close this dialog and check later by viewing our integration assets.', + })} + + + ) : ( <> = ({ agentsIds /> - {i18n.translate('xpack.fleet.confirmIncomingData.title', { + {i18n.translate('xpack.fleet.confirmIncomingData.subtitle', { defaultMessage: 'Your agent is enrolled successfully and your data is received.', })} )} - {/* build link to logs */} = async (context, request, response) => { const esClient = context.core.elasticsearch.client.asCurrentUser; try { - const items = await AgentService.getIncomingDataByAgentsId(esClient, request.query.agentsIds); + let items; + + if (isStringArray(request.query.agentsIds)) { + items = await AgentService.getIncomingDataByAgentsId(esClient, request.query.agentsIds); + } else { + items = await AgentService.getIncomingDataByAgentsId(esClient, [request.query.agentsIds]); + } const body = { items }; @@ -235,3 +241,7 @@ export const getAgentDataHandler: RequestHandler< return defaultIngestErrorHandler({ error, response }); } }; + +function isStringArray(arr: unknown | string[]): arr is string[] { + return Array.isArray(arr) && arr.every((p) => typeof p === 'string'); +} diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index 9f3196f256672..ea11637119dc9 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -114,6 +114,6 @@ export const GetAgentStatusRequestSchema = { export const GetAgentDataRequestSchema = { query: schema.object({ - agentsIds: schema.arrayOf(schema.string()), + agentsIds: schema.oneOf([schema.arrayOf(schema.string()), schema.string()]), }), }; From 6cf25fe9f71337613dfb44f79aa70a0d0f362544 Mon Sep 17 00:00:00 2001 From: criamico Date: Wed, 9 Mar 2022 18:00:27 +0100 Subject: [PATCH 5/8] Remove button for now --- .../confirm_incoming_data.tsx | 75 ++++++++----------- 1 file changed, 30 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx index eb505846eda3c..1d08582112fb4 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx @@ -6,7 +6,7 @@ */ import React, { useEffect, useState } from 'react'; -import { EuiCallOut, EuiButton, EuiText, EuiSpacer } from '@elastic/eui'; +import { EuiCallOut, EuiText, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { IncomingDataList } from '../../../public/applications/fleet/types'; @@ -38,53 +38,38 @@ export const ConfirmIncomingData: React.FunctionComponent = ({ agentsIds return !!agentData.data ? acc + 1 : acc; }, 0); + if (isLoading) { + return ( + + {i18n.translate('xpack.fleet.confirmIncomingData.loading', { + defaultMessage: + 'It may take a few minutes for data to arrive in Elasticsearch. If the system is not generating data, it may help to generate some to ensure data is being collected correctly. If you’re having trouble, see our troubleshooting guide. You may close this dialog and check later by viewing our integration assets.', + })} + + ); + } + return ( <> - {isLoading ? ( - <> - - {i18n.translate('xpack.fleet.confirmIncomingData.loading', { - defaultMessage: - 'It may take a few minutes for data to arrive in Elasticsearch. If the system is not generating data, it may help to generate some to ensure data is being collected correctly. If you’re having trouble, see our troubleshooting guide. You may close this dialog and check later by viewing our integration assets.', - })} - - - - ) : ( - <> - - - - {i18n.translate('xpack.fleet.confirmIncomingData.subtitle', { - defaultMessage: 'Your agent is enrolled successfully and your data is received.', - })} - - - - )} - - {i18n.translate('xpack.fleet.confirmIncomingData.button', { - defaultMessage: `View incoming data`, + + + + {i18n.translate('xpack.fleet.confirmIncomingData.subtitle', { + defaultMessage: 'Your agent is enrolled successfully and your data is received.', })} - + ); }; From 8cdfe4d40597f3e14456ad7f7fffee4c17e2c7a8 Mon Sep 17 00:00:00 2001 From: criamico Date: Thu, 10 Mar 2022 16:09:39 +0100 Subject: [PATCH 6/8] Address review comments --- .../confirm_incoming_data.tsx | 27 ++--------- x-pack/plugins/fleet/public/hooks/index.ts | 1 + .../hooks/use_get_agent_incoming_data.tsx | 45 +++++++++++++++++++ .../fleet/server/services/agents/status.ts | 15 +++++++ 4 files changed, 64 insertions(+), 24 deletions(-) create mode 100644 x-pack/plugins/fleet/public/hooks/use_get_agent_incoming_data.tsx diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx index 1d08582112fb4..7ae070865aa83 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx @@ -5,38 +5,17 @@ * 2.0. */ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { EuiCallOut, EuiText, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { IncomingDataList } from '../../../public/applications/fleet/types'; -import { sendGetAgentIncomingData } from '../../hooks'; +import { useGetAgentIncomingData } from '../../hooks'; interface Props { agentsIds: string[]; } export const ConfirmIncomingData: React.FunctionComponent = ({ agentsIds }) => { - const [isLoading, setIsLoading] = useState(true); - const [incomingData, setIncomingData] = useState([]); - - useEffect(() => { - const getIncomingData = async () => { - const { data } = await sendGetAgentIncomingData({ agentsIds }); - if (data?.items) { - setIncomingData(data?.items); - setIsLoading(false); - } - }; - if (agentsIds) { - getIncomingData(); - } - }, [agentsIds]); - - const enrolledAgents = incomingData.length; - const agentsWithData = incomingData.reduce((acc, curr) => { - const agentData = Object.values(curr)[0]; - return !!agentData.data ? acc + 1 : acc; - }, 0); + const { enrolledAgents, agentsWithData, isLoading } = useGetAgentIncomingData(agentsIds); if (isLoading) { return ( diff --git a/x-pack/plugins/fleet/public/hooks/index.ts b/x-pack/plugins/fleet/public/hooks/index.ts index 5c995131396b4..c5dcdd78b9bb9 100644 --- a/x-pack/plugins/fleet/public/hooks/index.ts +++ b/x-pack/plugins/fleet/public/hooks/index.ts @@ -27,3 +27,4 @@ export * from './use_platform'; export * from './use_agent_policy_refresh'; export * from './use_package_installations'; export * from './use_agent_enrollment_flyout_data'; +export * from './use_get_agent_incoming_data'; diff --git a/x-pack/plugins/fleet/public/hooks/use_get_agent_incoming_data.tsx b/x-pack/plugins/fleet/public/hooks/use_get_agent_incoming_data.tsx new file mode 100644 index 0000000000000..d59af09dd1512 --- /dev/null +++ b/x-pack/plugins/fleet/public/hooks/use_get_agent_incoming_data.tsx @@ -0,0 +1,45 @@ +/* + * 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 { useEffect, useState, useMemo } from 'react'; + +import type { IncomingDataList } from '../../common/types/rest_spec/agent'; + +import { sendGetAgentIncomingData } from './index'; + +export const useGetAgentIncomingData = (agentsIds: string[]) => { + const [isLoading, setIsLoading] = useState(true); + const [incomingData, setIncomingData] = useState([]); + + useEffect(() => { + const getIncomingData = async () => { + const { data } = await sendGetAgentIncomingData({ agentsIds }); + if (data?.items) { + setIncomingData(data?.items); + setIsLoading(false); + } + }; + if (agentsIds) { + getIncomingData(); + } + }, [agentsIds]); + + const enrolledAgents = useMemo(() => incomingData.length, [incomingData.length]); + const agentsWithData = useMemo( + () => + incomingData.reduce((acc, curr) => { + const agentData = Object.values(curr)[0]; + return !!agentData.data ? acc + 1 : acc; + }, 0), + [incomingData] + ); + + return { + enrolledAgents, + agentsWithData, + isLoading, + }; +}; diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index 2db7306761766..03c4b206985b0 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -14,6 +14,7 @@ import { fromKueryExpression } from '@kbn/es-query'; import { AGENTS_PREFIX } from '../../constants'; import type { AgentStatus } from '../../types'; import { AgentStatusKueryHelper } from '../../../common/services'; +import { FleetUnauthorizedError } from '../../errors'; import { getAgentById, getAgentsByKuery, removeSOAttributes } from './crud'; @@ -99,6 +100,20 @@ export async function getIncomingDataByAgentsId( agentsIds: string[] ) { try { + const { has_all_requested: hasAllPrivileges } = await esClient.security.hasPrivileges({ + body: { + index: [ + { + names: [DATA_STREAM_INDEX_PATTERN], + privileges: ['read'], + }, + ], + }, + }); + if (!hasAllPrivileges) { + throw new FleetUnauthorizedError('Missing permissions to read data streams indices'); + } + const searchResult = await esClient.search({ index: DATA_STREAM_INDEX_PATTERN, allow_partial_search_results: true, From 9325922ba4baf635f6619969309c6d4522080124 Mon Sep 17 00:00:00 2001 From: criamico Date: Thu, 10 Mar 2022 16:58:31 +0100 Subject: [PATCH 7/8] Add dynamic button functionality --- .../confirm_incoming_data.tsx | 83 ++++++++++++------- .../hooks/use_get_agent_incoming_data.tsx | 36 +++++++- 2 files changed, 83 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx index 7ae070865aa83..a48d976fca7a9 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx @@ -6,49 +6,68 @@ */ import React from 'react'; -import { EuiCallOut, EuiText, EuiSpacer } from '@elastic/eui'; +import { EuiCallOut, EuiText, EuiSpacer, EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import type { InstalledIntegrationPolicy } from '../../hooks'; import { useGetAgentIncomingData } from '../../hooks'; interface Props { agentsIds: string[]; + installedPolicy: InstalledIntegrationPolicy; } -export const ConfirmIncomingData: React.FunctionComponent = ({ agentsIds }) => { - const { enrolledAgents, agentsWithData, isLoading } = useGetAgentIncomingData(agentsIds); - - if (isLoading) { - return ( - - {i18n.translate('xpack.fleet.confirmIncomingData.loading', { - defaultMessage: - 'It may take a few minutes for data to arrive in Elasticsearch. If the system is not generating data, it may help to generate some to ensure data is being collected correctly. If you’re having trouble, see our troubleshooting guide. You may close this dialog and check later by viewing our integration assets.', - })} - - ); - } +export const ConfirmIncomingData: React.FunctionComponent = ({ + agentsIds, + installedPolicy, +}) => { + const { enrolledAgents, numAgentsWithData, isLoading, linkButton } = useGetAgentIncomingData( + agentsIds, + installedPolicy + ); return ( <> - + {isLoading ? ( + + {i18n.translate('xpack.fleet.confirmIncomingData.loading', { + defaultMessage: + 'It may take a few minutes for data to arrive in Elasticsearch. If the system is not generating data, it may help to generate some to ensure data is being collected correctly. If you’re having trouble, see our troubleshooting guide. You may close this dialog and check later by viewing our integration assets.', + })} + + ) : ( + <> + + + + {i18n.translate('xpack.fleet.confirmIncomingData.subtitle', { + defaultMessage: 'Your agent is enrolled successfully and your data is received.', + })} + + + )} + - - {i18n.translate('xpack.fleet.confirmIncomingData.subtitle', { - defaultMessage: 'Your agent is enrolled successfully and your data is received.', - })} - + + {linkButton.text} + ); }; diff --git a/x-pack/plugins/fleet/public/hooks/use_get_agent_incoming_data.tsx b/x-pack/plugins/fleet/public/hooks/use_get_agent_incoming_data.tsx index d59af09dd1512..3075650e33d57 100644 --- a/x-pack/plugins/fleet/public/hooks/use_get_agent_incoming_data.tsx +++ b/x-pack/plugins/fleet/public/hooks/use_get_agent_incoming_data.tsx @@ -5,12 +5,21 @@ * 2.0. */ import { useEffect, useState, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; import type { IncomingDataList } from '../../common/types/rest_spec/agent'; -import { sendGetAgentIncomingData } from './index'; +import { sendGetAgentIncomingData, useLink } from './index'; -export const useGetAgentIncomingData = (agentsIds: string[]) => { +export interface InstalledIntegrationPolicy { + name: string; + version: string; +} + +export const useGetAgentIncomingData = ( + agentsIds: string[], + installedPolicy: InstalledIntegrationPolicy +) => { const [isLoading, setIsLoading] = useState(true); const [incomingData, setIncomingData] = useState([]); @@ -28,7 +37,7 @@ export const useGetAgentIncomingData = (agentsIds: string[]) => { }, [agentsIds]); const enrolledAgents = useMemo(() => incomingData.length, [incomingData.length]); - const agentsWithData = useMemo( + const numAgentsWithData = useMemo( () => incomingData.reduce((acc, curr) => { const agentData = Object.values(curr)[0]; @@ -37,9 +46,28 @@ export const useGetAgentIncomingData = (agentsIds: string[]) => { [incomingData] ); + let href; + let text; + const { getAbsolutePath, getHref } = useLink(); + if (installedPolicy.name === 'apm') { + href = getAbsolutePath('/app/home#/tutorial/apm'); + text = i18n.translate('xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentButtonText', { + defaultMessage: 'Install APM Agent', + }); + } else { + href = getHref('integration_details_assets', { + pkgkey: `${installedPolicy.name}-${installedPolicy.version}`, + }); + text = i18n.translate('xpack.fleet.epm.agentEnrollment.viewDataAssetsLabel', { + defaultMessage: 'View assets', + }); + } + const linkButton = { href, text }; + return { enrolledAgents, - agentsWithData, + numAgentsWithData, isLoading, + linkButton, }; }; From e9a03f50b4fc20257bc399b4512cf2e5a8ed08e6 Mon Sep 17 00:00:00 2001 From: criamico Date: Mon, 14 Mar 2022 12:10:35 +0100 Subject: [PATCH 8/8] Add option to hide button and improve query --- .../confirm_incoming_data.tsx | 22 ++++++++++--------- .../hooks/use_get_agent_incoming_data.tsx | 17 +++++++++----- .../fleet/server/services/agents/status.ts | 3 ++- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx index a48d976fca7a9..85817fa9850a0 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx @@ -13,7 +13,7 @@ import type { InstalledIntegrationPolicy } from '../../hooks'; import { useGetAgentIncomingData } from '../../hooks'; interface Props { agentsIds: string[]; - installedPolicy: InstalledIntegrationPolicy; + installedPolicy?: InstalledIntegrationPolicy; } export const ConfirmIncomingData: React.FunctionComponent = ({ @@ -59,15 +59,17 @@ export const ConfirmIncomingData: React.FunctionComponent = ({ )} - - {linkButton.text} - + {installedPolicy && ( + + {linkButton.text} + + )} ); }; diff --git a/x-pack/plugins/fleet/public/hooks/use_get_agent_incoming_data.tsx b/x-pack/plugins/fleet/public/hooks/use_get_agent_incoming_data.tsx index 3075650e33d57..a14dbd30aef45 100644 --- a/x-pack/plugins/fleet/public/hooks/use_get_agent_incoming_data.tsx +++ b/x-pack/plugins/fleet/public/hooks/use_get_agent_incoming_data.tsx @@ -18,7 +18,7 @@ export interface InstalledIntegrationPolicy { export const useGetAgentIncomingData = ( agentsIds: string[], - installedPolicy: InstalledIntegrationPolicy + installedPolicy?: InstalledIntegrationPolicy ) => { const [isLoading, setIsLoading] = useState(true); const [incomingData, setIncomingData] = useState([]); @@ -45,20 +45,25 @@ export const useGetAgentIncomingData = ( }, 0), [incomingData] ); + const { getAbsolutePath, getHref } = useLink(); let href; let text; - const { getAbsolutePath, getHref } = useLink(); - if (installedPolicy.name === 'apm') { + if (!installedPolicy) { + href = ''; + text = ''; + } + + if (installedPolicy?.name === 'apm') { href = getAbsolutePath('/app/home#/tutorial/apm'); - text = i18n.translate('xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentButtonText', { + text = i18n.translate('xpack.fleet.confirmIncomingData.installApmAgentButtonText', { defaultMessage: 'Install APM Agent', }); } else { href = getHref('integration_details_assets', { - pkgkey: `${installedPolicy.name}-${installedPolicy.version}`, + pkgkey: `${installedPolicy?.name}-${installedPolicy?.version}`, }); - text = i18n.translate('xpack.fleet.epm.agentEnrollment.viewDataAssetsLabel', { + text = i18n.translate('xpack.fleet.confirmIncomingData.viewDataAssetsButtonText', { defaultMessage: 'View assets', }); } diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index 03c4b206985b0..828a3b1622cef 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -119,10 +119,11 @@ export async function getIncomingDataByAgentsId( allow_partial_search_results: true, _source: false, timeout: '5s', + size: 0, body: { query: { bool: { - must: [ + filter: [ { terms: { 'agent.id': agentsIds,