From 70c98f05c5c9e0750174d7e55dfcc53f1525b651 Mon Sep 17 00:00:00 2001 From: "illia.prokopchuk" Date: Tue, 14 Apr 2026 16:49:26 +0300 Subject: [PATCH] Implement breadcrumbs for nuclio function (minimal approach) --- src/common/Breadcrumbs/Breadcrumbs.jsx | 48 ++++++++--- .../BreadcrumbsStep/BreadcrumbsStep.jsx | 86 +++++++++++++++---- 2 files changed, 104 insertions(+), 30 deletions(-) diff --git a/src/common/Breadcrumbs/Breadcrumbs.jsx b/src/common/Breadcrumbs/Breadcrumbs.jsx index 44e48643a8..05f65b078f 100644 --- a/src/common/Breadcrumbs/Breadcrumbs.jsx +++ b/src/common/Breadcrumbs/Breadcrumbs.jsx @@ -17,16 +17,17 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import React, { useMemo, useRef, useState } from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' import PropTypes from 'prop-types' import { useLocation, useParams } from 'react-router-dom' -import { useSelector } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import BreadcrumbsStep from './BreadcrumbsStep/BreadcrumbsStep' import { generateMlrunScreens, generateTabsList } from './breadcrumbs.util' import { MONITORING_APP_PAGE, PROJECTS_PAGE_PATH } from '../../constants' import { generateProjectsList } from '../../utils/projects' +import { fetchNuclioFunctions } from '../../reducers/nuclioReducer' import './breadcrumbs.scss' @@ -34,16 +35,27 @@ const Breadcrumbs = ({ onClick = () => {} }) => { const [searchValue, setSearchValue] = useState('') const [showScreensList, setShowScreensList] = useState(false) const [showProjectsList, setShowProjectsList] = useState(false) + const [showFunctionsList, setShowFunctionsList] = useState(false) const breadcrumbsRef = useRef() const params = useParams() const location = useLocation() + const dispatch = useDispatch() const projectStore = useSelector(state => state.projectStore) + const nuclioStore = useSelector(state => state.nuclioStore) const projectsList = useMemo(() => { return generateProjectsList(projectStore.projectsNames.data) }, [projectStore.projectsNames.data]) + const currentProjectFunctions = nuclioStore.currentProjectFunctions || [] + + useEffect(() => { + if (params.projectName && location.pathname.includes('real-time-functions')) { + dispatch(fetchNuclioFunctions({ project: params.projectName })) + } + }, [dispatch, params.projectName, location.pathname]) + const mlrunScreens = useMemo(() => { return generateMlrunScreens(params) }, [params]) @@ -53,30 +65,35 @@ const Breadcrumbs = ({ onClick = () => {} }) => { const urlParts = useMemo(() => { if (params.projectName) { - const [projects, projectName, screenName] = location.pathname.split('/').slice(1, 4) + const pathParts = location.pathname.split('/').slice(1) + const [projects, projectName, screenName, functionName, ...functionPath] = pathParts + const screen = mlrunScreens.find(screen => screen.id === screenName) - let tab = projectTabs.find(tab => - location.pathname - .split('/') - .slice(3) - .find(pathItem => pathItem === tab.id) - ) - - if (screen.id === MONITORING_APP_PAGE) { + let tab = projectTabs.find(tab => pathParts[2] === tab.id) + + if (screen?.id === MONITORING_APP_PAGE) { tab = {} } + const pathItems = [projects, projectName, screen?.id || screenName] + + if (screen?.id === 'real-time-functions' && functionName) { + pathItems.push(functionName) + } + return { - pathItems: [projects, projectName, screen?.label || screenName], + pathItems, screen, - tab + tab, + functionName, + functionPath } } else { const [page] = location.pathname.split('/').slice(3, 4) const screen = mlrunScreens.find(screen => screen.id === page) return { - pathItems: [PROJECTS_PAGE_PATH, screen?.label || page], + pathItems: [PROJECTS_PAGE_PATH, screen?.id || page], screen } } @@ -99,8 +116,11 @@ const Breadcrumbs = ({ onClick = () => {} }) => { setSearchValue={setSearchValue} setShowProjectsList={setShowProjectsList} setShowScreensList={setShowScreensList} + setShowFunctionsList={setShowFunctionsList} showProjectsList={showProjectsList} showScreensList={showScreensList} + showFunctionsList={showFunctionsList} + currentProjectFunctions={currentProjectFunctions} urlPart={urlPart} urlParts={urlParts} /> diff --git a/src/common/Breadcrumbs/BreadcrumbsStep/BreadcrumbsStep.jsx b/src/common/Breadcrumbs/BreadcrumbsStep/BreadcrumbsStep.jsx index 33969d13bb..7e0b43a721 100644 --- a/src/common/Breadcrumbs/BreadcrumbsStep/BreadcrumbsStep.jsx +++ b/src/common/Breadcrumbs/BreadcrumbsStep/BreadcrumbsStep.jsx @@ -43,8 +43,11 @@ const BreadcrumbsStep = React.forwardRef( setSearchValue, setShowProjectsList, setShowScreensList, + setShowFunctionsList, showProjectsList, showScreensList, + showFunctionsList, + currentProjectFunctions, urlPart, urlParts }, @@ -54,10 +57,13 @@ const BreadcrumbsStep = React.forwardRef( const separatorRef = useRef() const isParam = useMemo(() => Object.values(params ?? {}).includes(urlPart), [urlPart, params]) - const label = useMemo( - () => (isParam ? urlPart : urlPart.charAt(0).toUpperCase() + urlPart.slice(1)), - [urlPart, isParam] - ) + const label = useMemo(() => { + if (isParam || urlPart === urlParts.functionName) return urlPart + if (urlParts.screen && urlPart === urlParts.screen.id) { + return urlParts.screen.label || urlPart + } + return urlPart.charAt(0).toUpperCase() + urlPart.slice(1) + }, [urlPart, isParam, urlParts.screen, urlParts.functionName]) const to = useMemo( () => `/${urlParts.pathItems.slice(0, index + 1).join('/')}`, [index, urlParts.pathItems] @@ -70,15 +76,35 @@ const BreadcrumbsStep = React.forwardRef( const separatorClassNames = classnames( 'breadcrumbs__separator', ((urlParts.pathItems[index + 1] === urlParts.screen?.id && !isParam) || - urlParts.pathItems[index + 1] === params.projectName) && + urlParts.pathItems[index + 1] === params.projectName || + urlParts.pathItems[index + 1] === urlParts.functionName) && 'breadcrumbs__separator_tumbler' ) + const functionsListFormatted = useMemo(() => { + if (!Array.isArray(currentProjectFunctions)) return [] + + return currentProjectFunctions.map(func => { + const functionId = func?.metadata?.name || '' + const functionPath = urlParts?.functionPath?.length + ? `/${urlParts.functionPath.join('/')}` + : '' + + return { + id: functionId, + label: functionId, + linkTo: `${to}/${functionId}${functionPath}` + } + }) + }, [currentProjectFunctions, to, urlParts.functionPath]) + const handleSelectDropdownItem = separatorRef => { if (showProjectsList) setShowProjectsList(false) if (showScreensList) setShowScreensList(false) + if (showFunctionsList) setShowFunctionsList(false) + separatorRef.current.classList.remove('breadcrumbs__separator_active') } @@ -94,6 +120,8 @@ const BreadcrumbsStep = React.forwardRef( if (showScreensList) setShowScreensList(false) if (showProjectsList) setShowProjectsList(false) + + if (showFunctionsList) setShowFunctionsList(false) } setSearchValue('') @@ -103,8 +131,10 @@ const BreadcrumbsStep = React.forwardRef( setSearchValue, setShowProjectsList, setShowScreensList, + setShowFunctionsList, showProjectsList, - showScreensList + showScreensList, + showFunctionsList ] ) @@ -127,9 +157,12 @@ const BreadcrumbsStep = React.forwardRef( }, [handleCloseDropdown]) const handleSeparatorClick = (nextItem, separatorRef) => { - const nextItemIsScreen = Boolean(mlrunScreens.find(screen => screen.label === nextItem)) + const nextItemIsScreen = Boolean(mlrunScreens.find(screen => screen.id === nextItem)) + const nextItemIsFunction = Boolean( + currentProjectFunctions.find(func => func.metadata.name === nextItem) + ) - if (nextItemIsScreen || nextItem === params.projectName) { + if (nextItemIsScreen || nextItem === params.projectName || nextItemIsFunction) { const [activeSeparator] = document.getElementsByClassName('breadcrumbs__separator_active') if ( @@ -142,17 +175,22 @@ const BreadcrumbsStep = React.forwardRef( if (nextItemIsScreen) { setShowScreensList(state => !state) - if (showProjectsList) { - setShowProjectsList(false) - } + if (showProjectsList) setShowProjectsList(false) + if (showFunctionsList) setShowFunctionsList(false) } if (nextItem === params.projectName) { setShowProjectsList(state => !state) - if (showScreensList) { - setShowScreensList(false) - } + if (showScreensList) setShowScreensList(false) + if (showFunctionsList) setShowFunctionsList(false) + } + + if (nextItemIsFunction) { + setShowFunctionsList(state => !state) + + if (showScreensList) setShowScreensList(false) + if (showProjectsList) setShowProjectsList(false) } separatorRef.current.classList.toggle('breadcrumbs__separator_active') @@ -185,7 +223,7 @@ const BreadcrumbsStep = React.forwardRef( > - {showScreensList && urlParts.pathItems[index + 1] === urlParts.screen?.label && ( + {showScreensList && urlParts.pathItems[index + 1] === urlParts.screen?.id && ( )} + {showFunctionsList && urlParts.pathItems[index + 1] === urlParts.functionName && ( + handleSelectDropdownItem(separatorRef)} + selectedItem={urlParts.functionName} + searchValue={searchValue} + setSearchValue={setSearchValue} + withSearch + /> + )} {showProjectsList && urlParts.pathItems[index + 1] === params.projectName && ( <>