Skip to content

Commit

Permalink
Model version details test
Browse files Browse the repository at this point in the history
Refactoring model registry folder structure
  • Loading branch information
dpanshug committed May 9, 2024
1 parent 070fe7e commit b817bcc
Show file tree
Hide file tree
Showing 33 changed files with 324 additions and 54 deletions.
13 changes: 13 additions & 0 deletions frontend/src/__mocks__/mockModelArtifact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ModelArtifact } from '~/concepts/modelRegistry/types';

export const mockModelArtifact = (): ModelArtifact => ({
createTimeSinceEpoch: '1712234877179',
id: '1',
lastUpdateTimeSinceEpoch: '1712234877179',
name: 'fraud detection model version 1',
description: 'Description of model version',
artifactType: 'model-artifact',
customProperties: {},
storagePath: 'test path',
uri: 'https://huggingface.io/mnist.onnx',
});
10 changes: 10 additions & 0 deletions frontend/src/__mocks__/mockModelArtifactList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* eslint-disable camelcase */
import { ModelArtifactList } from '~/concepts/modelRegistry/types';
import { mockModelArtifact } from './mockModelArtifact';

export const mockModelArtifactList = (): ModelArtifactList => ({
items: [mockModelArtifact()],
nextPageToken: '',
pageSize: 0,
size: 1,
});
54 changes: 51 additions & 3 deletions frontend/src/__mocks__/mockModelVersion.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,68 @@
/* eslint-disable camelcase */
import { ModelVersion, ModelVersionState } from '~/concepts/modelRegistry/types';

type MockModelVersionType = {
name?: string;
author?: string;
registeredModelId?: string;
id?: string;
};

export const mockModelVersion = ({
name = 'Version 1',
author = 'Test author',
registeredModelId = '1',
id = '1',
}: MockModelVersionType): ModelVersion => ({
author,
createTimeSinceEpoch: '1712234877179',
customProperties: {},
id: '26',
customProperties: {
'Testing label': {
metadataType: 'MetadataStringValue',
string_value: '',
},
Financial: {
metadataType: 'MetadataStringValue',
string_value: 'non-empty',
},
'Financial data': {
metadataType: 'MetadataStringValue',
string_value: '',
},
'Fraud detection': {
metadataType: 'MetadataStringValue',
string_value: '',
},
'Long label data to be truncated abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc':
{
metadataType: 'MetadataStringValue',
string_value: '',
},
'Machine learning': {
metadataType: 'MetadataStringValue',
string_value: '',
},
'Next data to be overflow': {
metadataType: 'MetadataStringValue',
string_value: '',
},
'Label x': {
metadataType: 'MetadataStringValue',
string_value: '',
},
'Label y': {
metadataType: 'MetadataStringValue',
string_value: '',
},
'Label z': {
metadataType: 'MetadataStringValue',
string_value: '',
},
},
id,
lastUpdateTimeSinceEpoch: '1712234877179',
name: 'fraud detection model version 1',
name,
state: ModelVersionState.ARCHIVED,
registeredModelId,
description: 'Description of model version',
});
7 changes: 5 additions & 2 deletions frontend/src/__mocks__/mockModelVersionList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { ModelVersionList } from '~/concepts/modelRegistry/types';
import { mockModelVersion } from './mockModelVersion';

export const mockModelVersionList = (): ModelVersionList => ({
items: [mockModelVersion({ author: 'Author 1', registeredModelId: '1' })],
items: [
mockModelVersion({ author: 'Author 1', registeredModelId: '1' }),
mockModelVersion({ author: 'Author 2', registeredModelId: '1', id: '2', name: 'Version 2' }),
],
nextPageToken: '',
pageSize: 0,
size: 1,
size: 2,
});
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ describe('Model Registry', () => {

modelRegistry.tabEnabled();
});

it('No registered models in the selected Model Registry', () => {
initIntercepts({
disableModelRegistryFeature: false,
Expand All @@ -80,7 +81,7 @@ describe('Model Registry', () => {
modelRegistry.shouldregisteredModelsEmpty();
});

it('Model registry table', () => {
it('Registered model table', () => {
initIntercepts({
disableModelRegistryFeature: false,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
mockDashboardConfig,
mockK8sResourceList,
mockRouteK8sResourceModelRegistry,
} from '~/__mocks__';
import { mockComponents } from '~/__mocks__/mockComponents';
import { mockModelRegistry } from '~/__mocks__/mockModelRegistry';
import { ModelRegistryModel, RouteModel } from '~/__tests__/cypress/cypress/utils/models';
import { MODEL_REGISTRY_API_VERSION } from '~/concepts/modelRegistry/const';
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 { verifyRelativeURL } from '~/__tests__/cypress/cypress/utils/url';

const initIntercepts = () => {
cy.interceptOdh(
'GET /api/config',
mockDashboardConfig({
disableModelRegistry: false,
}),
);
cy.interceptOdh('GET /api/components', { query: { installed: 'true' } }, mockComponents());

cy.interceptK8sList(ModelRegistryModel, mockK8sResourceList([mockModelRegistry({})]));

cy.interceptK8s(ModelRegistryModel, mockModelRegistry({}));

cy.interceptK8s(
RouteModel,
mockRouteK8sResourceModelRegistry({
name: 'modelregistry-sample-http',
namespace: 'odh-model-registries',
}),
);

cy.interceptOdh(
`GET /api/service/modelregistry/modelregistry-sample/api/model_registry/${MODEL_REGISTRY_API_VERSION}/registered_models/1`,
mockRegisteredModel({}),
);

cy.interceptOdh(
`GET /api/service/modelregistry/modelregistry-sample/api/model_registry/${MODEL_REGISTRY_API_VERSION}/registered_models/1/versions`,
mockModelVersionList(),
);

cy.interceptOdh(
`GET /api/service/modelregistry/modelregistry-sample/api/model_registry/${MODEL_REGISTRY_API_VERSION}/model_versions/:id`,
{ path: { id: '1' } },
mockModelVersion({}),
);

cy.interceptOdh(
`GET /api/service/modelregistry/modelregistry-sample/api/model_registry/${MODEL_REGISTRY_API_VERSION}/model_versions/:id`,
{ path: { id: '2' } },
mockModelVersion({ id: '2', name: 'Version 2' }),
);

cy.interceptOdh(
`GET /api/service/modelregistry/modelregistry-sample/api/model_registry/${MODEL_REGISTRY_API_VERSION}/model_versions/1/artifacts`,
mockModelArtifactList(),
);
};

describe('Model version details', () => {
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 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('https://huggingface.io/mnist.onnx');
});

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');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { modelVersionUrl } from '~/pages/modelRegistry/screens/routeUtils';

class ModelVersionDetails {
visit() {
cy.visit(modelVersionUrl('1', '1', 'modelregistry-sample'));
this.wait();
}

private wait() {
cy.findByTestId('app-page-title').should('exist');
cy.testA11y();
}

findVersionId() {
return cy.findByTestId('model-version-id');
}

findDescription() {
return cy.findByTestId('model-version-description');
}

findMoreLabelsButton() {
return cy.findByTestId('label-group').find('button');
}

findStorageLocation() {
return cy.findByTestId('storage-location');
}

shouldContainsModalLabels(labels: string[]) {
cy.findByTestId('label-group').within(() => labels.map((label) => cy.contains(label)));
return this;
}

findModelVersionDropdownButton() {
return cy.findByTestId('model-version-toggle-button');
}

findModelVersionDropdownSearch() {
return cy.findByTestId('search-input');
}

findModelVersionDropdownItem(name: string) {
return cy.findByTestId('model-version-selector-list').find('li').contains(name);
}
}

export const modelVersionDetails = new ModelVersionDetails();
30 changes: 26 additions & 4 deletions frontend/src/__tests__/cypress/cypress/support/commands/odh.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { K8sResourceListResult, K8sStatus } from '@openshift/dynamic-plugin-sdk-utils';
import type { GenericStaticResponse, RouteHandlerController } from 'cypress/types/net-stubbing';
import { BaseMetricCreationResponse, BaseMetricListResponse } from '~/api';
import { ModelVersionList, RegisteredModelList } from '~/concepts/modelRegistry/types';
import {
ModelArtifactList,
ModelVersion,
ModelVersionList,
RegisteredModel,
RegisteredModelList,
} from '~/concepts/modelRegistry/types';
import type {
DashboardConfigKind,
DataScienceClusterInitializationKindStatus,
Expand Down Expand Up @@ -316,16 +322,32 @@ declare global {
): Cypress.Chainable<null>;

interceptOdh(
type: 'DELETE /api/service/pipelines/:projectId/dspa/apis/v2beta1/recurringruns/:pipeline_id',
options: { path: { projectId: string; pipeline_id: string } },
response: OdhResponse<K8sStatus>,
type: `GET /api/service/modelregistry/modelregistry-sample/api/model_registry/v1alpha3/registered_models/1`,
response: OdhResponse<RegisteredModel | undefined>,
): Cypress.Chainable<null>;

interceptOdh(
type: `GET /api/service/modelregistry/modelregistry-sample/api/model_registry/v1alpha3/model_versions/:id`,
options: { path: { id: string } },
response: OdhResponse<ModelVersion | undefined>,
): Cypress.Chainable<null>;

interceptOdh(
type: `GET /api/service/modelregistry/modelregistry-sample/api/model_registry/v1alpha3/model_versions/1/artifacts`,
response: OdhResponse<ModelArtifactList | undefined>,
): Cypress.Chainable<null>;

interceptOdh(
type: 'GET /api/service/modelregistry/modelregistry-sample/api/model_registry/v1alpha3/registered_models/1/versions',
response: OdhResponse<ModelVersionList | undefined>,
): Cypress.Chainable<null>;

interceptOdh(
type: 'DELETE /api/service/pipelines/:projectId/dspa/apis/v2beta1/recurringruns/:pipeline_id',
options: { path: { projectId: string; pipeline_id: string } },
response: OdhResponse<K8sStatus>,
): Cypress.Chainable<null>;

interceptOdh(
type: 'GET /api/rolebindings/opendatahub/openshift-ai-notebooks-image-pullers',
response: OdhResponse<K8sResourceListResult<RoleBindingKind>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,9 @@ const EditableLabelsDescriptionListGroup: React.FC<EditableTextDescriptionListGr
setIsEditing(false);
}}
>
<LabelGroup>
<LabelGroup data-testid="label-group">
{labels.map((label) => (
<Label key={label} color="blue" data-testid="label">
<Label textMaxWidth="40ch" key={label} color="blue" data-testid="label">
{label}
</Label>
))}
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/EditableTextDescriptionListGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ type EditableTextDescriptionListGroupProps = Pick<
> & {
value: string;
saveEditedValue: (value: string) => Promise<void>;
testid?: string;
};

const EditableTextDescriptionListGroup: React.FC<EditableTextDescriptionListGroupProps> = ({
title,
contentWhenEmpty,
value,
saveEditedValue,
testid,
}) => {
const [isEditing, setIsEditing] = React.useState(false);
const [unsavedValue, setUnsavedValue] = React.useState(value);
Expand Down Expand Up @@ -58,6 +60,7 @@ const EditableTextDescriptionListGroup: React.FC<EditableTextDescriptionListGrou
}}
>
<ExpandableSection
data-testid={testid}
variant="truncate"
truncateMaxLines={12}
toggleText={isTextExpanded ? 'Show less' : 'Show more'}
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/pages/modelRegistry/ModelRegistryRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ 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';
import { ModelVersionsTab } from './screens/ModelVersions/const';
import ModelVersions from './screens/ModelVersions/ModelVersions';
import ModelVersionsDetails from './screens/ModelVersionDetails/ModelVersionDetails';
import { ModelVersionDetailsTab } from './screens/ModelVersionDetails/const';

Expand All @@ -21,14 +21,14 @@ const ModelRegistryRoutes: React.FC = () => (
>
<Route index element={<ModelRegistry />} />
<Route path="registeredModels/:registeredModelId">
<Route index element={<Navigate to={ModelVersionsTabs.VERSIONS} />} />
<Route index element={<Navigate to={ModelVersionsTab.VERSIONS} />} />
<Route
path={ModelVersionsTabs.VERSIONS}
element={<ModelVersions tab={ModelVersionsTabs.VERSIONS} empty={false} />}
path={ModelVersionsTab.VERSIONS}
element={<ModelVersions tab={ModelVersionsTab.VERSIONS} empty={false} />}
/>
<Route
path={ModelVersionsTabs.DETAILS}
element={<ModelVersions tab={ModelVersionsTabs.DETAILS} empty={false} />}
path={ModelVersionsTab.DETAILS}
element={<ModelVersions tab={ModelVersionsTab.DETAILS} empty={false} />}
/>
<Route path="versions/:modelVersionId">
<Route index element={<Navigate to={ModelVersionDetailsTab.DETAILS} />} />
Expand Down
Loading

0 comments on commit b817bcc

Please sign in to comment.