From 3c67d36fc864c4edac12074249544286922da2b5 Mon Sep 17 00:00:00 2001 From: Jakub Hadvig Date: Wed, 15 Apr 2026 13:02:38 +0200 Subject: [PATCH] OCPBUGS-82505: Re-enable add-flow-ci.feature e2e tests disabled for createRoot adoption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace setState-inside-useMemo anti-pattern in CatalogView with derived useMemo values, preventing extra render cycles that detach DOM nodes during Cypress interactions. Wrap search keyword URL updates in startTransition, fix ConsoleSelect prop→state feedback loop, and use startTransition for ImageSearch field resets. Split .clear().type() chains in catalog and topology search helpers so Cypress re-queries the DOM between operations. Co-Authored-By: Claude Opus 4.6 --- .../catalog/catalog-view/CatalogView.tsx | 123 +++--------------- .../components/formik-fields/InputField.tsx | 13 +- .../features/e2e/add-flow-ci.feature | 3 +- .../support/pages/add-flow/catalog-page.ts | 6 +- .../pages/add-flow/container-image-page.ts | 14 +- .../import/image-search/ImageSearch.tsx | 11 +- .../image-search/ImageSearchSection.tsx | 2 +- .../pages/topology/topology-helper-page.ts | 5 +- .../components/utils/console-select.tsx | 9 +- 9 files changed, 63 insertions(+), 123 deletions(-) diff --git a/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogView.tsx b/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogView.tsx index 9f1e8a5d8f9..f996036fa3d 100644 --- a/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogView.tsx +++ b/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogView.tsx @@ -1,5 +1,5 @@ import type { ReactNode, FC } from 'react'; -import { useMemo, useState, useRef, useCallback, useEffect } from 'react'; +import { useMemo, useState, useRef, useCallback, useEffect, startTransition } from 'react'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router'; @@ -8,13 +8,7 @@ import type { CatalogItem } from '@console/dynamic-plugin-sdk/src/extensions'; import { isModalOpen } from '@console/internal/components/modals'; import { useQueryParams } from '../../../hooks/useQueryParams'; import PaneBody from '../../layout/PaneBody'; -import { - setURLParams, - updateURLParams, - getCatalogTypeCounts, - calculateCatalogItemRelevanceScore, - getRedHatPriority, -} from '../utils/catalog-utils'; +import { setURLParams, updateURLParams, getCatalogTypeCounts } from '../utils/catalog-utils'; import { categorize, findActiveCategory, @@ -102,8 +96,6 @@ const CatalogView: FC = ({ }, [filterGroups, filters, queryParams]); const [filterGroupsShowAll, setFilterGroupsShowAll] = useState>({}); - const [filterGroupCounts, setFilterGroupCounts] = useState({}); - const [catalogTypeCounts, setCatalogTypeCounts] = useState({}); const isGrouped = _.has(groupings, activeGrouping); @@ -137,7 +129,9 @@ const CatalogView: FC = ({ const handleSearchKeywordChange = useCallback( (searchKeyword) => { - updateURLParams(CatalogQueryParams.KEYWORD, searchKeyword, navigate); + startTransition(() => { + updateURLParams(CatalogQueryParams.KEYWORD, searchKeyword, navigate); + }); }, [navigate], ); @@ -185,100 +179,25 @@ const CatalogView: FC = ({ [activeCategoryId, catalogCategories], ); - const filteredItems: CatalogItem[] = useMemo(() => { + const filteredBySearchItems = useMemo(() => { const filteredByCategoryItems = filterByCategory(items, activeCategoryId, categorizedIds); - const filteredBySearchItems = filterBySearchKeyword( - filteredByCategoryItems, - activeSearchKeyword, - sortOrder, - ); - const filteredByAttributes = filterByAttributes(filteredBySearchItems, activeFilters); - - const filterCounts = getFilterGroupCounts(filteredBySearchItems, activeFilters, filterGroups); - setFilterGroupCounts(filterCounts); - - const typeCounts = getCatalogTypeCounts(filteredBySearchItems, catalogTypes); - setCatalogTypeCounts(typeCounts); - - // Console table for final filtered results (only for operators) - if (filteredByAttributes.length > 0) { - // Check if we have active filters beyond just search and category - const hasAttributeFilters = Object.values(activeFilters).some((filterGroup) => - Object.values(filterGroup).some((filter) => filter.active), - ); - - // Only show console.table if we have search term or attribute filters - if (activeSearchKeyword || hasAttributeFilters) { - const REDHAT_PRIORITY = { - EXACT_MATCH: 2, - CONTAINS_REDHAT: 1, - NON_REDHAT: 0, - }; - - const tableData = filteredByAttributes.map((item) => { - // Ensure we have the scoring properties, calculate them if missing - const relevanceScore = activeSearchKeyword - ? (item as any).relevanceScore ?? - calculateCatalogItemRelevanceScore(activeSearchKeyword, item) - : 'N/A (No search)'; - const redHatPriority = (item as any).redHatPriority ?? getRedHatPriority(item); - - return { - Title: item.name || 'N/A', - 'Search Relevance Score': relevanceScore, - 'Is Red Hat Provider (Priority)': - redHatPriority === REDHAT_PRIORITY.EXACT_MATCH - ? `Exact Match (${REDHAT_PRIORITY.EXACT_MATCH})` - : redHatPriority === REDHAT_PRIORITY.CONTAINS_REDHAT - ? `Contains Red Hat (${REDHAT_PRIORITY.CONTAINS_REDHAT})` - : `Non-Red Hat (${REDHAT_PRIORITY.NON_REDHAT})`, - Provider: item.attributes?.provider || item.provider || 'N/A', - Type: item.type || 'N/A', - }; - }); - - // Build filter description - const activeFilterDescriptions = []; - if (activeSearchKeyword) activeFilterDescriptions.push(`Search: "${activeSearchKeyword}"`); - if (activeCategoryId !== 'all') - activeFilterDescriptions.push(`Category: ${activeCategoryId}`); - - Object.entries(activeFilters).forEach(([filterType, filterGroup]) => { - const activeFilterValues = Object.entries(filterGroup) - .filter(([, filter]) => filter.active) - .map(([, filter]) => filter.label || filter.value); - if (activeFilterValues.length > 0) { - activeFilterDescriptions.push(`${filterType}: [${activeFilterValues.join(', ')}]`); - } - }); - - const filterDescription = - activeFilterDescriptions.length > 0 ? activeFilterDescriptions.join(' + ') : 'No filters'; + return filterBySearchKeyword(filteredByCategoryItems, activeSearchKeyword, sortOrder); + }, [activeCategoryId, activeSearchKeyword, categorizedIds, items, sortOrder]); - // eslint-disable-next-line no-console - console.log( - `\n🎯 FINAL Catalog Results: ${filterDescription} (${filteredByAttributes.length} matches)`, - ); - // eslint-disable-next-line no-console - console.table(tableData); - } - } + const filteredItems: CatalogItem[] = useMemo( + () => filterByAttributes(filteredBySearchItems, activeFilters), + [activeFilters, filteredBySearchItems], + ); - // Always use filteredByAttributes since keywordCompare handles both cases: - // - With search terms: relevance scoring + Red Hat prioritization + filtering - // - Without search terms: Red Hat prioritization + alphabetical sorting (no filtering) - // The keywordCompare function is called by filterBySearchKeyword regardless of search term presence - return filteredByAttributes; - }, [ - activeCategoryId, - activeFilters, - activeSearchKeyword, - catalogTypes, - categorizedIds, - filterGroups, - items, - sortOrder, - ]); + const filterGroupCounts = useMemo( + () => getFilterGroupCounts(filteredBySearchItems, activeFilters, filterGroups), + [filteredBySearchItems, activeFilters, filterGroups], + ); + + const catalogTypeCounts = useMemo( + () => getCatalogTypeCounts(filteredBySearchItems, catalogTypes), + [filteredBySearchItems, catalogTypes], + ); const totalItems = filteredItems.length; diff --git a/frontend/packages/console-shared/src/components/formik-fields/InputField.tsx b/frontend/packages/console-shared/src/components/formik-fields/InputField.tsx index 3090af522a3..1297b49c671 100644 --- a/frontend/packages/console-shared/src/components/formik-fields/InputField.tsx +++ b/frontend/packages/console-shared/src/components/formik-fields/InputField.tsx @@ -11,14 +11,11 @@ const InputField = forwardRef( {(props) => (
- {props.validated && props.validated !== ValidatedOptions.default ? ( -