diff --git a/backend/src/routes/api/service/modelregistry/index.ts b/backend/src/routes/api/service/modelregistry/index.ts index f4cd428dad..a236548397 100644 --- a/backend/src/routes/api/service/modelregistry/index.ts +++ b/backend/src/routes/api/service/modelregistry/index.ts @@ -4,31 +4,29 @@ import { DEV_MODE } from '../../../../utils/constants'; import { getParam, setParam } from '../../../../utils/proxy'; export default async (fastify: KubeFastifyInstance): Promise => { - if (DEV_MODE) { - fastify.register(httpProxy, { - upstream: '', - prefix: '/:name', - rewritePrefix: '', - replyOptions: { - // preHandler must set the `upstream` param - getUpstream: (request) => getParam(request, 'upstream'), - }, - preHandler: (request, _, done) => { - const name = getParam(request, 'name'); + fastify.register(httpProxy, { + upstream: '', + prefix: '/:name', + rewritePrefix: '', + replyOptions: { + // preHandler must set the `upstream` param + getUpstream: (request) => getParam(request, 'upstream'), + }, + preHandler: (request, _, done) => { + const name = getParam(request, 'name'); - const upstream = DEV_MODE - ? // Use port forwarding for local development: - // kubectl port-forward -n svc/ : - `http://${process.env.MODEL_REGISTRY_SERVICE_HOST}:${process.env.MODEL_REGISTRY_SERVICE_PORT}` - : // Construct service URL - `http://${name}.odh-model-registries.svc.cluster.local:8080`; + const upstream = DEV_MODE + ? // Use port forwarding for local development: + // kubectl port-forward -n svc/ : + `http://${process.env.MODEL_REGISTRY_SERVICE_HOST}:${process.env.MODEL_REGISTRY_SERVICE_PORT}` + : // Construct service URL + `http://${name}.odh-model-registries.svc.cluster.local:8080`; - // assign the `upstream` param so we can dynamically set the upstream URL for http-proxy - setParam(request, 'upstream', upstream); + // assign the `upstream` param so we can dynamically set the upstream URL for http-proxy + setParam(request, 'upstream', upstream); - fastify.log.info(`Proxy ${request.method} request ${request.url} to ${upstream}`); - done(); - }, - }); - } + fastify.log.info(`Proxy ${request.method} request ${request.url} to ${upstream}`); + done(); + }, + }); }; diff --git a/frontend/src/__tests__/cypress/cypress/e2e/modelRegistry/ModelRegistry.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/modelRegistry/ModelRegistry.cy.ts index e8781191de..f09391c755 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/modelRegistry/ModelRegistry.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/modelRegistry/ModelRegistry.cy.ts @@ -76,7 +76,7 @@ describe('Model Registry', () => { modelRegistry.visit(); modelRegistry.navigate(); - modelRegistry.shouldtableToolbarExist(); + modelRegistry.shouldModelRegistrySelectorExist(); modelRegistry.shouldregisteredModelsEmpty(); }); diff --git a/frontend/src/__tests__/cypress/cypress/pages/modelRegistry.ts b/frontend/src/__tests__/cypress/cypress/pages/modelRegistry.ts index 9069d43e2a..f0ace5cf88 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/modelRegistry.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/modelRegistry.ts @@ -79,7 +79,11 @@ class ModelRegistry { } shouldregisteredModelsEmpty() { - cy.findByTestId('no-registered-models').should('exist'); + cy.findByTestId('empty-model-registry').should('exist'); + } + + shouldModelRegistrySelectorExist() { + cy.get('#model-registry-selector-dropdown').should('exist'); } shouldtableToolbarExist() { diff --git a/frontend/src/concepts/modelRegistry/context/ModelRegistryContext.tsx b/frontend/src/concepts/modelRegistry/context/ModelRegistryContext.tsx index b5ddda600d..eb3bdce52d 100644 --- a/frontend/src/concepts/modelRegistry/context/ModelRegistryContext.tsx +++ b/frontend/src/concepts/modelRegistry/context/ModelRegistryContext.tsx @@ -1,8 +1,6 @@ import * as React from 'react'; import { Alert, Bullseye } from '@patternfly/react-core'; import { SupportedArea, conditionalArea } from '~/concepts/areas'; -import { ModelRegistryKind } from '~/k8sTypes'; -import useModelRegistries from '~/concepts/modelRegistry/apiHooks/useModelRegistries'; import { MODEL_REGISTRY_DEFAULT_NAMESPACE } from '~/concepts/modelRegistry/const'; import useModelRegistryAPIState, { ModelRegistryAPIState } from './useModelRegistryAPIState'; import { @@ -19,9 +17,6 @@ export type ModelRegistryContextType = { ignoreTimedOut: () => void; refreshState: () => Promise; refreshAPIState: () => void; - modelRegistries: ModelRegistryKind[]; - preferredModelRegistry: ModelRegistryKind | undefined; - updatePreferredModelRegistry: (modelRegistry: ModelRegistryKind | undefined) => void; }; type ModelRegistryContextProviderProps = { @@ -37,25 +32,18 @@ export const ModelRegistryContext = React.createContext undefined, refreshState: async () => undefined, refreshAPIState: () => undefined, - modelRegistries: [], - preferredModelRegistry: undefined, - updatePreferredModelRegistry: () => undefined, }); export const ModelRegistryContextProvider = conditionalArea( SupportedArea.MODEL_REGISTRY, true, )(({ children, modelRegistryName }) => { - const [modelRegistries] = useModelRegistries(); - const [preferredModelRegistry, setPreferredModelRegistry] = - React.useState(undefined); - - const crState = useModelRegistryNamespaceCR(MODEL_REGISTRY_DEFAULT_NAMESPACE, modelRegistryName); - const [modelRegistryNamespaceCR, crLoaded, crLoadError, refreshCR] = crState; - const isCRReady = isModelRegistryAvailable(crState); + const state = useModelRegistryNamespaceCR(MODEL_REGISTRY_DEFAULT_NAMESPACE, modelRegistryName); + const [modelRegistryCR, crLoaded, crLoadError, refreshCR] = state; + const isCRReady = isModelRegistryAvailable(state); const [disableTimeout, setDisableTimeout] = React.useState(false); - const serverTimedOut = !disableTimeout && hasServerTimedOut(crState, isCRReady); + const serverTimedOut = !disableTimeout && hasServerTimedOut(state, isCRReady); const ignoreTimedOut = React.useCallback(() => { setDisableTimeout(true); }, []); @@ -64,29 +52,16 @@ export const ModelRegistryContextProvider = conditionalArea { - if (modelRegistries.length > 0 && !preferredModelRegistry) { - setPreferredModelRegistry(modelRegistries[0]); - } - }, [modelRegistries, preferredModelRegistry]); - const refreshState = React.useCallback( () => Promise.all([refreshCR()]).then(() => undefined), [refreshCR], ); - const updatePreferredModelRegistry = React.useCallback< - ModelRegistryContextType['updatePreferredModelRegistry'] - >((modelRegistry) => { - setPreferredModelRegistry(modelRegistry); - }, []); - - const error = crLoadError; - if (error) { + if (crLoadError) { return ( - {error.message} + {crLoadError.message} ); @@ -95,16 +70,13 @@ export const ModelRegistryContextProvider = conditionalArea {children} diff --git a/frontend/src/concepts/modelRegistry/context/ModelRegistrySelectorContext.tsx b/frontend/src/concepts/modelRegistry/context/ModelRegistrySelectorContext.tsx new file mode 100644 index 0000000000..155adea01a --- /dev/null +++ b/frontend/src/concepts/modelRegistry/context/ModelRegistrySelectorContext.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { Alert, Bullseye } from '@patternfly/react-core'; +import { SupportedArea, conditionalArea } from '~/concepts/areas'; +import { ModelRegistryKind } from '~/k8sTypes'; +import useModelRegistries from '~/concepts/modelRegistry/apiHooks/useModelRegistries'; + +export type ModelRegistrySelectorContextType = { + modelRegistries: ModelRegistryKind[]; + preferredModelRegistry: ModelRegistryKind | undefined; + updatePreferredModelRegistry: (modelRegistry: ModelRegistryKind | undefined) => void; +}; + +type ModelRegistrySelectorContextProviderProps = { + children: React.ReactNode; +}; + +export const ModelRegistrySelectorContext = React.createContext({ + modelRegistries: [], + preferredModelRegistry: undefined, + updatePreferredModelRegistry: () => undefined, +}); + +export const ModelRegistrySelectorContextProvider = + conditionalArea( + SupportedArea.MODEL_REGISTRY, + true, + )(({ children }) => { + const [modelRegistries, isLoaded, error] = useModelRegistries(); + const [preferredModelRegistry, setPreferredModelRegistry] = + React.useState(undefined); + + const firstModelRegistry = modelRegistries.length > 0 ? modelRegistries[0] : null; + + React.useEffect(() => { + if (firstModelRegistry && !preferredModelRegistry) { + setPreferredModelRegistry(firstModelRegistry); + } + }, [firstModelRegistry, preferredModelRegistry]); + + const updatePreferredModelRegistry = React.useCallback< + ModelRegistrySelectorContextType['updatePreferredModelRegistry'] + >((modelRegistry) => { + setPreferredModelRegistry(modelRegistry); + }, []); + + if (!isLoaded) { + return Loading model registries...; + } + + if (error) { + return ( + + + {error.message} + + + ); + } + + return ( + + {children} + + ); + }); diff --git a/frontend/src/pages/modelRegistry/ModelRegistryCoreLoader.tsx b/frontend/src/pages/modelRegistry/ModelRegistryCoreLoader.tsx index a5094c5c51..aa97e6de87 100644 --- a/frontend/src/pages/modelRegistry/ModelRegistryCoreLoader.tsx +++ b/frontend/src/pages/modelRegistry/ModelRegistryCoreLoader.tsx @@ -1,12 +1,85 @@ import * as React from 'react'; -import { Outlet } from 'react-router'; - +import { Navigate, Outlet, useParams } from 'react-router'; import { ModelRegistryContextProvider } from '~/concepts/modelRegistry/context/ModelRegistryContext'; +import ApplicationsPage from '~/pages/ApplicationsPage'; +import TitleWithIcon from '~/concepts/design/TitleWithIcon'; +import { ProjectObjectType } from '~/concepts/design/utils'; + +import { ModelRegistrySelectorContext } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext'; +import InvalidModelRegistry from './screens/InvalidModelRegistry'; +import EmptyModelRegistryState from './screens/EmptyModelRegistryState'; +import ModelRegistrySelectorNavigator from './screens/ModelRegistrySelectorNavigator'; + +type ApplicationPageProps = React.ComponentProps; +type EmptyStateProps = 'emptyStatePage' | 'empty'; + +type ModelRegistryCoreLoaderProps = { + getInvalidRedirectPath: (modelRegistry: string) => string; +}; + +type ApplicationPageRenderState = Pick; + +const ModelRegistryCoreLoader: React.FC = ({ + getInvalidRedirectPath, +}) => { + const { modelRegistry } = useParams<{ modelRegistry: string }>(); + const { modelRegistries, preferredModelRegistry } = React.useContext( + ModelRegistrySelectorContext, + ); + + let renderStateProps: ApplicationPageRenderState & { children?: React.ReactNode }; + if (modelRegistries.length === 0) { + renderStateProps = { + empty: true, + emptyStatePage: ( + // TODO: Replace this with a component for empty registries once we have the designs + { + // TODO: Add primary action + }} + /> + ), + }; + } else if (modelRegistry) { + const foundModelRegistry = modelRegistries.find((mr) => mr.metadata.name === modelRegistry); + if (foundModelRegistry) { + // Render the content + return ( + + + + ); + } + + // They ended up on a non-valid project path + renderStateProps = { + empty: true, + emptyStatePage: , + }; + } else { + // Redirect the namespace suffix into the URL + const redirectModelRegistry = preferredModelRegistry ?? modelRegistries[0]; + return ; + } + + return ( + + } + {...renderStateProps} + loaded + headerContent={ + `/modelRegistry/${modelRegistryName}`} + /> + } + provideChildrenPadding + /> + ); +}; -// TODO: Parametrize this to make the route dynamic -const ModelRegistryCoreLoader: React.FC = () => ( - - - -); export default ModelRegistryCoreLoader; diff --git a/frontend/src/pages/modelRegistry/ModelRegistryRoutes.tsx b/frontend/src/pages/modelRegistry/ModelRegistryRoutes.tsx index 89ea39bca8..ca9434bc2b 100644 --- a/frontend/src/pages/modelRegistry/ModelRegistryRoutes.tsx +++ b/frontend/src/pages/modelRegistry/ModelRegistryRoutes.tsx @@ -1,23 +1,31 @@ import * as React from 'react'; -import { Navigate, Route } from 'react-router-dom'; -import ProjectsRoutes from '~/concepts/projects/ProjectsRoutes'; +import { Navigate, Route, Routes } from 'react-router-dom'; +import { ModelRegistrySelectorContextProvider } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext'; import ModelRegistryCoreLoader from './ModelRegistryCoreLoader'; import ModelRegistry from './screens/ModelRegistry'; import { ModelVersionsTabs } from './screens/const'; import ModelVersions from './screens/ModelVersions'; const ModelRegistryRoutes: React.FC = () => ( - - }> - } /> - } /> + + } - /> - } /> - - + path={'/:modelRegistry?/*'} + element={ + `/modelRegistry/${modelRegistry}`} + /> + } + > + } /> + } + /> + } /> + + + ); export default ModelRegistryRoutes; diff --git a/frontend/src/pages/modelRegistry/screens/EmptyModelRegistryState.tsx b/frontend/src/pages/modelRegistry/screens/EmptyModelRegistryState.tsx new file mode 100644 index 0000000000..3b62e2312e --- /dev/null +++ b/frontend/src/pages/modelRegistry/screens/EmptyModelRegistryState.tsx @@ -0,0 +1,58 @@ +import { + Button, + ButtonVariant, + EmptyState, + EmptyStateActions, + EmptyStateBody, + EmptyStateFooter, + EmptyStateHeader, + EmptyStateIcon, + EmptyStateVariant, +} from '@patternfly/react-core'; +import { PlusCircleIcon } from '@patternfly/react-icons'; +import * as React from 'react'; + +type EmptyModelRegistryStateType = { + title: string; + description: string; + primaryActionText: string; + primaryActionOnClick: () => void; + secondaryActionText?: string; + secondaryActionOnClick?: () => void; +}; + +const EmptyModelRegistryState: React.FC = ({ + title, + description, + primaryActionText, + secondaryActionText, + primaryActionOnClick, + secondaryActionOnClick, +}) => ( + + } /> + {description} + + + + + {secondaryActionText && ( + + )} + + +); + +export default EmptyModelRegistryState; diff --git a/frontend/src/pages/modelRegistry/screens/EmptyModelVersions.tsx b/frontend/src/pages/modelRegistry/screens/EmptyModelVersions.tsx deleted file mode 100644 index 99e7e8df3e..0000000000 --- a/frontend/src/pages/modelRegistry/screens/EmptyModelVersions.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { - Button, - ButtonVariant, - EmptyState, - EmptyStateActions, - EmptyStateBody, - EmptyStateFooter, - EmptyStateHeader, - EmptyStateIcon, - EmptyStateVariant, -} from '@patternfly/react-core'; -import { PlusCircleIcon } from '@patternfly/react-icons'; -import * as React from 'react'; - -type EmptyRegisteredModelsType = { - rmName?: string; -}; - -const EmptyModelVersions: React.FC = ({ rmName }) => ( - - } /> - - {rmName} has no versions registered to it. Register a version to this -
- model. -
- - - - - - -
-); - -export default EmptyModelVersions; diff --git a/frontend/src/pages/modelRegistry/screens/EmptyRegisteredModels.tsx b/frontend/src/pages/modelRegistry/screens/EmptyRegisteredModels.tsx deleted file mode 100644 index e5fc317e0c..0000000000 --- a/frontend/src/pages/modelRegistry/screens/EmptyRegisteredModels.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { - EmptyState, - EmptyStateBody, - EmptyStateHeader, - EmptyStateIcon, - EmptyStateVariant, -} from '@patternfly/react-core'; -import { PlusCircleIcon } from '@patternfly/react-icons'; -import * as React from 'react'; - -type EmptyRegisteredModelsType = { - preferredModelRegistry?: string; -}; -const EmptyRegisteredModels: React.FC = ({ preferredModelRegistry }) => ( - - } - /> - - {preferredModelRegistry} has no models registered to it. Register a model to this -
- registry or select a different one. -
-
-); - -export default EmptyRegisteredModels; diff --git a/frontend/src/pages/modelRegistry/screens/InvalidModelRegistry.tsx b/frontend/src/pages/modelRegistry/screens/InvalidModelRegistry.tsx new file mode 100644 index 0000000000..6042a50a37 --- /dev/null +++ b/frontend/src/pages/modelRegistry/screens/InvalidModelRegistry.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import EmptyStateErrorMessage from '~/components/EmptyStateErrorMessage'; +import ModelRegistrySelectorNavigator from './ModelRegistrySelectorNavigator'; + +type InvalidModelRegistryProps = { + title?: string; + modelRegistry?: string; +}; + +const InvalidModelRegistry: React.FC = ({ title, modelRegistry }) => ( + + `/modelRegistry/${modelRegistryName}`} + primary + /> + +); + +export default InvalidModelRegistry; diff --git a/frontend/src/pages/modelRegistry/screens/ModelRegistry.tsx b/frontend/src/pages/modelRegistry/screens/ModelRegistry.tsx index 3530581e98..f62695e1c9 100644 --- a/frontend/src/pages/modelRegistry/screens/ModelRegistry.tsx +++ b/frontend/src/pages/modelRegistry/screens/ModelRegistry.tsx @@ -1,29 +1,47 @@ import React from 'react'; import ApplicationsPage from '~/pages/ApplicationsPage'; -import { ModelRegistryContext } from '~/concepts/modelRegistry/context/ModelRegistryContext'; import useRegisteredModels from '~/concepts/modelRegistry/apiHooks/useRegisteredModels'; -import EmptyRegisteredModels from './EmptyRegisteredModels'; -import RegisteredModelsTableToolbar from './RegisteredModelsTableToolbar'; +import { ModelRegistrySelectorContext } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext'; +import TitleWithIcon from '~/concepts/design/TitleWithIcon'; +import { ProjectObjectType } from '~/concepts/design/utils'; import RegisteredModelListView from './RegisteredModelListView'; +import EmptyModelRegistryState from './EmptyModelRegistryState'; +import ModelRegistrySelectorNavigator from './ModelRegistrySelectorNavigator'; const ModelRegistry: React.FC = () => { - const { preferredModelRegistry } = React.useContext(ModelRegistryContext); + const { preferredModelRegistry } = React.useContext(ModelRegistrySelectorContext); const [registeredModels, loaded, loadError] = useRegisteredModels(); return ( - - - + { + // TODO: Add primary action + }} + secondaryActionOnClick={() => { + // TODO: Add secondary action + }} + /> + } + title={ + } - title="Registered models" description="View and manage your registered models." + headerContent={ + `/modelRegistry/${modelRegistryName}`} + /> + } loadError={loadError} loaded={loaded} provideChildrenPadding + removeChildrenTopPadding > diff --git a/frontend/src/pages/modelRegistry/screens/ModelRegistrySelector.tsx b/frontend/src/pages/modelRegistry/screens/ModelRegistrySelector.tsx index 2be18d5ba4..db44c4a16a 100644 --- a/frontend/src/pages/modelRegistry/screens/ModelRegistrySelector.tsx +++ b/frontend/src/pages/modelRegistry/screens/ModelRegistrySelector.tsx @@ -5,44 +5,67 @@ import { SelectGroup, SelectOption, } from '@patternfly/react-core/deprecated'; -import { ModelRegistryContext } from '~/concepts/modelRegistry/context/ModelRegistryContext'; +import { Bullseye, Flex, FlexItem } from '@patternfly/react-core'; import { useBrowserStorage } from '~/components/browserStorage'; +import { ModelRegistrySelectorContext } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext'; +import { ProjectObjectType, typedObjectImage } from '~/concepts/design/utils'; +import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils'; const MODEL_REGISTRY_FAVORITE_STORAGE_KEY = 'odh.dashboard.model.registry.favorite'; -const ModelRegistrySelector: React.FC = () => { - const { modelRegistries, preferredModelRegistry, updatePreferredModelRegistry } = - React.useContext(ModelRegistryContext); +type ModelRegistrySelectorProps = { + modelRegistry: string; + onSelection: (modelRegistry: string) => void; + primary?: boolean; +}; + +const ModelRegistrySelector: React.FC = ({ + modelRegistry, + onSelection, + primary, +}) => { + const { modelRegistries, updatePreferredModelRegistry } = React.useContext( + ModelRegistrySelectorContext, + ); + const selection = modelRegistries.find((mr) => mr.metadata.name === modelRegistry); + const [isOpen, setIsOpen] = React.useState(false); const [favorites, setFavorites] = useBrowserStorage( MODEL_REGISTRY_FAVORITE_STORAGE_KEY, [], ); - const [isOpen, setIsOpen] = React.useState(false); + const selectionDisplayName = selection ? getDisplayNameFromK8sResource(selection) : modelRegistry; + + const toggleLabel = modelRegistries.length === 0 ? 'No model registries' : selectionDisplayName; const options = [ - - {modelRegistries.map((modelRegistry) => ( + + {modelRegistries.map((mr) => ( ))} , ]; - return ( + const selector = ( ); + + if (primary) { + return selector; + } + + return ( + + + + + Model registry + + {selector} + + + ); }; export default ModelRegistrySelector; diff --git a/frontend/src/pages/modelRegistry/screens/ModelRegistrySelectorNavigator.tsx b/frontend/src/pages/modelRegistry/screens/ModelRegistrySelectorNavigator.tsx new file mode 100644 index 0000000000..7c606fd31f --- /dev/null +++ b/frontend/src/pages/modelRegistry/screens/ModelRegistrySelectorNavigator.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import ModelRegistrySelector from './ModelRegistrySelector'; + +type ModelRegistrySelectorNavigatorProps = { + getRedirectPath: (namespace: string) => string; +} & Omit, 'onSelection' | 'modelRegistry'>; + +const ModelRegistrySelectorNavigator: React.FC = ({ + getRedirectPath, + ...modelRegistrySelectorProps +}) => { + const navigate = useNavigate(); + const { modelRegistry } = useParams<{ modelRegistry: string }>(); + + return ( + { + navigate(getRedirectPath(modelRegistryName)); + }} + modelRegistry={modelRegistry ?? ''} + /> + ); +}; + +export default ModelRegistrySelectorNavigator; diff --git a/frontend/src/pages/modelRegistry/screens/ModelVersionListView.tsx b/frontend/src/pages/modelRegistry/screens/ModelVersionListView.tsx index f2d33cb5ca..67dfd68105 100644 --- a/frontend/src/pages/modelRegistry/screens/ModelVersionListView.tsx +++ b/frontend/src/pages/modelRegistry/screens/ModelVersionListView.tsx @@ -18,7 +18,7 @@ import { SearchType } from '~/concepts/dashboard/DashboardSearchField'; import { ModelVersion } from '~/concepts/modelRegistry/types'; import SimpleDropdownSelect from '~/components/SimpleDropdownSelect'; import ModelVersionsTable from './ModelVersionsTable'; -import EmptyModelVersions from './EmptyModelVersions'; +import EmptyModelRegistryState from './EmptyModelRegistryState'; import { filteredmodelVersions } from './utils'; type ModelVersionListViewProps = { @@ -41,7 +41,20 @@ const ModelVersionListView: React.FC = ({ const filteredModelVersions = filteredmodelVersions(unfilteredmodelVersions, search, searchType); if (unfilteredmodelVersions.length === 0) { - return ; + return ( + { + // TODO: Add primary action + }} + secondaryActionOnClick={() => { + // TODO: Add secondary action + }} + /> + ); } return ( diff --git a/frontend/src/pages/modelRegistry/screens/ModelVersions.tsx b/frontend/src/pages/modelRegistry/screens/ModelVersions.tsx index bdd20ec219..15aa9e7ea2 100644 --- a/frontend/src/pages/modelRegistry/screens/ModelVersions.tsx +++ b/frontend/src/pages/modelRegistry/screens/ModelVersions.tsx @@ -1,10 +1,11 @@ import React from 'react'; import { useParams } from 'react-router'; import { Breadcrumb, BreadcrumbItem } from '@patternfly/react-core'; +import { Link } from 'react-router-dom'; import ApplicationsPage from '~/pages/ApplicationsPage'; import useModelVersionsByRegisteredModel from '~/concepts/modelRegistry/apiHooks/useModelVersionsByRegisteredModel'; import useRegisteredModelById from '~/concepts/modelRegistry/apiHooks/useRegisteredModelById'; -import { ModelRegistryContext } from '~/concepts/modelRegistry/context/ModelRegistryContext'; +import { ModelRegistrySelectorContext } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext'; import GlobalModelVersionsTabs from './GlobalModelVersionsTabs'; import { ModelVersionsTabs } from './const'; import ModelVersionsHeaderActions from './ModelVersionsHeaderActions'; @@ -17,7 +18,7 @@ type ModelVersionsProps = { >; const ModelVersions: React.FC = ({ tab, ...pageProps }) => { - const { preferredModelRegistry } = React.useContext(ModelRegistryContext); + const { preferredModelRegistry } = React.useContext(ModelRegistrySelectorContext); const { registeredModelId: rmId } = useParams(); const [modelVersions, mvLoaded, mvLoadError] = useModelVersionsByRegisteredModel(rmId); const [rm] = useRegisteredModelById(rmId); @@ -26,11 +27,14 @@ const ModelVersions: React.FC = ({ tab, ...pageProps }) => { - - Registered models - {preferredModelRegistry?.metadata.name} - + ( + + Registered models - {preferredModelRegistry?.metadata.name} + + )} + /> {rm?.name} } diff --git a/frontend/src/pages/modelRegistry/screens/RegisteredModelLink.tsx b/frontend/src/pages/modelRegistry/screens/RegisteredModelLink.tsx index 6313d14668..87c44541e6 100644 --- a/frontend/src/pages/modelRegistry/screens/RegisteredModelLink.tsx +++ b/frontend/src/pages/modelRegistry/screens/RegisteredModelLink.tsx @@ -1,7 +1,7 @@ import { Truncate } from '@patternfly/react-core'; import * as React from 'react'; import { Link } from 'react-router-dom'; -import { ModelRegistryContext } from '~/concepts/modelRegistry/context/ModelRegistryContext'; +import { ModelRegistrySelectorContext } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext'; import { RegisteredModel } from '~/concepts/modelRegistry/types'; type RegisteredModelLinkProps = { @@ -9,12 +9,12 @@ type RegisteredModelLinkProps = { }; const RegisteredModelLink: React.FC = ({ registeredModel }) => { - const { preferredModelRegistry } = React.useContext(ModelRegistryContext); + const { preferredModelRegistry } = React.useContext(ModelRegistrySelectorContext); const registeredModelId = registeredModel.id; return ( diff --git a/frontend/src/pages/modelRegistry/screens/RegisteredModelsTableToolbar.tsx b/frontend/src/pages/modelRegistry/screens/RegisteredModelsTableToolbar.tsx index bb6d0e964f..480a8c93cf 100644 --- a/frontend/src/pages/modelRegistry/screens/RegisteredModelsTableToolbar.tsx +++ b/frontend/src/pages/modelRegistry/screens/RegisteredModelsTableToolbar.tsx @@ -12,7 +12,6 @@ import { ToolbarToggleGroup, } from '@patternfly/react-core'; import { EllipsisVIcon, FilterIcon } from '@patternfly/react-icons'; -import ModelRegistrySelector from './ModelRegistrySelector'; type RegisteredModelsTableToolbarProps = { toggleGroupItems?: React.ReactNode; @@ -29,9 +28,6 @@ const RegisteredModelsTableToolbar: React.FC return ( - - - } breakpoint="xl"> {tableToggleGroupItems}