[Fleet] Persist category and search in url on package page (#111571)
This commit is contained in:
parent
fcd27d17c2
commit
d3dd617cd9
|
@ -39,7 +39,7 @@ export const NoPackagePolicies = memo<{ policyId: string }>(({ policyId }) => {
|
|||
fill
|
||||
onClick={() =>
|
||||
application.navigateToApp(INTEGRATIONS_PLUGIN_ID, {
|
||||
path: pagePathGetters.integrations_all()[1],
|
||||
path: pagePathGetters.integrations_all({})[1],
|
||||
state: { forAgentPolicyId: policyId },
|
||||
})
|
||||
}
|
||||
|
|
|
@ -270,7 +270,7 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
|
|||
iconType="plusInCircle"
|
||||
onClick={() => {
|
||||
application.navigateToApp(INTEGRATIONS_PLUGIN_ID, {
|
||||
path: pagePathGetters.integrations_all()[1],
|
||||
path: pagePathGetters.integrations_all({})[1],
|
||||
state: { forAgentPolicyId: agentPolicy.id },
|
||||
});
|
||||
}}
|
||||
|
|
|
@ -32,7 +32,7 @@ import {
|
|||
import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common';
|
||||
|
||||
import { AgentPolicyContextProvider, useUrlModal } from './hooks';
|
||||
import { INTEGRATIONS_ROUTING_PATHS } from './constants';
|
||||
import { INTEGRATIONS_ROUTING_PATHS, pagePathGetters } from './constants';
|
||||
|
||||
import { Error, Loading, SettingFlyout } from './components';
|
||||
|
||||
|
@ -242,7 +242,7 @@ export const AppRoutes = memo(() => {
|
|||
// BWC < 7.15 Fleet was using a hash router: redirect old routes using hash
|
||||
const shouldRedirectHash = location.pathname === '' && location.hash.length > 0;
|
||||
if (!shouldRedirectHash) {
|
||||
return <Redirect to={INTEGRATIONS_ROUTING_PATHS.integrations_all} />;
|
||||
return <Redirect to={pagePathGetters.integrations_all({})[1]} />;
|
||||
}
|
||||
const pathname = location.hash.replace(/^#/, '');
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { Fragment, useCallback, useState } from 'react';
|
||||
import type { Query } from '@elastic/eui';
|
||||
import {
|
||||
EuiFlexGrid,
|
||||
EuiFlexGroup,
|
||||
|
@ -34,7 +33,9 @@ interface ListProps {
|
|||
controls?: ReactNode;
|
||||
title: string;
|
||||
list: PackageList;
|
||||
setSelectedCategory?: (category: string) => void;
|
||||
initialSearch?: string;
|
||||
setSelectedCategory: (category: string) => void;
|
||||
onSearchChange: (search: string) => void;
|
||||
showMissingIntegrationMessage?: boolean;
|
||||
}
|
||||
|
||||
|
@ -43,33 +44,28 @@ export function PackageListGrid({
|
|||
controls,
|
||||
title,
|
||||
list,
|
||||
setSelectedCategory = () => {},
|
||||
initialSearch,
|
||||
onSearchChange,
|
||||
setSelectedCategory,
|
||||
showMissingIntegrationMessage = false,
|
||||
}: ListProps) {
|
||||
const initialQuery = EuiSearchBar.Query.MATCH_ALL;
|
||||
|
||||
const [query, setQuery] = useState<Query | null>(initialQuery);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [searchTerm, setSearchTerm] = useState(initialSearch || '');
|
||||
const localSearchRef = useLocalSearch(list);
|
||||
|
||||
const onQueryChange = ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
query,
|
||||
queryText: userInput,
|
||||
error,
|
||||
}: {
|
||||
query: Query | null;
|
||||
queryText: string;
|
||||
error: { message: string } | null;
|
||||
}) => {
|
||||
if (!error) {
|
||||
setQuery(query);
|
||||
onSearchChange(userInput);
|
||||
setSearchTerm(userInput);
|
||||
}
|
||||
};
|
||||
|
||||
const resetQuery = () => {
|
||||
setQuery(initialQuery);
|
||||
setSearchTerm('');
|
||||
};
|
||||
|
||||
|
@ -99,7 +95,7 @@ export function PackageListGrid({
|
|||
<EuiFlexItem grow={1}>{controlsContent}</EuiFlexItem>
|
||||
<EuiFlexItem grow={3}>
|
||||
<EuiSearchBar
|
||||
query={query || undefined}
|
||||
query={searchTerm || undefined}
|
||||
box={{
|
||||
placeholder: i18n.translate('xpack.fleet.epmList.searchPackagesPlaceholder', {
|
||||
defaultMessage: 'Search for integrations',
|
||||
|
|
|
@ -5,13 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useState, useMemo } from 'react';
|
||||
import { Switch, Route, useLocation, useHistory } from 'react-router-dom';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { Switch, Route, useLocation, useHistory, useParams } from 'react-router-dom';
|
||||
import semverLt from 'semver/functions/lt';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { installationStatuses } from '../../../../../../../common/constants';
|
||||
import { INTEGRATIONS_ROUTING_PATHS } from '../../../../constants';
|
||||
import {
|
||||
INTEGRATIONS_ROUTING_PATHS,
|
||||
INTEGRATIONS_SEARCH_QUERYPARAM,
|
||||
pagePathGetters,
|
||||
} from '../../../../constants';
|
||||
import { useGetCategories, useGetPackages, useBreadcrumbs } from '../../../../hooks';
|
||||
import { doesPackageHaveIntegrations } from '../../../../services';
|
||||
import { DefaultLayout } from '../../../../layouts';
|
||||
|
@ -20,6 +24,22 @@ import { PackageListGrid } from '../../components/package_list_grid';
|
|||
|
||||
import { CategoryFacets } from './category_facets';
|
||||
|
||||
export interface CategoryParams {
|
||||
category?: string;
|
||||
}
|
||||
|
||||
function getParams(params: CategoryParams, search: string) {
|
||||
const { category } = params;
|
||||
const selectedCategory = category || '';
|
||||
const queryParams = new URLSearchParams(search);
|
||||
const searchParam = queryParams.get(INTEGRATIONS_SEARCH_QUERYPARAM) || '';
|
||||
return { selectedCategory, searchParam };
|
||||
}
|
||||
|
||||
function categoryExists(category: string, categories: CategorySummaryItem[]) {
|
||||
return categories.some((c) => c.id === category);
|
||||
}
|
||||
|
||||
export const EPMHomePage: React.FC = memo(() => {
|
||||
return (
|
||||
<Switch>
|
||||
|
@ -69,7 +89,28 @@ const InstalledPackages: React.FC = memo(() => {
|
|||
const { data: allPackages, isLoading: isLoadingPackages } = useGetPackages({
|
||||
experimental: true,
|
||||
});
|
||||
const [selectedCategory, setSelectedCategory] = useState('');
|
||||
|
||||
const { selectedCategory, searchParam } = getParams(
|
||||
useParams<CategoryParams>(),
|
||||
useLocation().search
|
||||
);
|
||||
const history = useHistory();
|
||||
function setSelectedCategory(categoryId: string) {
|
||||
const url = pagePathGetters.integrations_installed({
|
||||
category: categoryId,
|
||||
searchTerm: searchParam,
|
||||
})[1];
|
||||
history.push(url);
|
||||
}
|
||||
function setSearchTerm(search: string) {
|
||||
// Use .replace so the browser's back button is tied to single keystroke
|
||||
history.replace(
|
||||
pagePathGetters.integrations_installed({
|
||||
category: selectedCategory,
|
||||
searchTerm: search,
|
||||
})[1]
|
||||
);
|
||||
}
|
||||
|
||||
const allInstalledPackages = useMemo(
|
||||
() =>
|
||||
|
@ -114,21 +155,28 @@ const InstalledPackages: React.FC = memo(() => {
|
|||
[allInstalledPackages.length, updatablePackages.length]
|
||||
);
|
||||
|
||||
const controls = useMemo(
|
||||
() => (
|
||||
if (!categoryExists(selectedCategory, categories)) {
|
||||
history.replace(
|
||||
pagePathGetters.integrations_installed({ category: '', searchTerm: searchParam })[1]
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const controls = (
|
||||
<CategoryFacets
|
||||
categories={categories}
|
||||
selectedCategory={selectedCategory}
|
||||
onCategoryChange={({ id }: CategorySummaryItem) => setSelectedCategory(id)}
|
||||
/>
|
||||
),
|
||||
[categories, selectedCategory]
|
||||
);
|
||||
|
||||
return (
|
||||
<PackageListGrid
|
||||
isLoading={isLoadingPackages}
|
||||
controls={controls}
|
||||
setSelectedCategory={setSelectedCategory}
|
||||
onSearchChange={setSearchTerm}
|
||||
initialSearch={searchParam}
|
||||
title={title}
|
||||
list={selectedCategory === 'updates_available' ? updatablePackages : allInstalledPackages}
|
||||
/>
|
||||
|
@ -137,10 +185,25 @@ const InstalledPackages: React.FC = memo(() => {
|
|||
|
||||
const AvailablePackages: React.FC = memo(() => {
|
||||
useBreadcrumbs('integrations_all');
|
||||
const { selectedCategory, searchParam } = getParams(
|
||||
useParams<CategoryParams>(),
|
||||
useLocation().search
|
||||
);
|
||||
const history = useHistory();
|
||||
const queryParams = new URLSearchParams(useLocation().search);
|
||||
const initialCategory = queryParams.get('category') || '';
|
||||
const [selectedCategory, setSelectedCategory] = useState(initialCategory);
|
||||
function setSelectedCategory(categoryId: string) {
|
||||
const url = pagePathGetters.integrations_all({
|
||||
category: categoryId,
|
||||
searchTerm: searchParam,
|
||||
})[1];
|
||||
history.push(url);
|
||||
}
|
||||
function setSearchTerm(search: string) {
|
||||
// Use .replace so the browser's back button is tied to single keystroke
|
||||
history.replace(
|
||||
pagePathGetters.integrations_all({ category: selectedCategory, searchTerm: search })[1]
|
||||
);
|
||||
}
|
||||
|
||||
const { data: allCategoryPackagesRes, isLoading: isLoadingAllPackages } = useGetPackages({
|
||||
category: '',
|
||||
});
|
||||
|
@ -182,16 +245,17 @@ const AvailablePackages: React.FC = memo(() => {
|
|||
[allPackages?.length, categoriesRes]
|
||||
);
|
||||
|
||||
if (!categoryExists(selectedCategory, categories)) {
|
||||
history.replace(pagePathGetters.integrations_all({ category: '', searchTerm: searchParam })[1]);
|
||||
return null;
|
||||
}
|
||||
|
||||
const controls = categories ? (
|
||||
<CategoryFacets
|
||||
isLoading={isLoadingCategories || isLoadingAllPackages}
|
||||
categories={categories}
|
||||
selectedCategory={selectedCategory}
|
||||
onCategoryChange={({ id }: CategorySummaryItem) => {
|
||||
// clear category query param in the url
|
||||
if (queryParams.get('category')) {
|
||||
history.push({});
|
||||
}
|
||||
setSelectedCategory(id);
|
||||
}}
|
||||
/>
|
||||
|
@ -202,8 +266,10 @@ const AvailablePackages: React.FC = memo(() => {
|
|||
isLoading={isLoadingCategoryPackages}
|
||||
title={title}
|
||||
controls={controls}
|
||||
initialSearch={searchParam}
|
||||
list={packages}
|
||||
setSelectedCategory={setSelectedCategory}
|
||||
onSearchChange={setSearchTerm}
|
||||
showMissingIntegrationMessage
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -11,14 +11,14 @@ export type StaticPage =
|
|||
| 'base'
|
||||
| 'overview'
|
||||
| 'integrations'
|
||||
| 'integrations_all'
|
||||
| 'integrations_installed'
|
||||
| 'policies'
|
||||
| 'policies_list'
|
||||
| 'enrollment_tokens'
|
||||
| 'data_streams';
|
||||
|
||||
export type DynamicPage =
|
||||
| 'integrations_all'
|
||||
| 'integrations_installed'
|
||||
| 'integration_details_overview'
|
||||
| 'integration_details_policies'
|
||||
| 'integration_details_assets'
|
||||
|
@ -65,10 +65,11 @@ export const FLEET_ROUTING_PATHS = {
|
|||
add_integration_to_policy: '/integrations/:pkgkey/add-integration/:integration?',
|
||||
};
|
||||
|
||||
export const INTEGRATIONS_SEARCH_QUERYPARAM = 'q';
|
||||
export const INTEGRATIONS_ROUTING_PATHS = {
|
||||
integrations: '/:tabId',
|
||||
integrations_all: '/browse',
|
||||
integrations_installed: '/installed',
|
||||
integrations_all: '/browse/:category?',
|
||||
integrations_installed: '/installed/:category?',
|
||||
integration_details: '/detail/:pkgkey/:panel?',
|
||||
integration_details_overview: '/detail/:pkgkey/overview',
|
||||
integration_details_policies: '/detail/:pkgkey/policies',
|
||||
|
@ -87,8 +88,16 @@ export const pagePathGetters: {
|
|||
base: () => [FLEET_BASE_PATH, '/'],
|
||||
overview: () => [FLEET_BASE_PATH, '/'],
|
||||
integrations: () => [INTEGRATIONS_BASE_PATH, '/'],
|
||||
integrations_all: () => [INTEGRATIONS_BASE_PATH, '/browse'],
|
||||
integrations_installed: () => [INTEGRATIONS_BASE_PATH, '/installed'],
|
||||
integrations_all: ({ searchTerm, category }: { searchTerm?: string; category?: string }) => {
|
||||
const categoryPath = category ? `/${category}` : ``;
|
||||
const queryParams = searchTerm ? `?${INTEGRATIONS_SEARCH_QUERYPARAM}=${searchTerm}` : ``;
|
||||
return [INTEGRATIONS_BASE_PATH, `/browse${categoryPath}${queryParams}`];
|
||||
},
|
||||
integrations_installed: ({ query, category }: { query?: string; category?: string }) => {
|
||||
const categoryPath = category ? `/${category}` : ``;
|
||||
const queryParams = query ? `?${INTEGRATIONS_SEARCH_QUERYPARAM}=${query}` : ``;
|
||||
return [INTEGRATIONS_BASE_PATH, `/installed${categoryPath}${queryParams}`];
|
||||
},
|
||||
integration_details_overview: ({ pkgkey, integration }) => [
|
||||
INTEGRATIONS_BASE_PATH,
|
||||
`/detail/${pkgkey}/overview${integration ? `?integration=${integration}` : ''}`,
|
||||
|
|
Loading…
Reference in a new issue