Skip to content

Commit

Permalink
[RHOAIENG-6573] Model Detail - Registered Deployments Section (#3092)
Browse files Browse the repository at this point in the history
* [RHOAIENG-6573] Model Detail - Registered Deployments Section

* redirect to deployments page/tab after deployment creation
  • Loading branch information
jpuzz0 authored Aug 16, 2024
1 parent 1768c96 commit 1391418
Show file tree
Hide file tree
Showing 28 changed files with 532 additions and 131 deletions.
8 changes: 8 additions & 0 deletions frontend/src/__mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ export * from './mockPipelineKF';
export * from './mockSecretK8sResource';
export * from './mockGoogleRpcStatusKF';
export * from './mockK8sStatus';
export * from './mockComponents';
export * from './mockRegisteredModel';
export * from './mockModelVersion';
export * from './mockModelVersionList';
export * from './mockModelArtifactList';
export * from './mockModelRegistryService';
export * from './mockServingRuntimeK8sResource';
export * from './mockInferenceServiceK8sResource';
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ class ModelVersionDetails {
findModelVersionDropdownItem(name: string) {
return cy.findByTestId('model-version-selector-list').find('li').contains(name);
}

findDetailsTab() {
return cy.findByTestId('model-versions-details-tab');
}

findRegisteredDeploymentsTab() {
return cy.findByTestId('deployments-tab');
}
}

export const modelVersionDetails = new ModelVersionDetails();
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
/* eslint-disable camelcase */
import { mockDashboardConfig, mockK8sResourceList } from '~/__mocks__';
import { mockComponents } from '~/__mocks__/mockComponents';
import { ServiceModel } from '~/__tests__/cypress/cypress/utils/models';
import { modelVersionDetails } from '~/__tests__/cypress/cypress/pages/modelRegistry/modelVersionDetails';
import { mockRegisteredModel } from '~/__mocks__/mockRegisteredModel';
import { mockModelVersion } from '~/__mocks__/mockModelVersion';
import { mockModelVersionList } from '~/__mocks__/mockModelVersionList';
import { mockModelArtifactList } from '~/__mocks__/mockModelArtifactList';
import {
mockDashboardConfig,
mockK8sResourceList,
mockComponents,
mockRegisteredModel,
mockModelVersion,
mockModelVersionList,
mockModelArtifactList,
mockModelRegistryService,
mockServingRuntimeK8sResource,
mockInferenceServiceK8sResource,
mockProjectK8sResource,
} from '~/__mocks__';

import {
InferenceServiceModel,
ProjectModel,
ServiceModel,
ServingRuntimeModel,
} from '~/__tests__/cypress/cypress/utils/models';
import { verifyRelativeURL } from '~/__tests__/cypress/cypress/utils/url';
import { mockModelRegistryService } from '~/__mocks__/mockModelRegistryService';
import { modelVersionDetails } from '~/__tests__/cypress/cypress/pages/modelRegistry/modelVersionDetails';
import { InferenceServiceModelState } from '~/pages/modelServing/screens/types';
import { modelServingGlobal } from '~/__tests__/cypress/cypress/pages/modelServing';

const MODEL_REGISTRY_API_VERSION = 'v1alpha3';

Expand All @@ -29,6 +43,8 @@ const initIntercepts = () => {
]),
);

cy.interceptK8sList(ProjectModel, mockK8sResourceList([mockProjectK8sResource({})]));

cy.interceptOdh(
`GET /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/registered_models/:registeredModelId`,
{
Expand Down Expand Up @@ -116,41 +132,82 @@ const initIntercepts = () => {
};

describe('Model version details', () => {
beforeEach(() => {
initIntercepts();
modelVersionDetails.visit();
});
describe('Details tab', () => {
beforeEach(() => {
initIntercepts();
modelVersionDetails.visit();
});

it('Model version details page header', () => {
verifyRelativeURL('/modelRegistry/modelregistry-sample/registeredModels/1/versions/1/details');
cy.findByTestId('app-page-title').should('have.text', 'Version 1');
cy.findByTestId('breadcrumb-version-name').should('have.text', 'Version 1');
});
it('Model version details page header', () => {
verifyRelativeURL(
'/modelRegistry/modelregistry-sample/registeredModels/1/versions/1/details',
);
cy.findByTestId('app-page-title').should('have.text', 'Version 1');
cy.findByTestId('breadcrumb-version-name').should('have.text', 'Version 1');
});

it('Model version details tab', () => {
modelVersionDetails.findVersionId().contains('1');
modelVersionDetails.findDescription().should('have.text', 'Description of model version');
modelVersionDetails.findMoreLabelsButton().contains('6 more');
modelVersionDetails.findMoreLabelsButton().click();
modelVersionDetails.shouldContainsModalLabels([
'Testing label',
'Financial',
'Financial data',
'Fraud detection',
'Machine learning',
'Next data to be overflow',
'Label x',
'Label y',
'Label z',
]);
modelVersionDetails.findStorageLocation().contains('s3://test-bucket/demo-models/test-path');
it('Model version details tab', () => {
modelVersionDetails.findVersionId().contains('1');
modelVersionDetails.findDescription().should('have.text', 'Description of model version');
modelVersionDetails.findMoreLabelsButton().contains('6 more');
modelVersionDetails.findMoreLabelsButton().click();
modelVersionDetails.shouldContainsModalLabels([
'Testing label',
'Financial',
'Financial data',
'Fraud detection',
'Machine learning',
'Next data to be overflow',
'Label x',
'Label y',
'Label z',
]);
modelVersionDetails.findStorageLocation().contains('s3://test-bucket/demo-models/test-path');
});

it('Switching model versions', () => {
modelVersionDetails.findVersionId().contains('1');
modelVersionDetails.findModelVersionDropdownButton().click();
modelVersionDetails.findModelVersionDropdownSearch().fill('Version 2');
modelVersionDetails.findModelVersionDropdownItem('Version 2').click();
modelVersionDetails.findVersionId().contains('2');
});
});

it('Switching model versions', () => {
modelVersionDetails.findVersionId().contains('1');
modelVersionDetails.findModelVersionDropdownButton().click();
modelVersionDetails.findModelVersionDropdownSearch().fill('Version 2');
modelVersionDetails.findModelVersionDropdownItem('Version 2').click();
modelVersionDetails.findVersionId().contains('2');
describe('Registered deployments tab', () => {
beforeEach(() => {
initIntercepts();
});

it('renders empty state when the version has no registered deployments', () => {
cy.interceptK8sList(InferenceServiceModel, mockK8sResourceList([]));
cy.interceptK8sList(ServingRuntimeModel, mockK8sResourceList([]));

modelVersionDetails.visit();
modelVersionDetails.findRegisteredDeploymentsTab().click();

cy.findByTestId('model-version-deployments-empty-state').should('exist');
});

it('renders table with data', () => {
cy.interceptK8sList(
InferenceServiceModel,
mockK8sResourceList([
mockInferenceServiceK8sResource({
url: 'test-inference-status.url.com',
activeModelState: InferenceServiceModelState.LOADED,
}),
]),
);
cy.interceptK8sList(
ServingRuntimeModel,
mockK8sResourceList([mockServingRuntimeK8sResource({})]),
);

modelVersionDetails.visit();
modelVersionDetails.findRegisteredDeploymentsTab().click();

modelServingGlobal.getModelRow('Test Inference Service').should('exist');
});
});
});
1 change: 1 addition & 0 deletions frontend/src/api/k8s/inferenceServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export const assembleInferenceService = (
[KnownLabels.DASHBOARD_RESOURCE]: 'true',
...(!isModelMesh &&
!externalRoute && { 'networking.knative.dev/visibility': 'cluster-local' }),
...data.labels,
},
annotations: {
'openshift.io/display-name': data.name.trim(),
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/k8sTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export enum KnownLabels {
DATA_CONNECTION_AWS = 'opendatahub.io/managed',
LABEL_SELECTOR_MODEL_REGISTRY = 'component=model-registry',
PROJECT_SUBJECT = 'opendatahub.io/rb-project-subject',
REGISTERED_MODEL_ID = 'modelregistry.opendatahub.io/registered-model-id',
MODEL_VERSION_ID = 'modelregistry.opendatahub.io/model-version-id',
}

export type K8sVerb =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import useModelVersionById from '~/concepts/modelRegistry/apiHooks/useModelVersi
import { ModelRegistrySelectorContext } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext';
import { modelVersionUrl, registeredModelUrl } from '~/pages/modelRegistry/screens/routeUtils';
import useRegisteredModelById from '~/concepts/modelRegistry/apiHooks/useRegisteredModelById';
import useInferenceServices from '~/pages/modelServing/useInferenceServices';
import useServingRuntimes from '~/pages/modelServing/useServingRuntimes';
import { useMakeFetchObject } from '~/utilities/useMakeFetchObject';
import { ModelVersionDetailsTab } from './const';
import ModelVersionsDetailsHeaderActions from './ModelVersionDetailsHeaderActions';
import ModelVersionDetailsTabs from './ModelVersionDetailsTabs';
Expand All @@ -26,7 +29,17 @@ const ModelVersionsDetails: React.FC<ModelVersionsDetailProps> = ({ tab, ...page

const { modelVersionId: mvId, registeredModelId: rmId } = useParams();
const [rm] = useRegisteredModelById(rmId);
const [mv, mvLoaded, mvLoadError, refresh] = useModelVersionById(mvId);
const [mv, mvLoaded, mvLoadError, refreshModelVersion] = useModelVersionById(mvId);
const inferenceServices = useMakeFetchObject(
useInferenceServices(undefined, mv?.registeredModelId, mv?.id),
);
const servingRuntimes = useMakeFetchObject(useServingRuntimes());

const refresh = React.useCallback(() => {
refreshModelVersion();
inferenceServices.refresh();
servingRuntimes.refresh();
}, [inferenceServices, servingRuntimes, refreshModelVersion]);

return (
<ApplicationsPage
Expand Down Expand Up @@ -72,7 +85,7 @@ const ModelVersionsDetails: React.FC<ModelVersionsDetailProps> = ({ tab, ...page
/>
</FlexItem>
<FlexItem>
<ModelVersionsDetailsHeaderActions mv={mv} />
<ModelVersionsDetailsHeaderActions mv={mv} refresh={refresh} />
</FlexItem>
</Flex>
)
Expand All @@ -82,7 +95,15 @@ const ModelVersionsDetails: React.FC<ModelVersionsDetailProps> = ({ tab, ...page
loaded={mvLoaded}
provideChildrenPadding
>
{mv !== null && <ModelVersionDetailsTabs tab={tab} modelVersion={mv} refresh={refresh} />}
{mv !== null && (
<ModelVersionDetailsTabs
tab={tab}
modelVersion={mv}
inferenceServices={inferenceServices}
servingRuntimes={servingRuntimes}
refresh={refresh}
/>
)}
</ApplicationsPage>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@ import { ModelRegistryContext } from '~/concepts/modelRegistry/context/ModelRegi
import { ModelVersion, ModelState } from '~/concepts/modelRegistry/types';
import { getPatchBodyForModelVersion } from '~/pages/modelRegistry/screens/utils';
import { ModelRegistrySelectorContext } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext';
import { modelVersionArchiveDetailsUrl } from '~/pages/modelRegistry/screens/routeUtils';
import {
modelVersionArchiveDetailsUrl,
modelVersionDeploymentsUrl,
} from '~/pages/modelRegistry/screens/routeUtils';
import DeployRegisteredModelModal from '~/pages/modelRegistry/screens/components/DeployRegisteredModelModal';

interface ModelVersionsDetailsHeaderActionsProps {
mv: ModelVersion;
refresh: () => void;
}

const ModelVersionsDetailsHeaderActions: React.FC<ModelVersionsDetailsHeaderActionsProps> = ({
mv,
refresh,
}) => {
const { apiState } = React.useContext(ModelRegistryContext);
const { preferredModelRegistry } = React.useContext(ModelRegistrySelectorContext);
Expand Down Expand Up @@ -67,6 +72,16 @@ const ModelVersionsDetailsHeaderActions: React.FC<ModelVersionsDetailsHeaderActi
</DropdownList>
</Dropdown>
<DeployRegisteredModelModal
onSubmit={() => {
refresh();
navigate(
modelVersionDeploymentsUrl(
mv.id,
mv.registeredModelId,
preferredModelRegistry?.metadata.name,
),
);
}}
onCancel={() => setIsDeployModalOpen(false)}
isOpen={isDeployModalOpen}
modelVersion={mv}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@ import { useNavigate } from 'react-router-dom';
import { PageSection, Tab, Tabs, TabTitleText } from '@patternfly/react-core';
import '~/pages/pipelines/global/runs/GlobalPipelineRunsTabs.scss';
import { ModelVersion } from '~/concepts/modelRegistry/types';
import { InferenceServiceKind, ServingRuntimeKind } from '~/k8sTypes';
import { FetchStateObject } from '~/types';
import { ModelVersionDetailsTabTitle, ModelVersionDetailsTab } from './const';
import ModelVersionDetailsView from './ModelVersionDetailsView';
import ModelVersionRegisteredDeploymentsView from './ModelVersionRegisteredDeploymentsView';

type ModelVersionDetailTabsProps = {
tab: ModelVersionDetailsTab;
modelVersion: ModelVersion;
inferenceServices: FetchStateObject<InferenceServiceKind[]>;
servingRuntimes: FetchStateObject<ServingRuntimeKind[]>;
refresh: () => void;
};

const ModelVersionDetailsTabs: React.FC<ModelVersionDetailTabsProps> = ({
tab,
modelVersion: mv,
inferenceServices,
servingRuntimes,
refresh,
}) => {
const navigate = useNavigate();
Expand Down Expand Up @@ -44,7 +50,10 @@ const ModelVersionDetailsTabs: React.FC<ModelVersionDetailTabsProps> = ({
data-testid="deployments-tab"
>
<PageSection isFilled variant="light" data-testid="deployments-tab-content">
<ModelVersionRegisteredDeploymentsView modelVersion={mv} />
<ModelVersionRegisteredDeploymentsView
inferenceServices={inferenceServices}
servingRuntimes={servingRuntimes}
/>
</PageSection>
</Tab>
</Tabs>
Expand Down
Loading

0 comments on commit 1391418

Please sign in to comment.