From de4f096d78d9d24426e60183ce9614e0e8609792 Mon Sep 17 00:00:00 2001 From: ArtemHoruzhenko Date: Mon, 14 Sep 2026 09:16:40 +0300 Subject: [PATCH 1/2] fix(rdi): give pipeline-management-v2 its own standalone page PipelineManagementV2Page was nested under RdiInstancePage, so it inherited the v1 shell's Pipeline/Analytics tabs (AppNavigation) and pipeline status bar (RdiPipelineHeader) - chrome that reads v1's own pipeline REST API and has no relationship to the v2 experience. Move Pages.rdiPipelineManagementV2 to a top-level route (placed before Pages.rdiPipeline so it isn't swallowed by that route's non-exact prefix match), and have the page render its own RdiInstanceHeader breadcrumb directly instead of relying on the v1 shell for it. Extract the shared instance-loading bootstrap (fetch instance list, fetch connected instance, reset stale context) into useConnectRdiInstance, used by both RdiInstancePage and the new standalone page, so instance loading behaves identically either way. Co-Authored-By: Claude Sonnet 5 --- .../main-router/constants/defaultRoutes.ts | 19 ++++-- .../pages/rdi/hooks/useConnectRdiInstance.ts | 60 +++++++++++++++++++ .../src/pages/rdi/instance/InstancePage.tsx | 52 ++-------------- .../PipelineManagementV2Page.spec.tsx | 12 ++++ .../PipelineManagementV2Page.tsx | 56 ++++++++++++----- .../pipeline-management-v2/styles.module.scss | 4 ++ 6 files changed, 135 insertions(+), 68 deletions(-) create mode 100644 redisinsight/ui/src/pages/rdi/hooks/useConnectRdiInstance.ts create mode 100644 redisinsight/ui/src/pages/rdi/pipeline-management-v2/styles.module.scss diff --git a/redisinsight/ui/src/components/main-router/constants/defaultRoutes.ts b/redisinsight/ui/src/components/main-router/constants/defaultRoutes.ts index 58f3a42a4c..67e948dbb1 100644 --- a/redisinsight/ui/src/components/main-router/constants/defaultRoutes.ts +++ b/redisinsight/ui/src/components/main-router/constants/defaultRoutes.ts @@ -114,12 +114,6 @@ const RDI_INSTANCE_ROUTES: IRoute[] = getRouteIncludedByEnv([ component: LAZY_LOAD ? LazyPipelineManagementPage : PipelineManagementPage, routes: RDI_PIPELINE_MANAGEMENT_ROUTES, }, - { - path: Pages.rdiPipelineManagementV2(':rdiInstanceId'), - component: LAZY_LOAD - ? LazyPipelineManagementV2Page - : PipelineManagementV2Page, - }, ]) const ROUTES: IRoute[] = [ @@ -190,6 +184,19 @@ const ROUTES: IRoute[] = [ exact: true, featureFlag: FeatureFlags.rdi, }, + // Standalone page, not nested under RdiInstancePage: @rdi-ui/pipeline + // owns its own navigation, so this route must not inherit the v1 shell's + // Pipeline/Analytics tabs or pipeline status bar. Must come before + // Pages.rdiPipeline below - that route has no `exact` (its own sub-routes + // rely on prefix matching), so it would otherwise swallow this URL first. + { + path: Pages.rdiPipelineManagementV2(':rdiInstanceId'), + component: LAZY_LOAD + ? LazyPipelineManagementV2Page + : PipelineManagementV2Page, + exact: true, + featureFlag: FeatureFlags.rdi, + }, { path: Pages.rdiPipeline(':rdiInstanceId'), component: LAZY_LOAD ? LazyRdiInstancePage : RdiInstancePage, diff --git a/redisinsight/ui/src/pages/rdi/hooks/useConnectRdiInstance.ts b/redisinsight/ui/src/pages/rdi/hooks/useConnectRdiInstance.ts new file mode 100644 index 0000000000..ff763ad1e8 --- /dev/null +++ b/redisinsight/ui/src/pages/rdi/hooks/useConnectRdiInstance.ts @@ -0,0 +1,60 @@ +import { useEffect } from 'react' +import { useAppDispatch, useAppSelector } from 'uiSrc/slices/hooks' +import { + appContextSelector, + resetDatabaseContext, + resetRdiContext, + setAppContextConnectedRdiInstanceId, + setLastPageContext, +} from 'uiSrc/slices/app/context' +import { + connectedInstanceSelector, + fetchConnectedInstanceAction, + fetchInstancesAction as fetchRdiInstancesAction, + instancesSelector as rdiInstancesSelector, +} from 'uiSrc/slices/rdi/instances' +import { + fetchInstancesAction, + instancesSelector as dbInstancesSelector, + resetConnectedInstance as resetConnectedDatabaseInstance, +} from 'uiSrc/slices/instances/instances' + +/** + * Loads the RDI instance list, the connected instance itself, and clears any + * leftover database-page context, given an instance id. + * + * Shared by every page that renders inside a given RDI instance - both the v1 + * shell (RdiInstancePage) and the standalone v2 page - so instance loading + * behaves identically regardless of which pipeline UI ends up rendering. + */ +export const useConnectRdiInstance = (rdiInstanceId: string) => { + const dispatch = useAppDispatch() + const { contextRdiInstanceId } = useAppSelector(appContextSelector) + const { data: rdiInstances } = useAppSelector(rdiInstancesSelector) + const { data: dbInstances } = useAppSelector(dbInstancesSelector) + const connectedInstance = useAppSelector(connectedInstanceSelector) + + useEffect(() => { + if (!dbInstances?.length) { + dispatch(fetchInstancesAction()) + } + if (!rdiInstances?.length) { + dispatch(fetchRdiInstancesAction()) + } + }, []) + + useEffect(() => { + if (!contextRdiInstanceId || contextRdiInstanceId !== rdiInstanceId) { + dispatch(resetRdiContext()) + dispatch(setLastPageContext('')) + dispatch(fetchConnectedInstanceAction(rdiInstanceId)) + } + dispatch(setAppContextConnectedRdiInstanceId(rdiInstanceId)) + + // clear database context + dispatch(resetConnectedDatabaseInstance()) + dispatch(resetDatabaseContext()) + }, [rdiInstanceId]) + + return { connectedInstance, contextRdiInstanceId } +} diff --git a/redisinsight/ui/src/pages/rdi/instance/InstancePage.tsx b/redisinsight/ui/src/pages/rdi/instance/InstancePage.tsx index 543d8eee9d..5a9e3d323e 100644 --- a/redisinsight/ui/src/pages/rdi/instance/InstancePage.tsx +++ b/redisinsight/ui/src/pages/rdi/instance/InstancePage.tsx @@ -1,25 +1,8 @@ import React, { useEffect, useState } from 'react' -import { useAppDispatch, useAppSelector } from 'uiSrc/slices/hooks' +import { useAppSelector } from 'uiSrc/slices/hooks' import { useHistory, useLocation, useParams } from 'react-router-dom' -import { - appContextSelector, - resetDatabaseContext, - resetRdiContext, - setAppContextConnectedRdiInstanceId, - setLastPageContext, -} from 'uiSrc/slices/app/context' +import { appContextSelector } from 'uiSrc/slices/app/context' import { IRoute, PageNames, Pages } from 'uiSrc/constants' -import { - connectedInstanceSelector, - fetchConnectedInstanceAction, - fetchInstancesAction as fetchRdiInstancesAction, - instancesSelector as rdiInstancesSelector, -} from 'uiSrc/slices/rdi/instances' -import { - fetchInstancesAction, - instancesSelector as dbInstancesSelector, - resetConnectedInstance as resetConnectedDatabaseInstance, -} from 'uiSrc/slices/instances/instances' import { isDevRdiUiEnabledSelector } from 'uiSrc/slices/app/features' import { Nullable } from 'uiSrc/utils' import { shouldUseRdiUiPipeline } from 'uiSrc/utils/rdi' @@ -28,6 +11,7 @@ import { RdiInstancePageTemplate } from 'uiSrc/templates' import { AppNavigation, RdiInstanceHeader } from 'uiSrc/components' import { Col, FlexItem } from 'uiSrc/components/base/layout/flex' import { useNavigation } from 'uiSrc/components/navigation-menu/hooks/useNavigation' +import { useConnectRdiInstance } from '../hooks/useConnectRdiInstance' import InstancePageRouter from './InstancePageRouter' import { RdiPipelineHeader } from './components' import styles from './styles.module.scss' @@ -37,43 +21,19 @@ export interface Props { } const RdiInstancePage = ({ routes = [] }: Props) => { - const dispatch = useAppDispatch() const history = useHistory() const location = useLocation<{ skipLastPageRestore?: boolean }>() const { pathname } = location const { privateRdiRoutes } = useNavigation() const { rdiInstanceId } = useParams<{ rdiInstanceId: string }>() - const { lastPage, contextRdiInstanceId } = useAppSelector(appContextSelector) - const { data: rdiInstances } = useAppSelector(rdiInstancesSelector) - const { data: dbInstances } = useAppSelector(dbInstancesSelector) - const connectedInstance = useAppSelector(connectedInstanceSelector) + const { lastPage } = useAppSelector(appContextSelector) + const { connectedInstance, contextRdiInstanceId } = + useConnectRdiInstance(rdiInstanceId) const isDevRdiUiEnabled = useAppSelector(isDevRdiUiEnabledSelector) const [actions, setActions] = useState>(null) - useEffect(() => { - if (!dbInstances?.length) { - dispatch(fetchInstancesAction()) - } - if (!rdiInstances?.length) { - dispatch(fetchRdiInstancesAction()) - } - }, []) - - useEffect(() => { - if (!contextRdiInstanceId || contextRdiInstanceId !== rdiInstanceId) { - dispatch(resetRdiContext()) - dispatch(setLastPageContext('')) - dispatch(fetchConnectedInstanceAction(rdiInstanceId)) - } - dispatch(setAppContextConnectedRdiInstanceId(rdiInstanceId)) - - // clear database context - dispatch(resetConnectedDatabaseInstance()) - dispatch(resetDatabaseContext()) - }, [rdiInstanceId]) - useEffect(() => { // redirect only if there is no exact path if (pathname === Pages.rdiPipeline(rdiInstanceId)) { diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.spec.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.spec.tsx index 334065d75b..7168993dc0 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.spec.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.spec.tsx @@ -29,6 +29,18 @@ describe('PipelineManagementV2Page', () => { ).toBeInTheDocument() }) + it('should render the instance breadcrumb, but not the v1 shell (tabs/status bar)', () => { + renderPage() + + // Kept for navigation consistency with the rest of RedisInsight. + expect(screen.getByTestId('breadcrumbs-container')).toBeInTheDocument() + // The v1-only chrome (AppNavigation's Pipeline/Analytics tabs and + // RdiPipelineHeader's status bar) must not be pulled in - this page is a + // standalone route, not nested inside RdiInstancePage. + expect(screen.queryByTestId('pipeline-management-page-btn')).toBeNull() + expect(screen.queryByTestId('pipeline-status-page-btn')).toBeNull() + }) + it('should record itself as the last visited rdi section on unmount', () => { const { unmount } = renderPage() diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.tsx index 622a56c331..02f5d681ec 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.tsx @@ -1,23 +1,40 @@ import React, { useEffect } from 'react' +import { useParams } from 'react-router-dom' -import { connectedInstanceSelector } from 'uiSrc/slices/rdi/instances' -import { useAppSelector, useAppDispatch } from 'uiSrc/slices/hooks' +import { useAppDispatch } from 'uiSrc/slices/hooks' import { formatLongName, setTitle } from 'uiSrc/utils' import { useTranslation } from 'uiSrc/i18n' import { Text } from 'uiSrc/components/base/text' -import { Row } from 'uiSrc/components/base/layout/flex' +import { Col, FlexItem, Row } from 'uiSrc/components/base/layout/flex' import { PageNames } from 'uiSrc/constants' import { setLastPageContext } from 'uiSrc/slices/app/context' +import { RdiInstanceHeader } from 'uiSrc/components' +import { RdiInstancePageTemplate } from 'uiSrc/templates' +import { useConnectRdiInstance } from '../hooks/useConnectRdiInstance' import * as S from './PipelineManagementV2Page.styles' +import styles from './styles.module.scss' +/** + * Standalone top-level page for the @rdi-ui/pipeline-backed management UI. + * + * A sibling of RdiInstancePage (the v1 shell), not a route nested inside it: + * the package owns its own internal navigation, so this page skips the + * v1-only chrome that would otherwise be sandwiched around it - AppNavigation's + * Pipeline/Analytics tabs and RdiPipelineHeader's pipeline status bar, both + * of which read v1's own pipeline REST API and have no relationship to the + * package's UI. + * + * The RDI instance breadcrumb (RdiInstanceHeader) is kept for navigation + * consistency with the rest of RedisInsight, and instance loading goes + * through the same hook the v1 shell uses, so both behave identically. + */ const PipelineManagementV2Page = () => { const { t } = useTranslation() const dispatch = useAppDispatch() - const { name: connectedRdiInstanceName } = useAppSelector( - connectedInstanceSelector, - ) + const { rdiInstanceId } = useParams<{ rdiInstanceId: string }>() + const { connectedInstance } = useConnectRdiInstance(rdiInstanceId) - const rdiInstanceName = formatLongName(connectedRdiInstanceName, 33, 0, '...') + const rdiInstanceName = formatLongName(connectedInstance.name, 33, 0, '...') setTitle(t('rdi.pipeline.pageTitle', { name: rdiInstanceName })) useEffect( @@ -28,15 +45,22 @@ const PipelineManagementV2Page = () => { ) return ( - - - The new pipeline management experience is coming soon. - - + + + + + + + + The new pipeline management experience is coming soon. + + + + ) } diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management-v2/styles.module.scss b/redisinsight/ui/src/pages/rdi/pipeline-management-v2/styles.module.scss new file mode 100644 index 0000000000..83a1251260 --- /dev/null +++ b/redisinsight/ui/src/pages/rdi/pipeline-management-v2/styles.module.scss @@ -0,0 +1,4 @@ +.page { + height: 100%; + padding-bottom: 16px; +} From 5a408a022eff8574ac3b6b89269720395a39723f Mon Sep 17 00:00:00 2001 From: ArtemHoruzhenko Date: Mon, 14 Sep 2026 10:47:53 +0300 Subject: [PATCH 2/2] fix(rdi): address PR review findings on pipeline-management-v2 layout - Don't reuse RdiInstancePageTemplate for the standalone v2 page: it hardcodes a -210px height offset sized for the v1 shell's AppNavigation tabs + RdiPipelineHeader status bar, neither of which this page renders. Use ExplorePanelTemplate directly instead, which lets the page fill its actual available height. - Move the page wrapper's padding from a hardcoded 16px in an SCSS module to a styled-components PageContainer using theme spacing (space200), matching repo convention. - Drop the explanatory comments added for this change - not required here. Co-Authored-By: Claude Sonnet 5 --- .../main-router/constants/defaultRoutes.ts | 5 --- .../pages/rdi/hooks/useConnectRdiInstance.ts | 8 ---- .../PipelineManagementV2Page.spec.tsx | 4 -- .../PipelineManagementV2Page.styles.ts | 7 ++- .../PipelineManagementV2Page.tsx | 45 +++++++------------ .../pipeline-management-v2/styles.module.scss | 4 -- 6 files changed, 21 insertions(+), 52 deletions(-) delete mode 100644 redisinsight/ui/src/pages/rdi/pipeline-management-v2/styles.module.scss diff --git a/redisinsight/ui/src/components/main-router/constants/defaultRoutes.ts b/redisinsight/ui/src/components/main-router/constants/defaultRoutes.ts index 67e948dbb1..e149a842d6 100644 --- a/redisinsight/ui/src/components/main-router/constants/defaultRoutes.ts +++ b/redisinsight/ui/src/components/main-router/constants/defaultRoutes.ts @@ -184,11 +184,6 @@ const ROUTES: IRoute[] = [ exact: true, featureFlag: FeatureFlags.rdi, }, - // Standalone page, not nested under RdiInstancePage: @rdi-ui/pipeline - // owns its own navigation, so this route must not inherit the v1 shell's - // Pipeline/Analytics tabs or pipeline status bar. Must come before - // Pages.rdiPipeline below - that route has no `exact` (its own sub-routes - // rely on prefix matching), so it would otherwise swallow this URL first. { path: Pages.rdiPipelineManagementV2(':rdiInstanceId'), component: LAZY_LOAD diff --git a/redisinsight/ui/src/pages/rdi/hooks/useConnectRdiInstance.ts b/redisinsight/ui/src/pages/rdi/hooks/useConnectRdiInstance.ts index ff763ad1e8..c9848c9dd9 100644 --- a/redisinsight/ui/src/pages/rdi/hooks/useConnectRdiInstance.ts +++ b/redisinsight/ui/src/pages/rdi/hooks/useConnectRdiInstance.ts @@ -19,14 +19,6 @@ import { resetConnectedInstance as resetConnectedDatabaseInstance, } from 'uiSrc/slices/instances/instances' -/** - * Loads the RDI instance list, the connected instance itself, and clears any - * leftover database-page context, given an instance id. - * - * Shared by every page that renders inside a given RDI instance - both the v1 - * shell (RdiInstancePage) and the standalone v2 page - so instance loading - * behaves identically regardless of which pipeline UI ends up rendering. - */ export const useConnectRdiInstance = (rdiInstanceId: string) => { const dispatch = useAppDispatch() const { contextRdiInstanceId } = useAppSelector(appContextSelector) diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.spec.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.spec.tsx index 7168993dc0..aed3885186 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.spec.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.spec.tsx @@ -32,11 +32,7 @@ describe('PipelineManagementV2Page', () => { it('should render the instance breadcrumb, but not the v1 shell (tabs/status bar)', () => { renderPage() - // Kept for navigation consistency with the rest of RedisInsight. expect(screen.getByTestId('breadcrumbs-container')).toBeInTheDocument() - // The v1-only chrome (AppNavigation's Pipeline/Analytics tabs and - // RdiPipelineHeader's status bar) must not be pulled in - this page is a - // standalone route, not nested inside RdiInstancePage. expect(screen.queryByTestId('pipeline-management-page-btn')).toBeNull() expect(screen.queryByTestId('pipeline-status-page-btn')).toBeNull() }) diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.styles.ts b/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.styles.ts index 771727d392..74af090ddd 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.styles.ts +++ b/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.styles.ts @@ -1,7 +1,12 @@ import styled from 'styled-components' -import { FlexItem } from 'uiSrc/components/base/layout/flex' +import { Col, FlexItem } from 'uiSrc/components/base/layout/flex' import { Theme } from 'uiSrc/components/base/theme/types' +export const PageContainer = styled(Col)` + height: 100%; + padding-bottom: ${({ theme }: { theme: Theme }) => theme.core.space.space200}; +` + export const PlaceholderContainer = styled(FlexItem)` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space300}; ` diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.tsx index 02f5d681ec..82779d691f 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management-v2/PipelineManagementV2Page.tsx @@ -5,29 +5,14 @@ import { useAppDispatch } from 'uiSrc/slices/hooks' import { formatLongName, setTitle } from 'uiSrc/utils' import { useTranslation } from 'uiSrc/i18n' import { Text } from 'uiSrc/components/base/text' -import { Col, FlexItem, Row } from 'uiSrc/components/base/layout/flex' +import { FlexItem, Row } from 'uiSrc/components/base/layout/flex' import { PageNames } from 'uiSrc/constants' import { setLastPageContext } from 'uiSrc/slices/app/context' import { RdiInstanceHeader } from 'uiSrc/components' -import { RdiInstancePageTemplate } from 'uiSrc/templates' +import { ExplorePanelTemplate } from 'uiSrc/templates' import { useConnectRdiInstance } from '../hooks/useConnectRdiInstance' import * as S from './PipelineManagementV2Page.styles' -import styles from './styles.module.scss' -/** - * Standalone top-level page for the @rdi-ui/pipeline-backed management UI. - * - * A sibling of RdiInstancePage (the v1 shell), not a route nested inside it: - * the package owns its own internal navigation, so this page skips the - * v1-only chrome that would otherwise be sandwiched around it - AppNavigation's - * Pipeline/Analytics tabs and RdiPipelineHeader's pipeline status bar, both - * of which read v1's own pipeline REST API and have no relationship to the - * package's UI. - * - * The RDI instance breadcrumb (RdiInstanceHeader) is kept for navigation - * consistency with the rest of RedisInsight, and instance loading goes - * through the same hook the v1 shell uses, so both behave identically. - */ const PipelineManagementV2Page = () => { const { t } = useTranslation() const dispatch = useAppDispatch() @@ -45,22 +30,22 @@ const PipelineManagementV2Page = () => { ) return ( - + - - - - The new pipeline management experience is coming soon. - - - - + + + + + + The new pipeline management experience is coming soon. + + + + + + ) } diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management-v2/styles.module.scss b/redisinsight/ui/src/pages/rdi/pipeline-management-v2/styles.module.scss deleted file mode 100644 index 83a1251260..0000000000 --- a/redisinsight/ui/src/pages/rdi/pipeline-management-v2/styles.module.scss +++ /dev/null @@ -1,4 +0,0 @@ -.page { - height: 100%; - padding-bottom: 16px; -}