mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Fleet] Show multiple agent policies in integrations table (#186087)
Closes https://github.com/elastic/kibana/issues/182111 ## Summary Show multiple agent policies in integrations table. ## Testing - Enable feature flag `enableReusableIntegrationPolicies` - Install an integration that has more than one agent policies associated with it (instructions are [here](https://github.com/elastic/kibana/pull/185916)) - Navigate to integrations table and verify that the policy displays a badge with the number of associated policies -1 and that it opens up a popover, like in below screenshots. **NOTE** the button "Manage agent policies" does not work for now, as the feature is under development and it's hidden with a feature flag! ### With feature flag enabled, when integration has multiple agent policies   When one of the policies is managed:  ### When feature flag not enabled or integration has only one agent policy The UI remains as it is today:  ### Checklist - [ ] 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/packages/kbn-i18n/README.md) - [ ] [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 --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
780fda17c1
commit
ee15561217
6 changed files with 198 additions and 7 deletions
|
@ -21,6 +21,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedRelative, FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { policyHasFleetServer } from '../../../../../../../../common/services';
|
||||
import { ExperimentalFeaturesService } from '../../../../../services';
|
||||
|
||||
import { InstallStatus } from '../../../../../types';
|
||||
import type { GetAgentPoliciesResponseItem, InMemoryPackagePolicy } from '../../../../../types';
|
||||
|
@ -35,6 +36,7 @@ import {
|
|||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants';
|
||||
import {
|
||||
AgentEnrollmentFlyout,
|
||||
MultipleAgentPoliciesSummaryLine,
|
||||
AgentPolicySummaryLine,
|
||||
PackagePolicyActionsMenu,
|
||||
} from '../../../../../components';
|
||||
|
@ -101,6 +103,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||
const packageInstallStatus = getPackageInstallStatus(name);
|
||||
const { pagination, pageSizeOptions, setPagination } = useUrlPagination();
|
||||
const { enableReusableIntegrationPolicies } = ExperimentalFeaturesService.get();
|
||||
|
||||
const {
|
||||
data,
|
||||
|
@ -114,8 +117,10 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
const { isPackagePolicyUpgradable } = useIsPackagePolicyUpgradable();
|
||||
|
||||
const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
|
||||
const canReadIntegrationPolicies = useAuthz().integrations.readIntegrationPolicies;
|
||||
const canAddAgents = useAuthz().fleet.addAgents;
|
||||
const canAddFleetServers = useAuthz().fleet.addFleetServers;
|
||||
const canReadAgentPolicies = useAuthz().fleet.readAgentPolicies;
|
||||
|
||||
const packageAndAgentPolicies = useMemo((): Array<{
|
||||
agentPolicies: GetAgentPoliciesResponseItem[];
|
||||
|
@ -167,7 +172,8 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
},
|
||||
[setPagination]
|
||||
);
|
||||
|
||||
const canShowMultiplePoliciesCell =
|
||||
enableReusableIntegrationPolicies && canReadIntegrationPolicies && canReadAgentPolicies;
|
||||
const columns: Array<EuiTableFieldDataColumnType<InMemoryPackagePolicyAndAgentPolicy>> = useMemo(
|
||||
() => [
|
||||
{
|
||||
|
@ -228,8 +234,11 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
truncateText: true,
|
||||
render(id, { agentPolicies }) {
|
||||
return agentPolicies.length > 0 ? (
|
||||
// TODO: handle multiple agent policies
|
||||
<AgentPolicySummaryLine policy={agentPolicies[0]} />
|
||||
canShowMultiplePoliciesCell && agentPolicies.length > 1 ? (
|
||||
<MultipleAgentPoliciesSummaryLine policies={agentPolicies} />
|
||||
) : (
|
||||
<AgentPolicySummaryLine policy={agentPolicies[0]} />
|
||||
)
|
||||
) : (
|
||||
<AgentPolicyNotFound />
|
||||
);
|
||||
|
@ -313,8 +322,9 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
[
|
||||
getHref,
|
||||
canWriteIntegrationPolicies,
|
||||
canAddAgents,
|
||||
canShowMultiplePoliciesCell,
|
||||
canAddFleetServers,
|
||||
canAddAgents,
|
||||
showAddAgentHelpForPackagePolicyId,
|
||||
]
|
||||
);
|
||||
|
|
|
@ -12,7 +12,7 @@ import { createFleetTestRendererMock } from '../mock';
|
|||
|
||||
import type { AgentPolicy, Agent } from '../types';
|
||||
|
||||
import { AgentPolicySummaryLine } from './link_and_revision';
|
||||
import { AgentPolicySummaryLine } from './agent_policy_summary_line';
|
||||
|
||||
describe('AgentPolicySummaryLine', () => {
|
||||
let testRenderer: TestRenderer;
|
|
@ -26,7 +26,6 @@ export const AgentPolicySummaryLine = memo<{
|
|||
const { name, id, is_managed: isManaged, description } = policy;
|
||||
|
||||
const revision = agent ? agent.policy_revision : policy.revision;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
<EuiFlexItem>
|
|
@ -20,7 +20,7 @@ export { PackagePolicyDeleteProvider } from './package_policy_delete_provider';
|
|||
export { PackagePolicyActionsMenu } from './package_policy_actions_menu';
|
||||
export { AddAgentHelpPopover } from './add_agent_help_popover';
|
||||
export { EuiButtonWithTooltip } from './eui_button_with_tooltip';
|
||||
export * from './link_and_revision';
|
||||
export * from './agent_policy_summary_line';
|
||||
export * from './agent_enrollment_flyout';
|
||||
export * from './platform_selector';
|
||||
export { ConfirmForceInstallModal } from './confirm_force_install_modal';
|
||||
|
@ -28,3 +28,4 @@ export { DevtoolsRequestFlyoutButton } from './devtools_request_flyout';
|
|||
export { HeaderReleaseBadge, InlineReleaseBadge } from './release_badge';
|
||||
export { WithGuidedOnboardingTour } from './with_guided_onboarding_tour';
|
||||
export { UninstallCommandFlyout } from './uninstall_command_flyout';
|
||||
export { MultipleAgentPoliciesSummaryLine } from './multiple_agent_policy_summary_line';
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 type { TestRenderer } from '../mock';
|
||||
import { createFleetTestRendererMock } from '../mock';
|
||||
|
||||
import type { AgentPolicy } from '../types';
|
||||
|
||||
import { MultipleAgentPoliciesSummaryLine } from './multiple_agent_policy_summary_line';
|
||||
|
||||
describe('MultipleAgentPolicySummaryLine', () => {
|
||||
let testRenderer: TestRenderer;
|
||||
|
||||
const render = (agentPolicies: AgentPolicy[]) =>
|
||||
testRenderer.render(<MultipleAgentPoliciesSummaryLine policies={agentPolicies} />);
|
||||
|
||||
beforeEach(() => {
|
||||
testRenderer = createFleetTestRendererMock();
|
||||
});
|
||||
|
||||
test('it should render only the policy name when there is only one policy', async () => {
|
||||
const results = render([{ name: 'Test policy', revision: 2 }] as AgentPolicy[]);
|
||||
expect(results.container.textContent).toBe('Test policy');
|
||||
expect(results.queryByTestId('agentPolicyNameBadge')).toBeInTheDocument();
|
||||
expect(results.queryByTestId('agentPoliciesNumberBadge')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('it should render the first policy name and the badge when there are multiple policies', async () => {
|
||||
const results = render([
|
||||
{ name: 'Test policy 1', id: '0001' },
|
||||
{ name: 'Test policy 2', id: '0002' },
|
||||
{ name: 'Test policy 3', id: '0003' },
|
||||
] as AgentPolicy[]);
|
||||
expect(results.queryByTestId('agentPolicyNameBadge')).toBeInTheDocument();
|
||||
expect(results.queryByTestId('agentPoliciesNumberBadge')).toBeInTheDocument();
|
||||
expect(results.container.textContent).toBe('Test policy 1+2');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(results.getByTestId('agentPoliciesNumberBadge'));
|
||||
});
|
||||
expect(results.queryByTestId('agentPoliciesPopover')).toBeInTheDocument();
|
||||
expect(results.queryByTestId('agentPoliciesPopoverButton')).toBeInTheDocument();
|
||||
expect(results.queryByTestId('policy-0001')).toBeInTheDocument();
|
||||
expect(results.queryByTestId('policy-0002')).toBeInTheDocument();
|
||||
expect(results.queryByTestId('policy-0003')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiBadge,
|
||||
EuiPopover,
|
||||
EuiPopoverTitle,
|
||||
EuiPopoverFooter,
|
||||
EuiButton,
|
||||
EuiListGroup,
|
||||
type EuiListGroupItemProps,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import React, { memo, useState } from 'react';
|
||||
|
||||
import type { AgentPolicy } from '../../common/types';
|
||||
import { useLink } from '../hooks';
|
||||
const MIN_WIDTH: CSSProperties = { minWidth: 0 };
|
||||
|
||||
export const MultipleAgentPoliciesSummaryLine = memo<{
|
||||
policies: AgentPolicy[];
|
||||
direction?: 'column' | 'row';
|
||||
}>(({ policies, direction = 'row' }) => {
|
||||
const { getHref } = useLink();
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const closePopover = () => setIsPopoverOpen(false);
|
||||
|
||||
// as default, show only the first policy
|
||||
const policy = policies[0];
|
||||
const { name, id } = policy;
|
||||
|
||||
const listItems: EuiListGroupItemProps[] = useMemo(() => {
|
||||
return policies.map((p) => {
|
||||
return {
|
||||
'data-test-subj': `policy-${p.id}`,
|
||||
label: p.name || p.id,
|
||||
href: getHref('policy_details', { policyId: p.id }),
|
||||
iconType: 'dot',
|
||||
extraAction: {
|
||||
color: 'text',
|
||||
iconType: p.is_managed ? 'lock' : '',
|
||||
alwaysShow: !!p.is_managed,
|
||||
iconSize: 's',
|
||||
'aria-label': 'Hosted agent policy',
|
||||
},
|
||||
showToolTip: !!p.is_managed,
|
||||
toolTipText: i18n.translate('xpack.fleet.agentPolicySummaryLine.hostedPolicyTooltip', {
|
||||
defaultMessage:
|
||||
'This policy is managed outside of Fleet. Most actions related to this policy are unavailable.',
|
||||
}),
|
||||
};
|
||||
});
|
||||
}, [getHref, policies]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
direction={direction}
|
||||
gutterSize={direction === 'column' ? 'none' : 's'}
|
||||
alignItems="baseline"
|
||||
style={MIN_WIDTH}
|
||||
responsive={false}
|
||||
justifyContent={'flexStart'}
|
||||
>
|
||||
<EuiFlexItem grow={false} className="eui-textTruncate">
|
||||
<EuiFlexGroup style={MIN_WIDTH} gutterSize="s" alignItems="baseline" responsive={false}>
|
||||
<EuiFlexItem grow={false} className="eui-textTruncate">
|
||||
<EuiBadge color="default" data-test-subj="agentPolicyNameBadge">
|
||||
{name || id}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
{policies.length > 1 && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge
|
||||
color="hollow"
|
||||
data-test-subj="agentPoliciesNumberBadge"
|
||||
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
|
||||
onClickAriaLabel="Open agent policies popover"
|
||||
>
|
||||
{`+${policies.length - 1}`}
|
||||
</EuiBadge>
|
||||
<EuiPopover
|
||||
data-test-subj="agentPoliciesPopover"
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
anchorPosition="downCenter"
|
||||
>
|
||||
<EuiPopoverTitle>
|
||||
{i18n.translate('xpack.fleet.agentPolicySummaryLine.popover.title', {
|
||||
defaultMessage: 'This integration is shared by',
|
||||
})}
|
||||
</EuiPopoverTitle>
|
||||
<div style={{ width: '280px' }}>
|
||||
<EuiListGroup
|
||||
listItems={listItems}
|
||||
color="primary"
|
||||
size="s"
|
||||
gutterSize="none"
|
||||
/>
|
||||
</div>
|
||||
<EuiPopoverFooter>
|
||||
{/* TODO: implement missing onClick function */}
|
||||
<EuiButton fullWidth size="s" data-test-subj="agentPoliciesPopoverButton">
|
||||
{i18n.translate('xpack.fleet.agentPolicySummaryLine.popover.button', {
|
||||
defaultMessage: 'Manage agent policies',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiPopoverFooter>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue