Skip to content

Commit

Permalink
API integration for Register Model form (opendatahub-io#3065)
Browse files Browse the repository at this point in the history
* Implement registerModel form submission (WIP)

Signed-off-by: Mike Turley <[email protected]>

* Construct URI for object storage mode

Signed-off-by: Mike Turley <[email protected]>

* Fix page layout and microcopy, and add sourceModelFormatVersion field

Signed-off-by: Mike Turley <[email protected]>

* Move uri utils and add unit tests for them

Signed-off-by: Mike Turley <[email protected]>

* Cypress test for register button

Signed-off-by: Mike Turley <[email protected]>

* Stub for register model page cypress tests

Signed-off-by: Mike Turley <[email protected]>

* Add full cypress tests for submitting register form

Signed-off-by: Mike Turley <[email protected]>

* Fix TODO

Signed-off-by: Mike Turley <[email protected]>

* Fix imports

Signed-off-by: Mike Turley <[email protected]>

* Fix tests

Signed-off-by: Mike Turley <[email protected]>

* Sanitize inputs in uri utils, fix breadcrumb bar

Signed-off-by: Mike Turley <[email protected]>

* Fix tests

Signed-off-by: Mike Turley <[email protected]>

* Fix another test

Signed-off-by: Mike Turley <[email protected]>

---------

Signed-off-by: Mike Turley <[email protected]>
  • Loading branch information
mturley authored Aug 7, 2024
1 parent 7ef421f commit 457d01d
Show file tree
Hide file tree
Showing 14 changed files with 1,030 additions and 294 deletions.
4 changes: 4 additions & 0 deletions frontend/src/__tests__/cypress/cypress/pages/modelRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ class ModelRegistry {
findModelVersionsTableFilter() {
return cy.findByTestId('model-versions-table-filter');
}

findRegisterModelButton() {
return cy.findByRole('button', { name: 'Register model' });
}
}

export const modelRegistry = new ModelRegistry();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export enum FormFieldSelector {
MODEL_NAME = '#model-name',
MODEL_DESCRIPTION = '#model-description',
VERSION_NAME = '#version-name',
VERSION_DESCRIPTION = '#version-description',
SOURCE_MODEL_FORMAT = '#source-model-format',
SOURCE_MODEL_FORMAT_VERSION = '#source-model-format-version',
LOCATION_TYPE_OBJECT_STORAGE = '#location-type-object-storage',
LOCATION_ENDPOINT = '#location-endpoint',
LOCATION_BUCKET = '#location-bucket',
LOCATION_REGION = '#location-region',
LOCATION_PATH = '#location-path',
LOCATION_TYPE_URI = '#location-type-uri',
LOCATION_URI = '#location-uri',
}

class RegisterModelPage {
visit() {
const preferredModelRegistry = 'modelregistry-sample';
cy.visitWithLogin(`/modelRegistry/${preferredModelRegistry}/registerModel`);
this.wait();
}

private wait() {
const preferredModelRegistry = 'modelregistry-sample';
cy.findByTestId('app-page-title').should('exist');
cy.findByTestId('app-page-title').contains('Register model');
cy.findByText(`Model registry - ${preferredModelRegistry}`).should('exist');
cy.testA11y();
}

findFormField(selector: FormFieldSelector) {
return cy.get(selector);
}

findSubmitButton() {
return cy.findByTestId('create-button');
}
}

export const registerModelPage = new RegisterModelPage();
16 changes: 16 additions & 0 deletions frontend/src/__tests__/cypress/cypress/support/commands/odh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { K8sResourceListResult } from '@openshift/dynamic-plugin-sdk-utils'
import type { GenericStaticResponse, RouteHandlerController } from 'cypress/types/net-stubbing';
import type { BaseMetricCreationResponse, BaseMetricListResponse } from '~/api';
import type {
ModelArtifact,
ModelArtifactList,
ModelVersion,
ModelVersionList,
Expand Down Expand Up @@ -294,11 +295,21 @@ declare global {
options: { path: { serviceName: string; apiVersion: string } },
response: OdhResponse<RegisteredModelList>,
) => Cypress.Chainable<null>) &
((
type: 'POST /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/registered_models',
options: { path: { serviceName: string; apiVersion: string } },
response: OdhResponse<RegisteredModel>,
) => Cypress.Chainable<null>) &
((
type: 'GET /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/registered_models/:registeredModelId/versions',
options: { path: { serviceName: string; apiVersion: string; registeredModelId: number } },
response: OdhResponse<ModelVersionList>,
) => Cypress.Chainable<null>) &
((
type: 'POST /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/registered_models/:registeredModelId/versions',
options: { path: { serviceName: string; apiVersion: string; registeredModelId: number } },
response: OdhResponse<ModelVersion>,
) => Cypress.Chainable<null>) &
((
type: 'GET /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/registered_models/:registeredModelId',
options: { path: { serviceName: string; apiVersion: string; registeredModelId: number } },
Expand All @@ -321,6 +332,11 @@ declare global {
options: { path: { serviceName: string; apiVersion: string; modelVersionId: number } },
response: OdhResponse<ModelArtifactList>,
) => Cypress.Chainable<null>) &
((
type: 'POST /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/model_versions/:modelVersionId/artifacts',
options: { path: { serviceName: string; apiVersion: string; modelVersionId: number } },
response: OdhResponse<ModelArtifact>,
) => Cypress.Chainable<null>) &
((
type: 'PATCH /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/model_versions/:modelVersionId',
options: { path: { serviceName: string; apiVersion: string; modelVersionId: number } },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const initIntercepts = ({
);
};

describe('Model Registry', () => {
describe('Model Registry core', () => {
it('Model Registry Disabled in the cluster', () => {
initIntercepts({
disableModelRegistryFeature: true,
Expand Down Expand Up @@ -134,57 +134,81 @@ describe('Model Registry', () => {
modelRegistry.shouldregisteredModelsEmpty();
});

it('Registered model table', () => {
initIntercepts({
disableModelRegistryFeature: false,
describe('Registered model table', () => {
beforeEach(() => {
initIntercepts({ disableModelRegistryFeature: false });
modelRegistry.visit();
});

it('Renders row contents', () => {
const registeredModelRow = modelRegistry.getRow('Fraud detection model');
registeredModelRow.findName().contains('Fraud detection model');
registeredModelRow
.findDescription()
.contains(
'A machine learning model trained to detect fraudulent transactions in financial data',
);
registeredModelRow.findOwner().contains('Author 1');

// Label popover
registeredModelRow.findLabelPopoverText().contains('2 more');
registeredModelRow.findLabelPopoverText().click();
registeredModelRow.shouldContainsPopoverLabels([
'Machine learning',
'Next data to be overflow',
]);
});

it('Renders labels in modal', () => {
const registeredModelRow2 = modelRegistry.getRow('Label modal');
registeredModelRow2.findLabelModalText().contains('6 more');
registeredModelRow2.findLabelModalText().click();
labelModal.shouldContainsModalLabels([
'Testing label',
'Financial',
'Financial data',
'Fraud detection',
'Machine learning',
'Next data to be overflow',
'Label x',
'Label y',
'Label z',
]);
labelModal.findModalSearchInput().type('Financial');
labelModal.shouldContainsModalLabels(['Financial', 'Financial data']);
labelModal.findCloseModal().click();
});

it('Sorts by model name', () => {
modelRegistry.findRegisteredModelTableHeaderButton('Model name').should(be.sortAscending);
modelRegistry.findRegisteredModelTableHeaderButton('Model name').click();
modelRegistry.findRegisteredModelTableHeaderButton('Model name').should(be.sortDescending);
});

it('Filters by keyword', () => {
modelRegistry.findTableSearch().type('Fraud detection model');
modelRegistry.findTableRows().should('have.length', 1);
modelRegistry.findTableRows().contains('Fraud detection model');
});
});
});

describe('Register Model button', () => {
it('Navigates to register page from empty state', () => {
initIntercepts({ disableModelRegistryFeature: false, registeredModels: [] });
modelRegistry.visit();
modelRegistry.findRegisterModelButton().click();
cy.findByTestId('app-page-title').should('exist');
cy.findByTestId('app-page-title').contains('Register model');
cy.findByText('Model registry - modelregistry-sample').should('exist');
});

const registeredModelRow = modelRegistry.getRow('Fraud detection model');
registeredModelRow.findName().contains('Fraud detection model');
registeredModelRow
.findDescription()
.contains(
'A machine learning model trained to detect fraudulent transactions in financial data',
);
registeredModelRow.findOwner().contains('Author 1');

// Label popover
registeredModelRow.findLabelPopoverText().contains('2 more');
registeredModelRow.findLabelPopoverText().click();
registeredModelRow.shouldContainsPopoverLabels([
'Machine learning',
'Next data to be overflow',
]);

// Label modal
const registeredModelRow2 = modelRegistry.getRow('Label modal');
registeredModelRow2.findLabelModalText().contains('6 more');
registeredModelRow2.findLabelModalText().click();
labelModal.shouldContainsModalLabels([
'Testing label',
'Financial',
'Financial data',
'Fraud detection',
'Machine learning',
'Next data to be overflow',
'Label x',
'Label y',
'Label z',
]);
labelModal.findModalSearchInput().type('Financial');
labelModal.shouldContainsModalLabels(['Financial', 'Financial data']);
labelModal.findCloseModal().click();

// sort by modelName
modelRegistry.findRegisteredModelTableHeaderButton('Model name').should(be.sortAscending);
modelRegistry.findRegisteredModelTableHeaderButton('Model name').click();
modelRegistry.findRegisteredModelTableHeaderButton('Model name').should(be.sortDescending);

// filtering by keyword
modelRegistry.findTableSearch().type('Fraud detection model');
modelRegistry.findTableRows().should('have.length', 1);
modelRegistry.findTableRows().contains('Fraud detection model');
it('Navigates to register page from table toolbar', () => {
initIntercepts({ disableModelRegistryFeature: false });
modelRegistry.visit();
modelRegistry.findRegisterModelButton().click();
cy.findByTestId('app-page-title').should('exist');
cy.findByTestId('app-page-title').contains('Register model');
cy.findByText('Model registry - modelregistry-sample').should('exist');
});
});
Loading

0 comments on commit 457d01d

Please sign in to comment.