Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Display outputs in agent list table and agent details #195801

Merged
merged 29 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cdb906e
[Fleet] Display outputs in agent list table and agent details
criamico Oct 10, 2024
d88bdfe
Create component to render outputs
criamico Oct 10, 2024
de08fda
Merge branch 'main' into 192339_show_outputs_in_agents_table
elasticmachine Oct 11, 2024
2064407
Implement text truncate and add badge with popover
criamico Oct 11, 2024
749db59
small fixes and unit tests
criamico Oct 11, 2024
5a22b25
Add outputs info in agent details section
criamico Oct 11, 2024
b6e8a5e
Merge branch 'main' into 192339_show_outputs_in_agents_table
elasticmachine Oct 11, 2024
de53fed
Merge branch 'main' into 192339_show_outputs_in_agents_table
elasticmachine Oct 14, 2024
ea3b503
Fix copy
criamico Oct 14, 2024
ce5c37b
Add basic functionality for new endpoint - get outputs by agent polic…
criamico Oct 14, 2024
36a17a3
Move logic to AgentPolicyService
criamico Oct 15, 2024
4e0a62b
Use new endpoint in UI
criamico Oct 15, 2024
41eedb0
Merge branch 'main' into 192339_show_outputs_in_agents_table
elasticmachine Oct 16, 2024
9ee5c29
Add new endpoint to retrieve outputs for all policies
criamico Oct 16, 2024
d80de5f
Improvements to new endpoint
criamico Oct 16, 2024
a2519c3
limit agent policies query to the ones displayed
criamico Oct 16, 2024
bdc585f
[CI] Auto-commit changed files from 'node scripts/capture_oas_snapsho…
kibanamachine Oct 16, 2024
cdb247a
[CI] Auto-commit changed files from 'make api-docs && make api-docs-s…
kibanamachine Oct 16, 2024
77a03a8
Fix linter and one unit test
criamico Oct 17, 2024
77e16d2
[CI] Auto-commit changed files from 'node scripts/capture_oas_snapsho…
kibanamachine Oct 17, 2024
d99f2cd
[CI] Auto-commit changed files from 'make api-docs && make api-docs-s…
kibanamachine Oct 17, 2024
42dc2a6
Use post instead of get
criamico Oct 17, 2024
4927926
fix failing test
criamico Oct 17, 2024
ef78686
[CI] Auto-commit changed files from 'node scripts/capture_oas_snapsho…
kibanamachine Oct 17, 2024
fc6f095
Merge branch 'main' into 192339_show_outputs_in_agents_table
elasticmachine Oct 21, 2024
a27aa64
Add concurrency to pMap
criamico Oct 21, 2024
ad9bdf9
[CI] Auto-commit changed files from 'make api-docs && make api-docs-s…
kibanamachine Oct 21, 2024
03e11f6
add integration tests
criamico Oct 22, 2024
34d780d
Merge branch 'main' into 192339_show_outputs_in_agents_table
elasticmachine Oct 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react';

import type { Agent, AgentPolicy } from '../../../../../types';
import { useAgentVersion } from '../../../../../hooks';
import { useAgentVersion, useGetOutputs } from '../../../../../hooks';
import { ExperimentalFeaturesService, isAgentUpgradeable } from '../../../../../services';
import { AgentPolicySummaryLine } from '../../../../../components';
import { AgentHealth } from '../../../components';
import { Tags } from '../../../components/tags';
import { formatAgentCPU, formatAgentMemory } from '../../../services/agent_metrics';
import { AgentDashboardLink } from '../agent_dashboard_link';
import { AgentUpgradeStatus } from '../../../agent_list_page/components/agent_upgrade_status';
import { AgentPolicyOutputsSummary } from '../../../agent_list_page/components/agent_policy_outputs_summary';

// Allows child text to be truncated
const FlexItemWithMinWidth = styled(EuiFlexItem)`
Expand All @@ -42,11 +43,17 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{
}> = memo(({ agent, agentPolicy }) => {
const latestAgentVersion = useAgentVersion();
const { displayAgentMetrics } = ExperimentalFeaturesService.get();
const outputsRequest = useGetOutputs();
const allOutputs = outputsRequest?.data?.items ?? [];

return (
<EuiPanel>
<EuiDescriptionList compressed>
<EuiFlexGroup direction="column" gutterSize="m">
<EuiFlexGroup
direction="column"
gutterSize="m"
data-test-subj="agentDetailsOverviewSection"
>
{displayAgentMetrics && (
<EuiFlexGroup>
<FlexItemWithMinWidth grow={5}>
Expand Down Expand Up @@ -206,6 +213,30 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{
? agent.local_metadata.host.id
: '-',
},
{
title: i18n.translate('xpack.fleet.agentDetails.outputForMonitoringLabel', {
defaultMessage: 'Output for Integrations',
criamico marked this conversation as resolved.
Show resolved Hide resolved
}),
description: agentPolicy ? (
<AgentPolicyOutputsSummary outputs={allOutputs} agentPolicy={agentPolicy} />
) : (
'-'
),
},
{
title: i18n.translate('xpack.fleet.agentDetails.outputForMonitoringLabel', {
defaultMessage: 'Output for Monitoring',
criamico marked this conversation as resolved.
Show resolved Hide resolved
}),
description: agentPolicy ? (
<AgentPolicyOutputsSummary
outputs={allOutputs}
agentPolicy={agentPolicy}
monitoring={true}
/>
) : (
'-'
),
},
{
title: i18n.translate('xpack.fleet.agentDetails.logLevel', {
defaultMessage: 'Logging level',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/
import React from 'react';
import type { EuiBasicTableColumn } from '@elastic/eui';
import { type CriteriaWithPagination } from '@elastic/eui';
import {
EuiBasicTable,
Expand All @@ -23,20 +24,31 @@ import { isAgentUpgradeable, ExperimentalFeaturesService } from '../../../../ser
import { AgentHealth } from '../../components';

import type { Pagination } from '../../../../hooks';
import { useAgentVersion } from '../../../../hooks';
import { useAgentVersion, useGetOutputs } from '../../../../hooks';
import { useLink, useAuthz } from '../../../../hooks';

import { AgentPolicySummaryLine } from '../../../../components';
import { Tags } from '../../components/tags';
import type { AgentMetrics } from '../../../../../../../common/types';
import type { AgentMetrics, Output } from '../../../../../../../common/types';
import { formatAgentCPU, formatAgentMemory } from '../../services/agent_metrics';

import { AgentPolicyOutputsSummary } from './agent_policy_outputs_summary';

import { AgentUpgradeStatus } from './agent_upgrade_status';

import { EmptyPrompt } from './empty_prompt';

const VERSION_FIELD = 'local_metadata.elastic.agent.version';
const HOSTNAME_FIELD = 'local_metadata.host.hostname';
const AGENTS_TABLE_FIELDS = {
ACTIVE: 'active',
HOSTNAME: 'local_metadata.host.hostname',
POLICY: 'policy_id',
METRICS: 'metrics',
VERSION: 'local_metadata.elastic.agent.version',
LAST_CHECKIN: 'last_checkin',
OUTPUT_INTEGRATION: 'output_integrations',
OUTPUT_MONITORING: 'output_monitoring',
};

function safeMetadata(val: any) {
if (typeof val !== 'string') {
return '-';
Expand Down Expand Up @@ -95,6 +107,8 @@ export const AgentListTable: React.FC<Props> = (props: Props) => {

const { getHref } = useLink();
const latestAgentVersion = useAgentVersion();
const outputsRequest = useGetOutputs();
const allOutputs = outputsRequest?.data?.items ?? [];

const isAgentSelectable = (agent: Agent) => {
if (!agent.active) return false;
Expand Down Expand Up @@ -140,9 +154,9 @@ export const AgentListTable: React.FC<Props> = (props: Props) => {
},
};

const columns = [
const columns: Array<EuiBasicTableColumn<Agent>> = [
{
field: 'active',
field: AGENTS_TABLE_FIELDS.ACTIVE,
sortable: false,
width: '85px',
name: i18n.translate('xpack.fleet.agentList.statusColumnTitle', {
Expand All @@ -151,7 +165,7 @@ export const AgentListTable: React.FC<Props> = (props: Props) => {
render: (active: boolean, agent: any) => <AgentHealth agent={agent} />,
},
{
field: HOSTNAME_FIELD,
field: AGENTS_TABLE_FIELDS.HOSTNAME,
sortable: true,
name: i18n.translate('xpack.fleet.agentList.hostColumnTitle', {
defaultMessage: 'Host',
Expand All @@ -171,7 +185,7 @@ export const AgentListTable: React.FC<Props> = (props: Props) => {
),
},
{
field: 'policy_id',
field: AGENTS_TABLE_FIELDS.POLICY,
sortable: true,
truncateText: true,
name: i18n.translate('xpack.fleet.agentList.policyColumnTitle', {
Expand Down Expand Up @@ -208,7 +222,7 @@ export const AgentListTable: React.FC<Props> = (props: Props) => {
...(displayAgentMetrics
? [
{
field: 'metrics',
field: AGENTS_TABLE_FIELDS.METRICS,
sortable: false,
name: (
<EuiToolTip
Expand All @@ -234,7 +248,7 @@ export const AgentListTable: React.FC<Props> = (props: Props) => {
),
},
{
field: 'metrics',
field: AGENTS_TABLE_FIELDS.METRICS,
sortable: false,
name: (
<EuiToolTip
Expand Down Expand Up @@ -264,19 +278,52 @@ export const AgentListTable: React.FC<Props> = (props: Props) => {
},
]
: []),

{
field: 'last_checkin',
field: AGENTS_TABLE_FIELDS.LAST_CHECKIN,
sortable: true,
name: i18n.translate('xpack.fleet.agentList.lastCheckinTitle', {
defaultMessage: 'Last activity',
}),
width: '100px',
render: (lastCheckin: string) =>
lastCheckin ? <FormattedRelative value={lastCheckin} /> : undefined,
},
{
field: AGENTS_TABLE_FIELDS.OUTPUT_INTEGRATION,
sortable: true,
name: i18n.translate('xpack.fleet.agentList.integrationsOutputTitle', {
defaultMessage: 'Output for integrations',
}),
width: '180px',
render: (lastCheckin: string, agent: any) =>
lastCheckin ? <FormattedRelative value={lastCheckin} /> : null,
render: (outputs: Output[], agent: Agent) => {
const agentPolicy = agent?.policy_id
? agentPoliciesIndexedById[agent.policy_id]
: undefined;
return <AgentPolicyOutputsSummary outputs={allOutputs} agentPolicy={agentPolicy} />;
},
},
{
field: AGENTS_TABLE_FIELDS.OUTPUT_MONITORING,
sortable: true,
name: i18n.translate('xpack.fleet.agentList.monitoringOutputTitle', {
defaultMessage: 'Output for Monitoring',
criamico marked this conversation as resolved.
Show resolved Hide resolved
}),
width: '180px',
render: (outputs: Output[], agent: Agent) => {
const agentPolicy = agent?.policy_id
? agentPoliciesIndexedById[agent.policy_id]
: undefined;
return (
<AgentPolicyOutputsSummary
outputs={allOutputs}
agentPolicy={agentPolicy}
monitoring={true}
/>
);
},
},
{
field: VERSION_FIELD,
field: AGENTS_TABLE_FIELDS.VERSION,
sortable: true,
width: '220px',
name: i18n.translate('xpack.fleet.agentList.versionTitle', {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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 { act, fireEvent } from '@testing-library/react';
import React from 'react';

import { createFleetTestRendererMock } from '../../../../../../mock';
import type { TestRenderer } from '../../../../../../mock';

import type { AgentPolicy, Output } from '../../../../types';
import { createAgentPolicyMock } from '../../../../../../../common/mocks';

import { AgentPolicyOutputsSummary } from './agent_policy_outputs_summary';

describe('MultipleAgentPolicySummaryLine', () => {
let testRenderer: TestRenderer;
const outputs: Output[] = [
{
id: 'elasticsearch1',
name: 'Elasticsearch1',
type: 'elasticsearch',
is_default: false,
is_default_monitoring: false,
hosts: ['http://test.io:449'],
},
{
id: 'logstash1',
name: 'Logstash 1',
type: 'logstash',
is_default: true,
is_default_monitoring: true,
hosts: ['http://test.io:449'],
},
{
id: 'remote_es1',
name: 'Remote ES',
type: 'remote_elasticsearch',
is_default: false,
is_default_monitoring: false,
hosts: ['http://test.io:449', 'http://test.io:448', 'http://test.io:447'],
},
];

const render = (agentPolicy: AgentPolicy, monitoring?: boolean) =>
testRenderer.render(
<AgentPolicyOutputsSummary
agentPolicy={agentPolicy}
outputs={outputs}
monitoring={monitoring}
/>
);

beforeEach(() => {
testRenderer = createFleetTestRendererMock();
});

test('it should render the host associated with the default output when the agent policy does not have custom outputs', async () => {
const mockAgentPolicy = createAgentPolicyMock();
const results = render(mockAgentPolicy);
expect(results.container.textContent).toBe('http://test.io:449');
expect(results.queryByTestId('outputNameLink')).toBeInTheDocument();
expect(results.queryByTestId('outputHostsNumberBadge')).not.toBeInTheDocument();
});

test('it should render the first host name and the badge when there are multiple hosts', async () => {
const agentPolicy = createAgentPolicyMock({
data_output_id: 'remote_es1',
monitoring_output_id: 'remote_es1',
});
const results = render(agentPolicy);

expect(results.queryByTestId('outputNameLink')).toBeInTheDocument();
expect(results.queryByTestId('outputHostsNumberBadge')).toBeInTheDocument();

await act(async () => {
fireEvent.click(results.getByTestId('outputHostsNumberBadge'));
});
expect(results.queryByTestId('outputHostsPopover')).toBeInTheDocument();
expect(results.queryByTestId('output-host-0')?.textContent).toContain('http://test.io:449');
expect(results.queryByTestId('output-host-1')?.textContent).toContain('http://test.io:448');
expect(results.queryByTestId('output-host-2')?.textContent).toContain('http://test.io:447');
});

test('it should not render the badge when monitoring is true', async () => {
const agentPolicy = createAgentPolicyMock({
data_output_id: 'remote_es1',
monitoring_output_id: 'remote_es1',
});
const results = render(agentPolicy, true);

expect(results.queryByTestId('outputNameLink')).toBeInTheDocument();
expect(results.queryByTestId('outputHostsNumberBadge')).not.toBeInTheDocument();
});
});
Loading