diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx index e19cb7b1ca5e..80b0c0a114f2 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx @@ -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 }, }) } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx index eb5f8e6c6a9b..eca02096781a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx @@ -270,7 +270,7 @@ export const PackagePoliciesTable: React.FunctionComponent = ({ iconType="plusInCircle" onClick={() => { application.navigateToApp(INTEGRATIONS_PLUGIN_ID, { - path: pagePathGetters.integrations_all()[1], + path: pagePathGetters.integrations_all({})[1], state: { forAgentPolicyId: agentPolicy.id }, }); }} diff --git a/x-pack/plugins/fleet/public/applications/integrations/app.tsx b/x-pack/plugins/fleet/public/applications/integrations/app.tsx index f0c94b51677e..d37cbe4c166d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/app.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/app.tsx @@ -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 ; + return ; } const pathname = location.hash.replace(/^#/, ''); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx index e017de920637..6bbd479c5c2b 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx @@ -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(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({ {controlsContent} c.id === category); +} + export const EPMHomePage: React.FC = memo(() => { return ( @@ -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(), + 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( - () => ( - setSelectedCategory(id)} - /> - ), - [categories, selectedCategory] + if (!categoryExists(selectedCategory, categories)) { + history.replace( + pagePathGetters.integrations_installed({ category: '', searchTerm: searchParam })[1] + ); + return null; + } + + const controls = ( + setSelectedCategory(id)} + /> ); return ( @@ -137,10 +185,25 @@ const InstalledPackages: React.FC = memo(() => { const AvailablePackages: React.FC = memo(() => { useBreadcrumbs('integrations_all'); + const { selectedCategory, searchParam } = getParams( + useParams(), + 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 ? ( { - // 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 /> ); diff --git a/x-pack/plugins/fleet/public/constants/page_paths.ts b/x-pack/plugins/fleet/public/constants/page_paths.ts index 828ec135cd93..93ef0d56b17a 100644 --- a/x-pack/plugins/fleet/public/constants/page_paths.ts +++ b/x-pack/plugins/fleet/public/constants/page_paths.ts @@ -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}` : ''}`,