From 0701780e95850fcf31236d2849aea0de6b76a6ba Mon Sep 17 00:00:00 2001 From: Dipanshu Gupta Date: Thu, 2 May 2024 23:10:03 +0530 Subject: [PATCH] Model Version - View --- .../modelRegistry/ModelRegistryRoutes.tsx | 10 ++ .../ModelVersionDetails.tsx | 82 ++++++++++++++++ .../ModelVersionDetailsHeaderActions.tsx | 52 ++++++++++ .../ModelVersionDetailsTabs.tsx | 45 +++++++++ .../ModelVersionSelector.tsx | 98 +++++++++++++++++++ .../screens/ModelVersionDetails/const.ts | 9 ++ .../screens/ModelVersionsTableRow.tsx | 91 ++++++++++------- .../screens/RegisteredModelLink.tsx | 17 ---- .../screens/RegisteredModelTableRow.tsx | 14 ++- .../pages/modelRegistry/screens/routeUtils.ts | 8 ++ .../screens/useRegisteredModelUrl.ts | 11 --- 11 files changed, 367 insertions(+), 70 deletions(-) create mode 100644 frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionDetails.tsx create mode 100644 frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionDetailsHeaderActions.tsx create mode 100644 frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionDetailsTabs.tsx create mode 100644 frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionSelector.tsx create mode 100644 frontend/src/pages/modelRegistry/screens/ModelVersionDetails/const.ts delete mode 100644 frontend/src/pages/modelRegistry/screens/RegisteredModelLink.tsx create mode 100644 frontend/src/pages/modelRegistry/screens/routeUtils.ts delete mode 100644 frontend/src/pages/modelRegistry/screens/useRegisteredModelUrl.ts diff --git a/frontend/src/pages/modelRegistry/ModelRegistryRoutes.tsx b/frontend/src/pages/modelRegistry/ModelRegistryRoutes.tsx index 69e8835626..1efe05a190 100644 --- a/frontend/src/pages/modelRegistry/ModelRegistryRoutes.tsx +++ b/frontend/src/pages/modelRegistry/ModelRegistryRoutes.tsx @@ -5,6 +5,8 @@ import ModelRegistryCoreLoader from './ModelRegistryCoreLoader'; import ModelRegistry from './screens/ModelRegistry'; import { ModelVersionsTabs } from './screens/const'; import ModelVersions from './screens/ModelVersions'; +import ModelVersionsDetails from './screens/ModelVersionDetails/ModelVersionDetails'; +import { ModelVersionDetailsTab } from './screens/ModelVersionDetails/const'; const ModelRegistryRoutes: React.FC = () => ( @@ -28,6 +30,14 @@ const ModelRegistryRoutes: React.FC = () => ( path={ModelVersionsTabs.DETAILS} element={} /> + + } /> + } + /> + } /> + } /> } /> diff --git a/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionDetails.tsx b/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionDetails.tsx new file mode 100644 index 0000000000..3240ee5580 --- /dev/null +++ b/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionDetails.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { useNavigate, useParams } from 'react-router'; +import { Breadcrumb, BreadcrumbItem, Flex, FlexItem } from '@patternfly/react-core'; +import { Link } from 'react-router-dom'; +import ApplicationsPage from '~/pages/ApplicationsPage'; +import useModelVersionById from '~/concepts/modelRegistry/apiHooks/useModelVersionById'; +import { ModelRegistrySelectorContext } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext'; +import { modelVersionUrl } from '~/pages/modelRegistry/screens/routeUtils'; +import { ModelVersionDetailsTab } from './const'; +import ModelVersionsDetailsHeaderActions from './ModelVersionDetailsHeaderActions'; +import ModelVersionDetailsTabs from './ModelVersionDetailsTabs'; +import ModelVersionSelector from './ModelVersionSelector'; + +type ModelVersionsDetailProps = { + tab: ModelVersionDetailsTab; +} & Omit< + React.ComponentProps, + 'breadcrumb' | 'title' | 'description' | 'loadError' | 'loaded' | 'provideChildrenPadding' +>; + +const ModelVersionsDetails: React.FC = ({ tab, ...pageProps }) => { + const navigate = useNavigate(); + + const { preferredModelRegistry } = React.useContext(ModelRegistrySelectorContext); + + const { modelVersionId: mvId, registeredModelId: rmId } = useParams(); + const [mv, mvLoaded, mvLoadError] = useModelVersionById(mvId); + + return ( + + ( + + Registered models - {preferredModelRegistry?.metadata.name} + + )} + /> + {preferredModelRegistry?.metadata.name}} + /> + {mv?.name} + + } + title={mv?.name} + headerAction={ + mvLoaded && + mv && ( + + + + navigate( + modelVersionUrl(modelVersionId, rmId, preferredModelRegistry?.metadata.name), + ) + } + /> + + + + + + ) + } + description={mv?.description} + loadError={mvLoadError} + loaded={mvLoaded} + provideChildrenPadding + > + {mv !== null && } + + ); +}; + +export default ModelVersionsDetails; diff --git a/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionDetailsHeaderActions.tsx b/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionDetailsHeaderActions.tsx new file mode 100644 index 0000000000..02be881425 --- /dev/null +++ b/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionDetailsHeaderActions.tsx @@ -0,0 +1,52 @@ +import * as React from 'react'; +import { Dropdown, DropdownList, MenuToggle, DropdownItem } from '@patternfly/react-core'; + +const ModelVersionsDetailsHeaderActions: React.FC = () => { + const [isOpenActionDropdown, setOpenActionDropdown] = React.useState(false); + const tooltipRef = React.useRef(null); + + return ( + setOpenActionDropdown(false)} + onOpenChange={(open) => setOpenActionDropdown(open)} + toggle={(toggleRef) => ( + setOpenActionDropdown(!isOpenActionDropdown)} + isExpanded={isOpenActionDropdown} + aria-label="Model version details action toggle" + data-testid="model-version-details-action-button" + > + Actions + + )} + > + + undefined} + ref={tooltipRef} + isDisabled // This feature is currently disabled but will be enabled in a future PR post-summit release. + > + Deploy + + undefined} + ref={tooltipRef} + isDisabled // This feature is currently disabled but will be enabled in a future PR post-summit release. + > + Archive version + + + + ); +}; + +export default ModelVersionsDetailsHeaderActions; diff --git a/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionDetailsTabs.tsx b/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionDetailsTabs.tsx new file mode 100644 index 0000000000..3f7c402929 --- /dev/null +++ b/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionDetailsTabs.tsx @@ -0,0 +1,45 @@ +import * as React from 'react'; +import { PageSection, Tab, Tabs, TabTitleText } from '@patternfly/react-core'; +import '~/pages/pipelines/global/runs/GlobalPipelineRunsTabs.scss'; +import { ModelVersion } from '~/concepts/modelRegistry/types'; +import { ModelVersionDetailsTabTitle, ModelVersionDetailsTab } from './const'; + +type ModelVersionDetailTabsProps = { + tab: ModelVersionDetailsTab; + modelVersion?: ModelVersion; +}; + +const ModelVersionDetailsTabs: React.FC = ({ + tab, + modelVersion: mv, +}) => ( + + {ModelVersionDetailsTabTitle.DETAILS}} + aria-label="Model versions tab" + data-testid="model-versions-tab" + > + + {/* */} + + + {ModelVersionDetailsTabTitle.REGISTERED_DEPLOYMENTS}} + aria-label="Model Details tab" + data-testid="model-details-tab" + > + + {/* TODO: Fill Model Details Page Component here */} + + + +); + +export default ModelVersionDetailsTabs; diff --git a/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionSelector.tsx b/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionSelector.tsx new file mode 100644 index 0000000000..ceb2771cfe --- /dev/null +++ b/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionSelector.tsx @@ -0,0 +1,98 @@ +import * as React from 'react'; +import { + HelperText, + HelperTextItem, + Menu, + MenuContainer, + MenuContent, + MenuItem, + MenuList, + MenuSearch, + MenuSearchInput, + MenuToggle, + SearchInput, +} from '@patternfly/react-core'; +import useModelVersionsByRegisteredModel from '~/concepts/modelRegistry/apiHooks/useModelVersionsByRegisteredModel'; +import { ModelVersion } from '~/concepts/modelRegistry/types'; + +type ModelVersionSelectorProps = { + rmId?: string; + selection: ModelVersion; + onSelect: (versionId: string) => void; +}; + +const ModelVersionSelector: React.FC = ({ + rmId, + selection, + onSelect, +}) => { + const [isOpen, setOpen] = React.useState(false); + const [input, setInput] = React.useState(''); + + const toggleRef = React.useRef(null); + const menuRef = React.useRef(null); + + const [modelVersions] = useModelVersionsByRegisteredModel(rmId); + + const menu = ( + { + onSelect(itemId as string); + setOpen(false); + }} + data-id="model-version-selector-menu" + ref={menuRef} + isScrollable + activeItemId={selection.id} + > + + + + setInput(value)} + /> + + + + {`Type a name to search your ${modelVersions.size} versions.`} + + + + + {modelVersions.items.map((mv, idx) => ( + + {mv.name} + + ))} + + + + ); + + return ( + setOpen(!isOpen)} + isExpanded={isOpen} + isFullWidth + data-testid="pipeline-version-toggle-button" + > + {selection.name} + + } + menu={menu} + menuRef={menuRef} + popperProps={{ maxWidth: 'trigger' }} + onOpenChange={(open) => setOpen(open)} + /> + ); +}; + +export default ModelVersionSelector; diff --git a/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/const.ts b/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/const.ts new file mode 100644 index 0000000000..280d3a3832 --- /dev/null +++ b/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/const.ts @@ -0,0 +1,9 @@ +export enum ModelVersionDetailsTab { + DETAILS = 'details', + REGISTERED_DEPLOYMENTS = 'registered_deployments', +} + +export enum ModelVersionDetailsTabTitle { + DETAILS = 'Details', + REGISTERED_DEPLOYMENTS = 'Registered deployments', +} diff --git a/frontend/src/pages/modelRegistry/screens/ModelVersionsTableRow.tsx b/frontend/src/pages/modelRegistry/screens/ModelVersionsTableRow.tsx index 30bb635c19..9d7c8a8afe 100644 --- a/frontend/src/pages/modelRegistry/screens/ModelVersionsTableRow.tsx +++ b/frontend/src/pages/modelRegistry/screens/ModelVersionsTableRow.tsx @@ -1,49 +1,66 @@ import * as React from 'react'; import { ActionsColumn, Td, Tr } from '@patternfly/react-table'; -import { Text, TextVariants, Truncate } from '@patternfly/react-core'; +import { Text, TextVariants, Truncate, FlexItem } from '@patternfly/react-core'; +import { Link } from 'react-router-dom'; import { ModelVersion } from '~/concepts/modelRegistry/types'; +import { ModelRegistrySelectorContext } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext'; import ModelLabels from './ModelLabels'; import ModelTimestamp from './ModelTimestamp'; +import { modelVersionUrl } from './routeUtils'; type ModelVersionsTableRowProps = { modelVersion: ModelVersion; }; -const ModelVersionsTableRow: React.FC = ({ modelVersion: mv }) => ( - - -
- -
- {mv.description && ( - - - - )} - - - - - {mv.author} - - - - - undefined, - }, - { - title: 'Archive version', - isDisabled: true, // This feature is currently disabled but will be enabled in a future PR post-summit release. - }, - ]} - /> - - -); +const ModelVersionsTableRow: React.FC = ({ modelVersion: mv }) => { + const { preferredModelRegistry } = React.useContext(ModelRegistrySelectorContext); + + return ( + + +
+ + + + + +
+ {mv.description && ( + + + + )} + + + + + {mv.author} + + + + + undefined, + }, + { + title: 'Archive version', + isDisabled: true, // This feature is currently disabled but will be enabled in a future PR post-summit release. + }, + ]} + /> + + + ); +}; export default ModelVersionsTableRow; diff --git a/frontend/src/pages/modelRegistry/screens/RegisteredModelLink.tsx b/frontend/src/pages/modelRegistry/screens/RegisteredModelLink.tsx deleted file mode 100644 index 62a13d51ac..0000000000 --- a/frontend/src/pages/modelRegistry/screens/RegisteredModelLink.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Truncate } from '@patternfly/react-core'; -import * as React from 'react'; -import { Link } from 'react-router-dom'; -import { RegisteredModel } from '~/concepts/modelRegistry/types'; -import useRegisteredModelUrl from './useRegisteredModelUrl'; - -type RegisteredModelLinkProps = { - registeredModel: RegisteredModel; -}; - -const RegisteredModelLink: React.FC = ({ registeredModel }) => ( - - - -); - -export default RegisteredModelLink; diff --git a/frontend/src/pages/modelRegistry/screens/RegisteredModelTableRow.tsx b/frontend/src/pages/modelRegistry/screens/RegisteredModelTableRow.tsx index ca2de53612..179d53e81d 100644 --- a/frontend/src/pages/modelRegistry/screens/RegisteredModelTableRow.tsx +++ b/frontend/src/pages/modelRegistry/screens/RegisteredModelTableRow.tsx @@ -1,14 +1,14 @@ import * as React from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, Link } from 'react-router-dom'; import { ActionsColumn, Td, Tr } from '@patternfly/react-table'; import { FlexItem, Text, TextVariants, Truncate } from '@patternfly/react-core'; import { RegisteredModel } from '~/concepts/modelRegistry/types'; +import { ModelRegistrySelectorContext } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext'; import RegisteredModelOwner from './RegisteredModelOwner'; -import RegisteredModelLink from './RegisteredModelLink'; import ModelLabels from './ModelLabels'; import ModelTimestamp from './ModelTimestamp'; -import useRegisteredModelUrl from './useRegisteredModelUrl'; import { ModelVersionsTabs } from './const'; +import { registeredModelUrl } from './routeUtils'; type RegisteredModelTableRowProps = { registeredModel: RegisteredModel; @@ -18,13 +18,17 @@ const RegisteredModelTableRow: React.FC = ({ registeredModel: rm, }) => { const navigate = useNavigate(); - const rmUrl = useRegisteredModelUrl(rm); + const { preferredModelRegistry } = React.useContext(ModelRegistrySelectorContext); + const rmUrl = registeredModelUrl(rm.id, preferredModelRegistry?.metadata.name); + return (
- + + +
{rm.description && ( diff --git a/frontend/src/pages/modelRegistry/screens/routeUtils.ts b/frontend/src/pages/modelRegistry/screens/routeUtils.ts new file mode 100644 index 0000000000..bfa9f48870 --- /dev/null +++ b/frontend/src/pages/modelRegistry/screens/routeUtils.ts @@ -0,0 +1,8 @@ +export const registeredModelUrl = (rmId?: string, preferredModelRegistry?: string): string => + `/modelRegistry/${preferredModelRegistry}/registeredModels/${rmId}`; + +export const modelVersionUrl = ( + mvId: string, + rmId?: string, + preferredModelRegistry?: string, +): string => `${registeredModelUrl(rmId, preferredModelRegistry)}/versions/${mvId}`; diff --git a/frontend/src/pages/modelRegistry/screens/useRegisteredModelUrl.ts b/frontend/src/pages/modelRegistry/screens/useRegisteredModelUrl.ts deleted file mode 100644 index 97b2473aa1..0000000000 --- a/frontend/src/pages/modelRegistry/screens/useRegisteredModelUrl.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as React from 'react'; -import { ModelRegistrySelectorContext } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext'; -import { RegisteredModel } from '~/concepts/modelRegistry/types'; - -const useRegisteredModelUrl = (rm: RegisteredModel): string => { - const { preferredModelRegistry } = React.useContext(ModelRegistrySelectorContext); - const registeredModelId = rm.id; - return `/modelRegistry/${preferredModelRegistry?.metadata.name}/registeredModels/${registeredModelId}`; -}; - -export default useRegisteredModelUrl;