Skip to content

Commit b2294bc

Browse files
committed
[Fleet] Integration Policies List view (#83634)
* Show table with list of Integration polices * hook to support list pagination that uses/persist to the URL
1 parent 518c24d commit b2294bc

15 files changed

Lines changed: 551 additions & 48 deletions

File tree

x-pack/plugins/fleet/public/applications/fleet/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export { PackageIcon } from './package_icon';
1111
export { ContextMenuActions } from './context_menu_actions';
1212
export { SearchBar } from './search_bar';
1313
export * from './settings_flyout';
14+
export * from './link_and_revision';
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText } from '@elastic/eui';
8+
import { FormattedMessage } from '@kbn/i18n/react';
9+
import React, { CSSProperties, memo } from 'react';
10+
import { EuiLinkProps } from '@elastic/eui/src/components/link/link';
11+
12+
const MIN_WIDTH: CSSProperties = { minWidth: 0 };
13+
const NO_WRAP_WHITE_SPACE: CSSProperties = { whiteSpace: 'nowrap' };
14+
15+
export type LinkAndRevisionProps = EuiLinkProps & {
16+
revision?: string | number;
17+
};
18+
19+
/**
20+
* Components shows a link for a given value along with a revision number to its right. The display
21+
* value is truncated if it is longer than the width of where it is displayed, while the revision
22+
* always remain visible
23+
*/
24+
export const LinkAndRevision = memo<LinkAndRevisionProps>(
25+
({ revision, className, ...euiLinkProps }) => {
26+
return (
27+
<EuiFlexGroup gutterSize="s" alignItems="baseline" style={MIN_WIDTH} responsive={false}>
28+
<EuiFlexItem grow={false} className="eui-textTruncate">
29+
<EuiLink className={`eui-textTruncate ${className ?? ''}`} {...euiLinkProps} />
30+
</EuiFlexItem>
31+
{revision && (
32+
<EuiFlexItem grow={true}>
33+
<EuiText color="subdued" size="xs" style={NO_WRAP_WHITE_SPACE}>
34+
<FormattedMessage
35+
id="xpack.fleet.policyNameLink.revisionNumber"
36+
defaultMessage="rev. {revNumber}"
37+
values={{ revNumber: revision }}
38+
/>
39+
</EuiText>
40+
</EuiFlexItem>
41+
)}
42+
</EuiFlexGroup>
43+
);
44+
}
45+
);

x-pack/plugins/fleet/public/applications/fleet/hooks/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ export { useBreadcrumbs } from './use_breadcrumbs';
1313
export { useLink } from './use_link';
1414
export { useKibanaLink } from './use_kibana_link';
1515
export { usePackageIconType, UsePackageIconType } from './use_package_icon_type';
16-
export { usePagination, Pagination } from './use_pagination';
16+
export { usePagination, Pagination, PAGE_SIZE_OPTIONS } from './use_pagination';
17+
export { useUrlPagination } from './use_url_pagination';
1718
export { useSorting } from './use_sorting';
1819
export { useDebounce } from './use_debounce';
1920
export * from './use_request';

x-pack/plugins/fleet/public/applications/fleet/hooks/use_pagination.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,27 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import { useState } from 'react';
7+
import { useMemo, useState } from 'react';
8+
9+
export const PAGE_SIZE_OPTIONS: readonly number[] = [5, 20, 50];
810

911
export interface Pagination {
1012
currentPage: number;
1113
pageSize: number;
1214
}
1315

14-
export function usePagination() {
15-
const [pagination, setPagination] = useState<Pagination>({
16+
export function usePagination(
17+
pageInfo: Pagination = {
1618
currentPage: 1,
1719
pageSize: 20,
18-
});
20+
}
21+
) {
22+
const [pagination, setPagination] = useState<Pagination>(pageInfo);
23+
const pageSizeOptions = useMemo(() => [...PAGE_SIZE_OPTIONS], []);
1924

2025
return {
2126
pagination,
2227
setPagination,
23-
pageSizeOptions: [5, 20, 50],
28+
pageSizeOptions,
2429
};
2530
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { useCallback, useEffect, useMemo } from 'react';
8+
import { useHistory, useLocation } from 'react-router-dom';
9+
import { useUrlParams } from './use_url_params';
10+
import { PAGE_SIZE_OPTIONS, Pagination, usePagination } from './use_pagination';
11+
12+
type SetUrlPagination = (pagination: Pagination) => void;
13+
interface UrlPagination {
14+
pagination: Pagination;
15+
setPagination: SetUrlPagination;
16+
pageSizeOptions: number[];
17+
}
18+
19+
type UrlPaginationParams = Partial<Pagination>;
20+
21+
/**
22+
* Uses URL params for pagination and also persists those to the URL as they are updated
23+
*/
24+
export const useUrlPagination = (): UrlPagination => {
25+
const location = useLocation();
26+
const history = useHistory();
27+
const { urlParams, toUrlParams } = useUrlParams();
28+
const urlPaginationParams = useMemo(() => {
29+
return paginationFromUrlParams(urlParams);
30+
}, [urlParams]);
31+
const { pagination, pageSizeOptions, setPagination } = usePagination(urlPaginationParams);
32+
33+
const setUrlPagination = useCallback<SetUrlPagination>(
34+
({ pageSize, currentPage }) => {
35+
history.push({
36+
...location,
37+
search: toUrlParams({
38+
...urlParams,
39+
currentPage,
40+
pageSize,
41+
}),
42+
});
43+
},
44+
[history, location, toUrlParams, urlParams]
45+
);
46+
47+
useEffect(() => {
48+
setPagination((prevState) => {
49+
return {
50+
...prevState,
51+
...paginationFromUrlParams(urlParams),
52+
};
53+
});
54+
}, [setPagination, urlParams]);
55+
56+
return {
57+
pagination,
58+
setPagination: setUrlPagination,
59+
pageSizeOptions,
60+
};
61+
};
62+
63+
const paginationFromUrlParams = (urlParams: UrlPaginationParams): Pagination => {
64+
const pagination: Pagination = {
65+
pageSize: 20,
66+
currentPage: 1,
67+
};
68+
69+
// Search params can appear multiple times in the URL, in which case the value for them,
70+
// once parsed, would be an array. In these case, we take the last value defined
71+
pagination.currentPage = Number(
72+
(Array.isArray(urlParams.currentPage)
73+
? urlParams.currentPage[urlParams.currentPage.length - 1]
74+
: urlParams.currentPage) ?? pagination.currentPage
75+
);
76+
pagination.pageSize =
77+
Number(
78+
(Array.isArray(urlParams.pageSize)
79+
? urlParams.pageSize[urlParams.pageSize.length - 1]
80+
: urlParams.pageSize) ?? pagination.pageSize
81+
) ?? pagination.pageSize;
82+
83+
// If Current Page is not a valid positive integer, set it to 1
84+
if (!Number.isFinite(pagination.currentPage) || pagination.currentPage < 1) {
85+
pagination.currentPage = 1;
86+
}
87+
88+
// if pageSize is not one of the expected page sizes, reset it to 20 (default)
89+
if (!PAGE_SIZE_OPTIONS.includes(pagination.pageSize)) {
90+
pagination.pageSize = 20;
91+
}
92+
93+
return pagination;
94+
};

x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{
247247
id="xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyAgentsDescriptionText"
248248
defaultMessage="{count, plural, one {# agent} other {# agents}} are enrolled with the selected agent policy."
249249
values={{
250-
count: agentPoliciesById[selectedPolicyId]?.agents || 0,
250+
count: agentPoliciesById[selectedPolicyId]?.agents ?? 0,
251251
}}
252252
/>
253253
) : null
@@ -283,7 +283,7 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{
283283
id="xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyAgentsCountText"
284284
defaultMessage="{count, plural, one {# agent} other {# agents}} enrolled"
285285
values={{
286-
count: agentPoliciesById[option.value!].agents || 0,
286+
count: agentPoliciesById[option.value!]?.agents ?? 0,
287287
}}
288288
/>
289289
</EuiTextColor>

x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
useUrlParams,
3535
useBreadcrumbs,
3636
} from '../../../hooks';
37-
import { SearchBar } from '../../../components';
37+
import { LinkAndRevision, SearchBar } from '../../../components';
3838
import { LinkedAgentCount, AgentPolicyActionMenu } from '../components';
3939
import { CreateAgentPolicyFlyout } from './components';
4040

@@ -129,26 +129,13 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
129129
}),
130130
width: '20%',
131131
render: (name: string, agentPolicy: AgentPolicy) => (
132-
<EuiFlexGroup gutterSize="s" alignItems="baseline" style={{ minWidth: 0 }}>
133-
<EuiFlexItem grow={false} className="eui-textTruncate">
134-
<EuiLink
135-
className="eui-textTruncate"
136-
href={getHref('policy_details', { policyId: agentPolicy.id })}
137-
title={name || agentPolicy.id}
138-
>
139-
{name || agentPolicy.id}
140-
</EuiLink>
141-
</EuiFlexItem>
142-
<EuiFlexItem grow={true}>
143-
<EuiText color="subdued" size="xs" style={{ whiteSpace: 'nowrap' }}>
144-
<FormattedMessage
145-
id="xpack.fleet.agentPolicyList.revisionNumber"
146-
defaultMessage="rev. {revNumber}"
147-
values={{ revNumber: agentPolicy.revision }}
148-
/>
149-
</EuiText>
150-
</EuiFlexItem>
151-
</EuiFlexGroup>
132+
<LinkAndRevision
133+
href={getHref('policy_details', { policyId: agentPolicy.id })}
134+
title={name || agentPolicy.id}
135+
revision={agentPolicy.revision}
136+
>
137+
{name || agentPolicy.id}
138+
</LinkAndRevision>
152139
),
153140
},
154141
{

x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/content.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { SettingsPanel } from './settings_panel';
1717

1818
type ContentProps = PackageInfo & Pick<DetailParams, 'panel'>;
1919

20-
const SideNavColumn = styled(LeftColumn)`
20+
const LeftSideColumn = styled(LeftColumn)`
2121
/* 🤢🤷 https://www.styled-components.com/docs/faqs#how-can-i-override-styles-with-higher-specificity */
2222
&&& {
2323
margin-top: 77px;
@@ -30,15 +30,18 @@ const ContentFlexGroup = styled(EuiFlexGroup)`
3030
`;
3131

3232
export function Content(props: ContentProps) {
33+
const showRightColumn = props.panel !== 'policies';
3334
return (
3435
<ContentFlexGroup>
35-
<SideNavColumn />
36-
<CenterColumn>
36+
<LeftSideColumn {...(!showRightColumn ? { columnGrow: 1 } : undefined)} />
37+
<CenterColumn {...(!showRightColumn ? { columnGrow: 6 } : undefined)}>
3738
<ContentPanel {...props} />
3839
</CenterColumn>
39-
<RightColumn>
40-
<RightColumnContent {...props} />
41-
</RightColumn>
40+
{showRightColumn && (
41+
<RightColumn>
42+
<RightColumnContent {...props} />
43+
</RightColumn>
44+
)}
4245
</ContentFlexGroup>
4346
);
4447
}

x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,7 @@ export function Detail() {
237237
return (entries(PanelDisplayNames)
238238
.filter(([panelId]) => {
239239
return (
240-
panelId !== 'policies' ||
241-
(packageInfoData?.response.status === InstallStatus.installed && false) // Remove `false` when ready to implement policies tab
240+
panelId !== 'policies' || packageInfoData?.response.status === InstallStatus.installed
242241
);
243242
})
244243
.map(([panelId, display]) => {

x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/layout.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,45 @@
66

77
import { EuiFlexItem } from '@elastic/eui';
88
import React, { FunctionComponent, ReactNode } from 'react';
9+
import { FlexItemGrowSize } from '@elastic/eui/src/components/flex/flex_item';
910

1011
interface ColumnProps {
1112
children?: ReactNode;
1213
className?: string;
14+
columnGrow?: FlexItemGrowSize;
1315
}
1416

15-
export const LeftColumn: FunctionComponent<ColumnProps> = ({ children, ...rest }) => {
17+
export const LeftColumn: FunctionComponent<ColumnProps> = ({
18+
columnGrow = 2,
19+
children,
20+
...rest
21+
}) => {
1622
return (
17-
<EuiFlexItem grow={2} {...rest}>
23+
<EuiFlexItem grow={columnGrow} {...rest}>
1824
{children}
1925
</EuiFlexItem>
2026
);
2127
};
2228

23-
export const CenterColumn: FunctionComponent<ColumnProps> = ({ children, ...rest }) => {
29+
export const CenterColumn: FunctionComponent<ColumnProps> = ({
30+
columnGrow = 9,
31+
children,
32+
...rest
33+
}) => {
2434
return (
25-
<EuiFlexItem grow={9} {...rest}>
35+
<EuiFlexItem grow={columnGrow} {...rest}>
2636
{children}
2737
</EuiFlexItem>
2838
);
2939
};
3040

31-
export const RightColumn: FunctionComponent<ColumnProps> = ({ children, ...rest }) => {
41+
export const RightColumn: FunctionComponent<ColumnProps> = ({
42+
columnGrow = 3,
43+
children,
44+
...rest
45+
}) => {
3246
return (
33-
<EuiFlexItem grow={3} {...rest}>
47+
<EuiFlexItem grow={columnGrow} {...rest}>
3448
{children}
3549
</EuiFlexItem>
3650
);

0 commit comments

Comments
 (0)