diff --git a/playwright/UI/FilterTypePatch.spec.ts b/playwright/UI/FilterTypePatch.spec.ts index 7828f758e..137176938 100644 --- a/playwright/UI/FilterTypePatch.spec.ts +++ b/playwright/UI/FilterTypePatch.spec.ts @@ -188,8 +188,8 @@ test.describe('Patch Filters', () => { await applyFilterSubtype(page, 'Name', { name: packageName!, inputType: 'search' }); const rows = page.getByRole('row'); - await expect(rows).toHaveCount(2); - await expect(rows.filter({ hasText: packageName })).toHaveCount(1); + expect(await rows.count()).toBeGreaterThan(1); // header + at least one body row + await expect(rows.filter({ has: page.getByText(packageName!, { exact: true }) })).toHaveCount(1); await resetFilters(page); }); diff --git a/playwright/UI/PaginationTests.spec.ts b/playwright/UI/PaginationTests.spec.ts index 261b25333..f8088dcd1 100644 --- a/playwright/UI/PaginationTests.spec.ts +++ b/playwright/UI/PaginationTests.spec.ts @@ -44,7 +44,7 @@ test.describe('Pagination', () => { await test.step('Filter and sort the table', async () => { await page.getByPlaceholder(/^Filter by name.*$/).fill(prefix); await waitForTableLoad(page); - await page.getByRole('columnheader', { name: 'Name' }).click(); + await page.getByRole('columnheader', { name: 'Name' }).getByRole('button').click(); await expect(toolbarPaginationButton).toHaveText('1 - 12 of 12'); await expect(targetRows).toHaveCount(12); diff --git a/playwright/UI/VerifyExportingFeature.spec.ts b/playwright/UI/VerifyExportingFeature.spec.ts index 59440a779..bc2fff977 100644 --- a/playwright/UI/VerifyExportingFeature.spec.ts +++ b/playwright/UI/VerifyExportingFeature.spec.ts @@ -125,6 +125,9 @@ test.describe('Verify exporting feature', () => { getRowCellByHeader(page, row, 'Installed packages'), ]); + await expect(osCell).not.toBeEmpty(); + await expect(installedPackagesCell).not.toBeEmpty(); + const [osText, installedPackagesText] = await Promise.all([ osCell.textContent(), installedPackagesCell.textContent(), diff --git a/playwright/test-utils/helpers/filters.ts b/playwright/test-utils/helpers/filters.ts index 77a1675c8..dd86c6b6f 100644 --- a/playwright/test-utils/helpers/filters.ts +++ b/playwright/test-utils/helpers/filters.ts @@ -164,10 +164,10 @@ export const applyFilterSubtype = async ( // Select the filter type first await selectFilterType(page, filterType); - // Tags uses a textbox (combobox) "input with dropdown and clear", not Group filter. + // Tags uses a textbox (combobox) "input with dropdown and clear button", not Group filter. // Two exist (global filter + table toolbar); use the table toolbar one (second) when filtering the Systems table. if (filterType === 'Tags' && subtype.inputType !== 'search') { - const tagsTextbox = page.getByRole('textbox', { name: 'input with dropdown and clear' }).nth(1); + const tagsTextbox = page.locator('.pf-v6-c-toolbar').getByRole('textbox', { name: 'input with dropdown and clear button' }).first(); await tagsTextbox.waitFor({ state: 'visible', timeout: 10000 }); await tagsTextbox.click(); } else { diff --git a/playwright/test-utils/helpers/navigation.ts b/playwright/test-utils/helpers/navigation.ts index 6e85c8a2e..57138f27c 100644 --- a/playwright/test-utils/helpers/navigation.ts +++ b/playwright/test-utils/helpers/navigation.ts @@ -87,6 +87,6 @@ export const navigateToTemplates = async (page: Page) => { await page.goto('/insights/content/templates'); } await expect(page.getByRole('heading', { name: 'Templates' })).toBeVisible(); - const subheading = page.getByText('View all content templates within your organization.'); + const subheading = page.getByText('Control content stability of your systems by combining repositories into templates.'); await expect(subheading).toBeVisible(); // Wait for either list page or zero state }; diff --git a/src/SmartComponents/AdvisorySystems/AdvisorySystemsTable.js b/src/SmartComponents/AdvisorySystems/AdvisorySystemsTable.js index a9b620882..e505476ee 100644 --- a/src/SmartComponents/AdvisorySystems/AdvisorySystemsTable.js +++ b/src/SmartComponents/AdvisorySystems/AdvisorySystemsTable.js @@ -5,7 +5,6 @@ import propTypes from 'prop-types'; import { shallowEqual, useDispatch, useSelector, useStore } from 'react-redux'; import { combineReducers } from 'redux'; import messages from '../../Messages'; -import searchFilter from '../../PresentationalComponents/Filters/SearchFilter'; import { defaultReducers } from '../../store'; import { systemSelectAction } from '../../store/Actions/Actions'; import { @@ -59,30 +58,21 @@ const AdvisorySystemsTable = ({ const { systemProfile, selectedTags, filter, search, page, perPage, sort } = queryParams; const [deleteFilters, deleteFilterGroup] = useRemoveFilter( - { search, ...filter }, + filter, apply, pageDefaultFilters.advisorySystems, ); const filterConfig = { - items: [ - searchFilter( - apply, - search, - intl.formatMessage(messages.labelsFiltersSystemsSearchTitle), - intl.formatMessage(messages.labelsFiltersSystemsSearchPlaceholder), - ), - advisoryStatusFilter(apply, filter), - ], + items: [advisoryStatusFilter(apply, filter)], }; const activeFiltersConfig = buildActiveFilterConfig( filter, - search, + undefined, deleteFilters, deleteFilterGroup, intl.formatMessage(messages.labelsFiltersSystemsSearchTitle), - deleteFilterGroup, pageDefaultFilters.advisorySystems, ); @@ -134,12 +124,19 @@ const AdvisorySystemsTable = ({ autoRefresh initialLoading ignoreRefresh - hideFilters={{ all: true, tags: false, hostGroupFilter: false, operatingSystem: false }} + hideFilters={{ + all: true, + name: false, + tags: false, + hostGroupFilter: false, + operatingSystem: false, + }} columns={(inventoryColumns) => mergeInventoryColumns(ADVISORY_SYSTEMS_COLUMNS, inventoryColumns) } showTags customFilters={{ + filters: [...(search ? [{ value: 'hostname_or_id', filter: search }] : [])], patchParams: { search, filter, diff --git a/src/SmartComponents/AdvisorySystems/AdvisorySystemsTable.test.js b/src/SmartComponents/AdvisorySystems/AdvisorySystemsTable.test.js index e371f1613..f6b13b733 100644 --- a/src/SmartComponents/AdvisorySystems/AdvisorySystemsTable.test.js +++ b/src/SmartComponents/AdvisorySystems/AdvisorySystemsTable.test.js @@ -71,6 +71,7 @@ describe('AdvisorySystemsTable.js', () => { expect(InventoryTable).toHaveBeenCalledWith( expect.objectContaining({ customFilters: { + filters: [{ filter: 'test-search', value: 'hostname_or_id' }], patchParams: { filter: { status: ['Installable'] }, search: 'test-search', @@ -90,6 +91,7 @@ describe('AdvisorySystemsTable.js', () => { expect.objectContaining({ hideFilters: { all: true, + name: false, tags: false, hostGroupFilter: false, operatingSystem: false, @@ -136,16 +138,6 @@ describe('AdvisorySystemsTable.js', () => { expect.objectContaining({ filterConfig: { items: [ - { - filterValues: { - 'aria-label': 'search-field', - onChange: expect.any(Function), - placeholder: 'Filter by name', - value: undefined, - }, - label: 'Name', - type: 'text', - }, { filterValues: { items: [ diff --git a/src/SmartComponents/PackageSystems/PackageSystems.js b/src/SmartComponents/PackageSystems/PackageSystems.js index 4b95f25ab..9fdb3b909 100644 --- a/src/SmartComponents/PackageSystems/PackageSystems.js +++ b/src/SmartComponents/PackageSystems/PackageSystems.js @@ -5,7 +5,6 @@ import propTypes from 'prop-types'; import { shallowEqual, useDispatch, useSelector, useStore } from 'react-redux'; import { useSearchParams } from 'react-router-dom'; import messages from '../../Messages'; -import searchFilter from '../../PresentationalComponents/Filters/SearchFilter'; import statusFilter from '../../PresentationalComponents/Filters/StatusFilter'; import versionFilter from '../../PresentationalComponents/Filters/VersionFilter'; import ErrorHandler from '../../PresentationalComponents/Snippets/ErrorHandler'; @@ -85,35 +84,26 @@ const PackageSystems = ({ packageName }) => { ); const [deleteFilters, deleteFilterGroup] = useRemoveFilter( - { ...filter, search }, + filter, apply, pageDefaultFilters.packageSystems, ); const filterConfig = { - items: [ - searchFilter( - apply, - search, - intl.formatMessage(messages.labelsFiltersSystemsSearchTitle), - intl.formatMessage(messages.labelsFiltersSystemsSearchPlaceholder), - ), - statusFilter(apply, filter), - versionFilter(apply, filter, packageVersions), - ], + items: [statusFilter(apply, filter), versionFilter(apply, filter, packageVersions)], }; const activeFiltersConfig = useMemo( () => buildActiveFilterConfig( filter, - search, + undefined, deleteFilters, deleteFilterGroup, intl.formatMessage(messages.labelsFiltersSystemsSearchTitle), pageDefaultFilters.packageSystems, ), - [deleteFilters, deleteFilterGroup, filter, search], + [deleteFilters, deleteFilterGroup, filter], ); const constructFilename = (system) => `${system.available_evra}`; @@ -189,13 +179,20 @@ const PackageSystems = ({ packageName }) => { isFullView autoRefresh initialLoading - hideFilters={{ all: true, tags: false, hostGroupFilter: false, operatingSystem: false }} + hideFilters={{ + all: true, + name: false, + tags: false, + hostGroupFilter: false, + operatingSystem: false, + }} columns={(inventoryColumns) => mergeInventoryColumns(PACKAGE_SYSTEMS_COLUMNS, inventoryColumns) } showTags getEntities={getEntites} customFilters={{ + filters: [...(search ? [{ value: 'hostname_or_id', filter: search }] : [])], patchParams: { search, filter, diff --git a/src/SmartComponents/Systems/SystemTable.test.js b/src/SmartComponents/Systems/SystemTable.test.js index 57cfea82f..9486ea22b 100644 --- a/src/SmartComponents/Systems/SystemTable.test.js +++ b/src/SmartComponents/Systems/SystemTable.test.js @@ -76,6 +76,7 @@ describe('SystemsTable', () => { SystemsStore: { queryParams: { filter: { packages_updatable: 'eq:0' }, + search: 'test-search', selectedTags: ['tags=test-tag'], systemProfile: { ansible: { controller_version: 'not_nil' } }, }, @@ -87,8 +88,10 @@ describe('SystemsTable', () => { expect(InventoryTable).toHaveBeenCalledWith( expect.objectContaining({ customFilters: { + filters: [{ filter: 'test-search', value: 'hostname_or_id' }], patchParams: { filter: { packages_updatable: 'eq:0' }, + search: 'test-search', selectedTags: ['tags=test-tag'], systemProfile: { ansible: { controller_version: 'not_nil' } }, }, diff --git a/src/SmartComponents/Systems/SystemsTable.js b/src/SmartComponents/Systems/SystemsTable.js index 2601abe80..99a2fcdf9 100644 --- a/src/SmartComponents/Systems/SystemsTable.js +++ b/src/SmartComponents/Systems/SystemsTable.js @@ -70,6 +70,7 @@ const SystemsTable = ({ page, perPage, sort, + search, } = queryParams; const { os: operatingSystemFilter, @@ -135,7 +136,7 @@ const SystemsTable = ({ const activeFiltersConfig = useMemo(() => { const config = buildActiveFilterConfig( filter, - '', + undefined, deleteFilters, deleteFilterGroup, intl.formatMessage(messages.labelsFiltersSystemsSearchTitle), @@ -208,12 +209,12 @@ const SystemsTable = ({ columns={(inventoryColumns) => mergeInventoryColumns(SYSTEMS_LIST_COLUMNS, inventoryColumns)} showTags customFilters={{ - ...(operatingSystemFilter - ? { - filters: [...(osFilter || [])], - } - : {}), + filters: [ + ...(search ? [{ value: 'hostname_or_id', filter: search }] : []), + ...(operatingSystemFilter ? osFilter : []), + ], patchParams: { + search, filter: apiFilter, systemProfile: mergedSystemProfile, selectedTags, @@ -228,7 +229,7 @@ const SystemsTable = ({ ...defaultReducers, ...mergeWithEntities( inventoryEntitiesReducer(SYSTEMS_LIST_COLUMNS, modifyInventory), - persistantParams({ page, perPage, sort }, decodedParams), + persistantParams({ page, perPage, sort, search }, decodedParams), ), }), ); diff --git a/src/Utilities/SystemHelpers.test.js b/src/Utilities/SystemHelpers.test.js index 2f910e0ca..47159bef3 100644 --- a/src/Utilities/SystemHelpers.test.js +++ b/src/Utilities/SystemHelpers.test.js @@ -1,4 +1,6 @@ +import { renderHook } from '@testing-library/react'; import { + buildFilterConfig, createSystemsSortBy, mergeInventoryColumns, workloadToSystemProfile, @@ -170,3 +172,25 @@ describe('workloadToSystemProfile', () => { }); }); }); + +describe('buildFilterConfig', () => { + it('should return filter items for status, patch status, and workload', () => { + const apply = jest.fn(); + const filter = { stale: [true, false] }; + + const { result } = renderHook(() => buildFilterConfig(filter, apply)); + expect(result.current.items).toHaveLength(3); + expect(result.current.items[0]).toMatchObject({ + label: 'Status', + type: 'checkbox', + }); + expect(result.current.items[1]).toMatchObject({ + label: 'Patch status', + type: 'singleSelect', + }); + expect(result.current.items[2]).toMatchObject({ + label: 'Workload', + type: 'checkbox', + }); + }); +}); diff --git a/src/Utilities/hooks/Hooks.js b/src/Utilities/hooks/Hooks.js index 0064f507b..01d2f9a77 100644 --- a/src/Utilities/hooks/Hooks.js +++ b/src/Utilities/hooks/Hooks.js @@ -234,7 +234,7 @@ export const useGetEntities = ( ) => { const { selectedTags: activeTags = [] } = patchParams; const { selectedTags } = mapGlobalFilters(filters.tagFilters); - const search = filters?.hostnameOrId || ''; + const search = filters?.hostnameOrId ?? ''; const sort = createSystemsSortBy(orderBy, orderDirection, packageName); const filter = buildApiFilters(patchParams.filter, filters); @@ -277,6 +277,7 @@ export const useGetEntities = ( perPage, sort, filter, + search, }), { replace: true, diff --git a/src/Utilities/hooks/Hooks.test.js b/src/Utilities/hooks/Hooks.test.js index 58fe822b1..ad1143289 100644 --- a/src/Utilities/hooks/Hooks.test.js +++ b/src/Utilities/hooks/Hooks.test.js @@ -226,6 +226,33 @@ describe('Custom hooks tests', () => { await request; }); + it('useGetEntities: should propagate search from inventory filters to setSearchParams', async () => { + const fetchApi = jest.fn(() => Promise.resolve({ data: [], meta: { total_items: 0 } })); + const apply = jest.fn(); + const setSearchParams = jest.fn(); + const params = { + orderBy: 'display_name', + orderDirection: 'ASC', + page: 1, + per_page: 20, + patchParams: { + filter: { stale: [true, false] }, + }, + filters: { + hostnameOrId: 'test-host', + }, + }; + const { result } = renderHook(() => useGetEntities(fetchApi, apply, {}, setSearchParams)); + + await result.current([], params); + + expect(fetchApi).toHaveBeenCalledWith(expect.objectContaining({ search: 'test-host' })); + expect(apply).toHaveBeenCalledWith(expect.objectContaining({ search: 'test-host' })); + expect(setSearchParams).toHaveBeenCalledWith(expect.stringContaining('search=test-host'), { + replace: true, + }); + }); + it('useEntitlements, should return correct entitlements', async () => { const { result } = renderHook(() => useEntitlements()); const finalResult = await result.current();