Skip to content

Commit

Permalink
Archive and restore registered models
Browse files Browse the repository at this point in the history
  • Loading branch information
manaswinidas committed May 30, 2024
1 parent f336007 commit 412d440
Show file tree
Hide file tree
Showing 33 changed files with 1,175 additions and 106 deletions.
6 changes: 3 additions & 3 deletions frontend/src/__mocks__/mockModelVersion.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ModelVersion, ModelVersionState } from '~/concepts/modelRegistry/types';
import { ModelVersion, ModelState } from '~/concepts/modelRegistry/types';
import { createModelRegistryLabelsObject } from './utils';

type MockModelVersionType = {
Expand All @@ -7,7 +7,7 @@ type MockModelVersionType = {
registeredModelId?: string;
name?: string;
labels?: string[];
state?: ModelVersionState;
state?: ModelState;
description?: string;
};

Expand All @@ -17,7 +17,7 @@ export const mockModelVersion = ({
name = 'new model version',
labels = [],
id = '1',
state = ModelVersionState.LIVE,
state = ModelState.LIVE,
description = 'Description of model version',
}: MockModelVersionType): ModelVersion => ({
author,
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/__mocks__/mockRegisteredModel.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { RegisteredModel, RegisteredModelState } from '~/concepts/modelRegistry/types';
import { RegisteredModel, ModelState } from '~/concepts/modelRegistry/types';
import { createModelRegistryLabelsObject } from './utils';

type MockRegisteredModelType = {
id?: string;
name?: string;
state?: RegisteredModelState;
state?: ModelState;
description?: string;
labels?: string[];
};

export const mockRegisteredModel = ({
name = 'test',
state = RegisteredModelState.LIVE,
state = ModelState.LIVE,
description = '',
labels = [],
id = '1',
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/__mocks__/mockRegisteredModelsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { mockRegisteredModel } from './mockRegisteredModel';

export const mockRegisteredModelList = ({
size = 5,
}: Partial<RegisteredModelList>): RegisteredModelList => ({
items: [
items = [
mockRegisteredModel({ name: 'test-1' }),
mockRegisteredModel({ name: 'test-2' }),
mockRegisteredModel({
Expand Down Expand Up @@ -46,6 +45,8 @@ export const mockRegisteredModelList = ({
],
}),
],
}: Partial<RegisteredModelList>): RegisteredModelList => ({
items,
nextPageToken: '',
pageSize: 0,
size,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { mockRegisteredModelList } from '~/__mocks__/mockRegisteredModelsList';
import { ModelRegistryModel } from '~/__tests__/cypress/cypress/utils/models';
import { mockModelVersionList } from '~/__mocks__/mockModelVersionList';
import { mockModelVersion } from '~/__mocks__/mockModelVersion';
import { ModelVersion, ModelVersionState } from '~/concepts/modelRegistry/types';
import { ModelVersion, ModelState } from '~/concepts/modelRegistry/types';
import { mockRegisteredModel } from '~/__mocks__/mockRegisteredModel';
import { verifyRelativeURL } from '~/__tests__/cypress/cypress/utils/url';
import {
Expand Down Expand Up @@ -39,9 +39,9 @@ const initIntercepts = ({
'Test label y',
'Test label z',
],
state: ModelVersionState.ARCHIVED,
state: ModelState.ARCHIVED,
}),
mockModelVersion({ id: '2', name: 'model version 2', state: ModelVersionState.ARCHIVED }),
mockModelVersion({ id: '2', name: 'model version 2', state: ModelState.ARCHIVED }),
mockModelVersion({ id: '3', name: 'model version 3' }),
],
}: HandlersProps) => {
Expand Down Expand Up @@ -100,7 +100,7 @@ const initIntercepts = ({
modelVersionId: 2,
},
},
mockModelVersion({ id: '2', name: 'model version 2', state: ModelVersionState.ARCHIVED }),
mockModelVersion({ id: '2', name: 'model version 2', state: ModelState.ARCHIVED }),
);

cy.interceptOdh(
Expand All @@ -112,7 +112,7 @@ const initIntercepts = ({
modelVersionId: 3,
},
},
mockModelVersion({ id: '3', name: 'model version 3', state: ModelVersionState.LIVE }),
mockModelVersion({ id: '3', name: 'model version 3', state: ModelState.LIVE }),
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
/* eslint-disable camelcase */
import { mockK8sResourceList } from '~/__mocks__';
import { mockDashboardConfig } from '~/__mocks__/mockDashboardConfig';
import { MODEL_REGISTRY_API_VERSION } from '~/concepts/modelRegistry/const';
import { mockModelRegistry } from '~/__mocks__/mockModelRegistry';
import { mockRegisteredModelList } from '~/__mocks__/mockRegisteredModelsList';
import { ModelRegistryModel } from '~/__tests__/cypress/cypress/utils/models';
import { mockModelVersion } from '~/__mocks__/mockModelVersion';
import { ModelState, ModelVersion, RegisteredModel } from '~/concepts/modelRegistry/types';
import { mockRegisteredModel } from '~/__mocks__/mockRegisteredModel';
import { verifyRelativeURL } from '~/__tests__/cypress/cypress/utils/url';
import { labelModal, modelRegistry } from '~/__tests__/cypress/cypress/pages/modelRegistry';
import {
archiveModelModal,
registeredModelArchive,
restoreModelModal,
} from '~/__tests__/cypress/cypress/pages/modelRegistry/registeredModelArchive';
import { mockModelVersionList } from '~/__mocks__/mockModelVersionList';

type HandlersProps = {
registeredModelsSize?: number;
registeredModels?: RegisteredModel[];
modelVersions?: ModelVersion[];
};

const initIntercepts = ({
registeredModelsSize = 4,
registeredModels = [
mockRegisteredModel({
name: 'model 1',
id: '1',
labels: [
'Financial data',
'Fraud detection',
'Test label',
'Machine learning',
'Next data to be overflow',
'Test label x',
'Test label y',
'Test label z',
],
state: ModelState.ARCHIVED,
}),
mockRegisteredModel({ id: '2', name: 'model 2', state: ModelState.ARCHIVED }),
mockRegisteredModel({ id: '3', name: 'model 3' }),
mockRegisteredModel({ id: '4', name: 'model 4' }),
],
modelVersions = [
mockModelVersion({ author: 'Author 1' }),
mockModelVersion({ name: 'model version' }),
],
}: HandlersProps) => {
cy.interceptOdh(
'GET /api/config',
mockDashboardConfig({
disableModelRegistry: false,
}),
);

cy.interceptK8sList(
ModelRegistryModel,
mockK8sResourceList([mockModelRegistry({}), mockModelRegistry({ name: 'test-registry' })]),
);

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

cy.interceptOdh(
`GET /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/registered_models`,
{
path: { serviceName: 'modelregistry-sample', apiVersion: MODEL_REGISTRY_API_VERSION },
},
mockRegisteredModelList({ size: registeredModelsSize }),
);

cy.interceptOdh(
`GET /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/registered_models`,
{
path: { serviceName: 'modelregistry-sample', apiVersion: MODEL_REGISTRY_API_VERSION },
},
mockRegisteredModelList({ items: registeredModels }),
);

cy.interceptOdh(
`GET /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/registered_models/:registeredModelId/versions`,
{
path: {
serviceName: 'modelregistry-sample',
apiVersion: MODEL_REGISTRY_API_VERSION,
registeredModelId: 1,
},
},
mockModelVersionList({ items: modelVersions }),
);

cy.interceptOdh(
'GET /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/registered_models/:registeredModelId',
{
path: {
serviceName: 'modelregistry-sample',
apiVersion: MODEL_REGISTRY_API_VERSION,
registeredModelId: 2,
},
},
mockRegisteredModel({ id: '2', name: 'model 2', state: ModelState.ARCHIVED }),
);
};

describe('Model archive list', () => {
it('No archive models in the selected model registry', () => {
initIntercepts({
registeredModels: [],
});
registeredModelArchive.visitModelList();
verifyRelativeURL('/modelRegistry/modelregistry-sample');
registeredModelArchive.findTableKebabMenu().findDropdownItem('View archived models').click();
registeredModelArchive.shouldArchiveVersionsEmpty();
});

it('Archive models list', () => {
initIntercepts({});
registeredModelArchive.visit();
verifyRelativeURL('/modelRegistry/modelregistry-sample/registeredModels/archive');

//breadcrumb
registeredModelArchive.findArchiveModelBreadcrumbItem().contains('Archived models');

// name, last modified, owner, labels modal
registeredModelArchive.findArchiveModelTable().should('be.visible');
registeredModelArchive.findArchiveModelsTableRows().should('have.length', 2);

const archiveModelRow = registeredModelArchive.getRow('model 1');

archiveModelRow.findLabelModalText().contains('5 more');
archiveModelRow.findLabelModalText().click();
labelModal.shouldContainsModalLabels([
'Financial',
'Financial data',
'Fraud detection',
'Test label',
'Machine learning',
'Next data to be overflow',
'Test label x',
'Test label y',
'Test label y',
]);
labelModal.findCloseModal().click();
});
});

describe('Restoring archive model', () => {
it('Restore from archive models table', () => {
cy.interceptOdh(
'PATCH /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/registered_models/:registeredModelId',
{
path: {
serviceName: 'modelregistry-sample',
apiVersion: MODEL_REGISTRY_API_VERSION,
registeredModelId: 2,
},
},
mockRegisteredModel({ id: '2', name: 'model 2', state: ModelState.LIVE }),
).as('modelRestored');

initIntercepts({});
registeredModelArchive.visit();

// Bypass patternfly ExpandableSection error https://github.com/patternfly/patternfly-react/issues/10410
// Cannot destructure property 'offsetWidth' of 'this.expandableContentRef.current' as it is null.
Cypress.on('uncaught:exception', () => false);

const archiveModelRow = registeredModelArchive.getRow('model 2');
archiveModelRow.findKebabAction('Restore model').click();

restoreModelModal.findRestoreButton().click();

cy.wait('@modelRestored').then((interception) => {
expect(interception.request.body).to.eql({
customProperties: {},
description: '',
externalID: '1234132asdfasdf',
state: 'LIVE',
});
});
});

it('Restore from archive model details', () => {
cy.interceptOdh(
'PATCH /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/registered_models/:registeredModelId',
{
path: {
serviceName: 'modelregistry-sample',
apiVersion: MODEL_REGISTRY_API_VERSION,
registeredModelId: 2,
},
},
mockRegisteredModel({ id: '2', name: 'model 2', state: ModelState.LIVE }),
).as('modelRestored');

initIntercepts({});
registeredModelArchive.visitArchiveModelDetail();

registeredModelArchive.findRestoreButton().click();
restoreModelModal.findRestoreButton().click();

cy.wait('@modelRestored').then((interception) => {
expect(interception.request.body).to.eql({
customProperties: {},
description: '',
externalID: '1234132asdfasdf',
state: 'LIVE',
});
});
});
});

describe('Archiving model', () => {
it('Archive model from registered models table', () => {
cy.interceptOdh(
'PATCH /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/registered_models/:registeredModelId',
{
path: {
serviceName: 'modelregistry-sample',
apiVersion: MODEL_REGISTRY_API_VERSION,
registeredModelId: 3,
},
},
mockRegisteredModel({ id: '3', name: 'model 3', state: ModelState.ARCHIVED }),
).as('modelArchived');

initIntercepts({});
registeredModelArchive.visitModelList();

const modelRow = modelRegistry.getRow('model 3');
modelRow.findKebabAction('Archive model').click();
archiveModelModal.findArchiveButton().should('be.disabled');
archiveModelModal.findModalTextInput().fill('model 3');
archiveModelModal.findArchiveButton().should('be.enabled').click();
cy.wait('@modelArchived').then((interception) => {
expect(interception.request.body).to.eql({
customProperties: {},
description: '',
externalID: '1234132asdfasdf',
state: 'ARCHIVED',
});
});
});

it('Archive model from model details', () => {
cy.interceptOdh(
'PATCH /api/service/modelregistry/:serviceName/api/model_registry/:apiVersion/registered_models/:registeredModelId',
{
path: {
serviceName: 'modelregistry-sample',
apiVersion: MODEL_REGISTRY_API_VERSION,
registeredModelId: 2,
},
},
mockRegisteredModel({ id: '2', name: 'model 2', state: ModelState.ARCHIVED }),
).as('modelArchived');

initIntercepts({});
registeredModelArchive.visitModelDetails();
registeredModelArchive
.findModelVersionsDetailsHeaderAction()
.findDropdownItem('Archive model')
.click();

archiveModelModal.findArchiveButton().should('be.disabled');
archiveModelModal.findModalTextInput().fill('model 2');
archiveModelModal.findArchiveButton().should('be.enabled').click();
cy.wait('@modelArchived').then((interception) => {
expect(interception.request.body).to.eql({
customProperties: {},
description: '',
externalID: '1234132asdfasdf',
state: 'ARCHIVED',
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,6 @@ class ModelVersionArchive {
this.wait();
}

findTableKebabMenu() {
return cy.findByTestId('model-versions-table-kebab-action');
}

findModelVersionsTableKebab() {
return cy.findByTestId('model-versions-table-kebab-action');
}
Expand Down
Loading

0 comments on commit 412d440

Please sign in to comment.