[Customer Portal][FE][Web] Introduce Paginated Select Load More, Feature Flag Navigation, and UI Improvements - #458
Conversation
Fetch portal metadata and gate the Usage & Metrics page on the usageMetricsEnabled feature flag. Adds useGetMetadata, useParams and useNavigate; shows skeleton UI while metadata loads; redirects to project dashboard or home if the feature is disabled; and prevents rendering the page when not enabled. Also updates imports accordingly.
Trim the classification product label and treat empty or placeholder labels (via isUnknownPlaceholderProductLabel) as absent before building extra product options. This prevents placeholder or whitespace-only labels from being added to the product options list.
Add optional nullable createdByFirstName and createdByLastName fields to the CaseComment interface so UIs can display the comment author's given and family names when available.
Refactor ProjectCardInfo unit tests to use actual @wso2/oxygen-ui components where needed and wrap rendered UI with ThemeProvider via a renderWithTheme helper. Replace previous manual mocks with an importActual-based mock that overrides Form.CardHeader and Tooltip, and remove the crude Box/Typography mocks. Update assertions (check rendered h6 class) and add a new test suite that simulates line-clamped overflow by spying on scrollHeight/clientHeight to verify the truncation tooltip shows the full title. Also restore mocks after each test and include a minor license whitespace fix.
Add afterEach to the vitest imports in ProjectCardInfo.test.tsx so tests can perform teardown/cleanup between cases (e.g., reset mocks or DOM). This prevents cross-test interference and ensures isolated test runs.
Import paginated dropdown helpers and a load-more row, and use a shared EMPTY_DROPDOWN_PLACEHOLDER. Add showNoDeploymentsHint and derive deployment/product MenuListProps via paginatedSelectMenuListProps instead of inline onScroll handlers. Update renderValue and placeholder MenuItem text to show the empty placeholder when no options exist, and insert SelectMenuLoadMoreRow entries to surface ongoing fetch-more state for both deployments and products. These changes unify empty state text and enable paginated/infinite-scroll behavior in the select menus.
Replace the Cell stroke in ActiveCasesChart.tsx from colors.common.white to "none" to remove the white outline around pie slices. This cleans up the chart appearance and avoids unintended borders between segments.
Change Cell stroke in CasesTrendChart.tsx from colors.common.white to "none" to eliminate white borders between pie slices and simplify rendering of the chart segments.
Introduce SelectMenuLoadMoreRow component (apps/customer-portal/webapp/src/components/common/select-menu-load-more-row/SelectMenuLoadMoreRow.tsx). Adds a disabled MenuItem footer row with a centered CircularProgress for paginated Select/TextField selects while the next page is loading. Exports a sentinel value SELECT_MENU_LOAD_MORE_ROW_VALUE (not intended for real form state) and a SelectMenuLoadMoreRowProps interface with a visible prop to conditionally render the row. Includes styling via sx to keep the spinner centered and the row non-interactive.
Replace the local ClampedTextWithTooltip implementation with a shared component import from @components/project-hub/project-card/ClampedTextWithTooltip. Cleaned up unused imports (Tooltip, Typography, useRef/useState/useEffect) and adjusted React type import. Also include a minor whitespace tweak in the license header.
Add a Vitest test file for ClampedTextWithTooltip component under project-card tests. The tests mock @wso2/oxygen-ui Tooltip for deterministic output, wrap the component with ThemeProvider, and assert two behaviours: no tooltip is rendered when content isn't truncated, and the tooltip shows the full text when the element is truncated (simulated by mocking scrollHeight/clientHeight).
Rename and adjust the test to match updated UI text and props: the test now expects the short "Not available" message when productOptionList is empty, and passes a deployment value to renderSection. This aligns the assertion with the component's current behavior.
Replace hard-coded "No ... available" messages with EMPTY_DROPDOWN_PLACEHOLDER for product, version, and start/end level dropdowns in AllUpdatesTab. Import the constant from @constants/dropdownConstants to centralize placeholder text and improve consistency (easier to update/localize).
Add a new unit test file for SelectMenuLoadMoreRow using Vitest and React Testing Library. Tests verify the component renders nothing when visible is false and displays a progress indicator when visible is true. Tests are wrapped with the app ThemeProvider to ensure consistent theming.
Move the spread of request.pagination before offset and limit in the payload construction so the explicit offset/limit parameters take precedence over any values provided in request.pagination. This prevents request-provided pagination from accidentally overwriting the function's pagination arguments.
Introduce a new constants file for customer-portal dropdowns. Adds PAGINATED_SELECT_MENU_MAX_HEIGHT_PX (320) and EMPTY_DROPDOWN_PLACEHOLDER ("Not available"), plus paginatedSelectMenuListProps(onScroll?) which returns MUI MenuList props with a fixed maxHeight and optional onScroll handler for fetching next pages in paginated selects.
Introduce commentAuthorDisplayName to build a display name from first/last names (trimmed and joined) and fall back to comment.createdBy or "Unknown". Replace direct uses of comment.createdBy with the helper for display name and initials, and update useMemo dependencies to depend on the comment object so values re-compute when the comment changes. Improves name handling and correctness of avatar initials.
Add support for infinite-scroll/paginated select menus in FilterPopover by using paginatedSelectMenuListProps to attach MenuListProps when a field provides onLoadMore. Import SelectMenuLoadMoreRow and render a loading row conditionally when more items are available and fetching. Use EMPTY_DROPDOWN_PLACEHOLDER when a field has no options to show a consistent empty placeholder. Overall improves UX for large dropdowns and centralizes pagination behavior.
Refactor tests for AddProductModal to be more robust and match paginated API shapes. Added mockProductsPage and mockVersionsPage objects and updated useGetProducts to return a page shape (including totalRecords/offset/limit) and isFetching flags. Replaced useSearchProductVersions mockReturnValue with an implementation that returns versions only when a productId is provided, and added isFetching. Updated UI interactions to use async findByRole(option) queries with hidden:true and added waitFor checks to ensure the version field is enabled before interacting. Marked affected tests async and removed an obsolete expectation for "Initial Update Information".
Improve the deployments filter UX in AllCasesFilters.tsx by reusing paginatedSelectMenuListProps for the MenuList scroll handling instead of an inline onScroll. Import and use SelectMenuLoadMoreRow to show a loading row when more deployments are being fetched, and show EMPTY_DROPDOWN_PLACEHOLDER when there are no options. These changes centralize pagination behavior and provide clearer empty/loading states for the deployment select.
Mock useGetMetadata to expose a controllable usageMetricsEnabled flag and add a test that verifies the "Usage & Metrics" nav item is hidden when the flag is false. Introduces mockUsageMetricsEnabled variable and resets it in beforeEach, and adds a test that sets the project label to a supported type to ensure the item would normally appear, then asserts it's not rendered when the feature flag is disabled.
Import portal metadata and use its usageMetricsEnabled flag to conditionally remove the "usage-metrics" item from APP_SHELL_NAV_ITEMS. Refactor navItems memo to start from a mutable items list, filter out "usage-metrics" when the feature flag is false and remove "operations" when the project type is unresolved or the user lacks operations permission. Also add usageMetricsEnabled to the useMemo dependency array.
Refactor CasesFilters select inputs to support paginated/infinite loading and handle empty option lists. Uses paginatedSelectMenuListProps for MenuProps when field.onLoadMore is present (with scroll threshold logic), adds SelectMenuLoadMoreRow to render loading/more indicator, and displays EMPTY_DROPDOWN_PLACEHOLDER when there are no options. Small cleanup: extracted hasNoOptions variable and conditional MenuProps instead of inline scroll handler.
Replace colors.common.white stroke with "none" for Pie chart Cell in OutstandingIncidentsChart to remove the white border around segments and ensure cleaner visuals.
Introduce a new ClampedTextWithTooltip React component used in project cards. It renders a line-clamped Typography element and detects truncation via a ref (comparing scrollHeight and clientHeight); when truncated it wraps the content in a Tooltip. Props: text, lineClamp, optional variant (h6|body2) and sx. Uses @wso2/oxygen-ui Tooltip and Typography.
📝 WalkthroughWalkthroughThis PR implements paginated select dropdown loading across multiple components, adds feature-flag-based gating for the usage metrics page, extracts a reusable ClampedTextWithTooltip component, removes white stroke styling from pie charts, and sanitizes product label handling in the case-creation flow. Changes
Sequence DiagramssequenceDiagram
participant User
participant SelectMenu as Select Menu
participant MenuList as Menu List (paginatedSelectMenuListProps)
participant API as API / useQuery
participant State as Component State
User->>SelectMenu: Opens dropdown
SelectMenu->>MenuList: Renders with onScroll handler
User->>MenuList: Scrolls near bottom
MenuList->>State: Triggers onLoadMore (threshold=24px)
State->>API: Fetch next page (offset += limit)
API-->>State: Returns paginated items
State->>State: Append items to options
SelectMenu->>SelectMenu: Render SelectMenuLoadMoreRow when isFetchingMore
SelectMenu->>User: Display new items + loading row
User->>SelectMenu: Scroll further or select option
sequenceDiagram
participant Router as React Router
participant UsageMetricsPage
participant useGetMetadata as useGetMetadata Hook
participant Portal as Portal Metadata API
Router->>UsageMetricsPage: Navigate to /usage-metrics
UsageMetricsPage->>useGetMetadata: Fetch portal metadata
useGetMetadata->>Portal: GET portal metadata
Portal-->>useGetMetadata: Return metadata + featureFlags
useGetMetadata-->>UsageMetricsPage: isLoading=false, usageMetricsEnabled
alt usageMetricsEnabled === false
UsageMetricsPage->>Router: navigate(dashboard, {replace: true})
UsageMetricsPage-->>Router: Return null
else usageMetricsEnabled === true
UsageMetricsPage-->>Router: Render UsageMetrics content
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
apps/customer-portal/webapp/src/components/common/side-nav-bar/__tests__/SideBar.test.tsx (1)
211-217: Consider adding a complementary positive test case.The test correctly verifies the nav item is hidden when the flag is
false. For completeness, consider adding a test that explicitly verifies "Usage & Metrics" is rendered whenmockUsageMetricsEnabled = true(with the same project type), ensuring the filtering logic works in both directions.📝 Suggested additional test case
it("should show Usage & Metrics when featureFlags.usageMetricsEnabled is true", () => { mockUsageMetricsEnabled = true; mockProjectTypeLabel = PROJECT_TYPE_LABELS.MANAGED_CLOUD_SUBSCRIPTION; render(<SideBar collapsed={false} />); expect(screen.getByText("Usage & Metrics")).toBeInTheDocument(); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/common/side-nav-bar/__tests__/SideBar.test.tsx` around lines 211 - 217, Add a complementary positive test that verifies "Usage & Metrics" is rendered when the feature flag is enabled: set mockUsageMetricsEnabled = true, set mockProjectTypeLabel = PROJECT_TYPE_LABELS.MANAGED_CLOUD_SUBSCRIPTION, render the SideBar component (render(<SideBar collapsed={false} />)) and assert screen.getByText("Usage & Metrics") is in the document; this mirrors the existing negative test and ensures the filtering logic around mockUsageMetricsEnabled and mockProjectTypeLabel behaves correctly.apps/customer-portal/webapp/src/components/project-details/deployments/AddProductModal.tsx (1)
115-126: Potential duplicate items on react-query refetch.The accumulation logic appends items when
offset > 0without deduplication. If react-query refetches an existing page (e.g., window refocus, staleTime expiry), the same items could be appended again.Given the 5-minute
staleTimeon these hooks and typical modal usage duration, this is unlikely to occur in practice. If this becomes an issue, consider either:
- Using a
SetorMapfor deduplication byid- Comparing the incoming page offset with a ref to skip duplicate appends
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/project-details/deployments/AddProductModal.tsx` around lines 115 - 126, The useEffect that accumulates paginated rows (useEffect dependent on productsPage) can append duplicate items when a previously fetched page is refetched; update the handler for productsPage in AddProductModal.tsx to deduplicate before calling setProducts by either filtering incoming pageItems against existing products (compare by product id) or track appended offsets in a ref (e.g., fetchedOffsetsRef) and skip appending when productsPage.offset is already recorded; ensure you still replace the list when offset === 0 and update the ref or existing list accordingly so setProducts never receives duplicates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/customer-portal/webapp/src/components/common/filter-panel/FilterPopover.tsx`:
- Around line 35-39: In FilterPopover, prevent the sentinel load-more value from
being written into tempFilters by guarding any place that writes raw selection
values (e.g., where tempFilters is updated in selection handlers) to ignore
values equal to SELECT_MENU_LOAD_MORE_ROW_VALUE; update the logic that sets
tempFilters (and any related setTempFilters or onChange code paths tied to
SelectMenuLoadMoreRow/paginatedSelectMenuListProps) to filter out or
early-return when the selected value === SELECT_MENU_LOAD_MORE_ROW_VALUE so only
real choices are stored.
In
`@apps/customer-portal/webapp/src/components/dashboard/cases-table/CasesFilters.tsx`:
- Around line 28-32: Guard against persisting the in-menu load-more sentinel by
checking for SELECT_MENU_LOAD_MORE_ROW_VALUE before calling onFilterChange;
update the handlers that call onFilterChange (the functions handling the
assignee/other dropdown changes using SELECT_MENU_LOAD_MORE_ROW_VALUE and
SelectMenuLoadMoreRow) so they return early or ignore the selection when value
=== SELECT_MENU_LOAD_MORE_ROW_VALUE and only call onFilterChange for real filter
values; apply this same sentinel-check logic to both occurrences referenced (the
change handlers around the 56-60 region and the 118-125 region).
In
`@apps/customer-portal/webapp/src/components/project-hub/project-card/ClampedTextWithTooltip.tsx`:
- Around line 43-47: The effect in ClampedTextWithTooltip that computes
truncation (useEffect) only depends on text, so when lineClamp changes the
truncation state can be stale; update the dependency array to include lineClamp
(in addition to text) and ensure the effect reads from ref.current and calls
setIsTruncated(el.scrollHeight > el.clientHeight) so the truncation/tooltip
updates whenever lineClamp or text changes.
In
`@apps/customer-portal/webapp/src/components/support/all-cases/AllCasesFilters.tsx`:
- Around line 32-36: The handler onFilterChange is currently forwarding the
"load more" sentinel value from the select menu into the filters; update
onFilterChange (and the other similar handlers at the other occurrences
mentioned) to early-return or ignore when the selected value equals the
load-more sentinel (use paginatedSelectMenuListProps.loadMoreSentinel) before
calling the code that updates filters/state so the sentinel never becomes a
filter value.
---
Nitpick comments:
In
`@apps/customer-portal/webapp/src/components/common/side-nav-bar/__tests__/SideBar.test.tsx`:
- Around line 211-217: Add a complementary positive test that verifies "Usage &
Metrics" is rendered when the feature flag is enabled: set
mockUsageMetricsEnabled = true, set mockProjectTypeLabel =
PROJECT_TYPE_LABELS.MANAGED_CLOUD_SUBSCRIPTION, render the SideBar component
(render(<SideBar collapsed={false} />)) and assert screen.getByText("Usage &
Metrics") is in the document; this mirrors the existing negative test and
ensures the filtering logic around mockUsageMetricsEnabled and
mockProjectTypeLabel behaves correctly.
In
`@apps/customer-portal/webapp/src/components/project-details/deployments/AddProductModal.tsx`:
- Around line 115-126: The useEffect that accumulates paginated rows (useEffect
dependent on productsPage) can append duplicate items when a previously fetched
page is refetched; update the handler for productsPage in AddProductModal.tsx to
deduplicate before calling setProducts by either filtering incoming pageItems
against existing products (compare by product id) or track appended offsets in a
ref (e.g., fetchedOffsetsRef) and skip appending when productsPage.offset is
already recorded; ensure you still replace the list when offset === 0 and update
the ref or existing list accordingly so setProducts never receives duplicates.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a44aed98-f621-448c-8928-ca9c5d55d0e3
📒 Files selected for processing (25)
apps/customer-portal/webapp/src/api/usePostProjectDeploymentsSearch.tsapps/customer-portal/webapp/src/components/common/filter-panel/FilterPopover.tsxapps/customer-portal/webapp/src/components/common/select-menu-load-more-row/SelectMenuLoadMoreRow.tsxapps/customer-portal/webapp/src/components/common/select-menu-load-more-row/__tests__/SelectMenuLoadMoreRow.test.tsxapps/customer-portal/webapp/src/components/common/side-nav-bar/SideBar.tsxapps/customer-portal/webapp/src/components/common/side-nav-bar/__tests__/SideBar.test.tsxapps/customer-portal/webapp/src/components/dashboard/cases-table/CasesFilters.tsxapps/customer-portal/webapp/src/components/dashboard/charts/ActiveCasesChart.tsxapps/customer-portal/webapp/src/components/dashboard/charts/CasesTrendChart.tsxapps/customer-portal/webapp/src/components/dashboard/charts/OutstandingIncidentsChart.tsxapps/customer-portal/webapp/src/components/project-details/deployments/AddProductModal.tsxapps/customer-portal/webapp/src/components/project-details/deployments/__tests__/AddProductModal.test.tsxapps/customer-portal/webapp/src/components/project-hub/project-card/ClampedTextWithTooltip.tsxapps/customer-portal/webapp/src/components/project-hub/project-card/ProjectCardInfo.tsxapps/customer-portal/webapp/src/components/project-hub/project-card/__tests__/ClampedTextWithTooltip.test.tsxapps/customer-portal/webapp/src/components/project-hub/project-card/__tests__/ProjectCardInfo.test.tsxapps/customer-portal/webapp/src/components/support/all-cases/AllCasesFilters.tsxapps/customer-portal/webapp/src/components/support/case-creation-layout/form-sections/basic-information-section/BasicInformationSection.tsxapps/customer-portal/webapp/src/components/support/case-creation-layout/form-sections/basic-information-section/__tests__/BasicInformationSection.test.tsxapps/customer-portal/webapp/src/components/support/case-details/activity-tab/CommentBubble.tsxapps/customer-portal/webapp/src/components/updates/all-updates/AllUpdatesTab.tsxapps/customer-portal/webapp/src/constants/dropdownConstants.tsapps/customer-portal/webapp/src/models/responses.tsapps/customer-portal/webapp/src/pages/CreateCasePage.tsxapps/customer-portal/webapp/src/pages/UsageMetricsPage.tsx
Decription
This pull request introduces improvements to the customer portal's UI, focusing on enhancing the user experience for paginated dropdowns ("Select" menus) and making navigation items dynamically responsive to feature flags. The most significant changes are the addition of a reusable "load more" row for select menus, consistent handling of empty dropdown states, and the conditional display of navigation items based on backend feature flags.
Paginated Select Menu Enhancements:
SelectMenuLoadMoreRowcomponent that displays a loading spinner at the bottom of dropdown menus when more options are being fetched, and integrated it into all relevant select menus (FilterPopover, CasesFilters, AddProductModal). This improves the UX for infinite/paginated dropdowns. [1] [2] [3] [4] [5] [6] [7]Feature Flag–Driven Navigation:
SideBarcomponent to conditionally show or hide the "Usage & Metrics" navigation item based on theusageMetricsEnabledfeature flag from portal metadata, ensuring that navigation accurately reflects enabled features. [1] [2] [3]Other UI Improvements:
ActiveCasesChart,CasesTrendChart,OutstandingIncidentsChart) to remove the white stroke from pie chart cells, resulting in a cleaner appearance. [1] [2] [3]Testing and Code Quality:
SelectMenuLoadMoreRowcomponent to verify correct rendering behavior.Bug Fixes and Refactoring:
These changes collectively improve the portal's usability, maintainability, and adaptability to feature toggles.