Skip to content

Commit

Permalink
List connected deployed models for each connection table row (opendat…
Browse files Browse the repository at this point in the history
  • Loading branch information
jeff-phillips-18 authored Oct 2, 2024
1 parent 96beee5 commit 9ab1076
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { mockInferenceServiceK8sResource } from '~/__mocks__';
import { testHook } from '~/__tests__/unit/testUtils/hooks';
import { useInferenceServicesForConnection } from '~/pages/projects/useInferenceServicesForConnection';
import { mockConnection } from '~/__mocks__/mockConnection';

jest.mock('react', () => ({
...jest.requireActual('react'),
useContext: jest.fn(),
}));

const useContextMock = React.useContext as jest.Mock;

const connection = mockConnection({
name: 'connection1',
displayName: 'Connection 1',
description: 'desc1',
});

const mockInferenceServices = [
mockInferenceServiceK8sResource({
name: 'item-1',
displayName: 'Item 1',
secretName: 'connection1',
}),
mockInferenceServiceK8sResource({
name: 'item-2',
displayName: 'Item 2',
secretName: 'connection2',
}),
];

describe('useInferenceServicesForConnection', () => {
beforeEach(() => {
useContextMock.mockReturnValue({ inferenceServices: { data: mockInferenceServices } });
});

it('should return matching inference services', () => {
const renderResult = testHook(useInferenceServicesForConnection)(connection);
expect(renderResult.result.current).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Connection } from '~/concepts/connectionTypes/types';
import { ProjectObjectType } from '~/concepts/design/utils';
import ResourceLabel from '~/pages/projects/screens/detail/connections/ResourceLabel';
import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils';
import { useInferenceServicesForConnection } from '~/pages/projects/useInferenceServicesForConnection';

type Props = {
connection: Connection;
Expand All @@ -18,6 +19,7 @@ const ConnectedResources: React.FC<Props> = ({ connection }) => {
ConnectedNotebookContext.EXISTING_DATA_CONNECTION,
connection.metadata.name,
);
const connectedModels = useInferenceServicesForConnection(connection);

if (!notebooksLoaded) {
return <Spinner size="sm" />;
Expand All @@ -36,6 +38,13 @@ const ConnectedResources: React.FC<Props> = ({ connection }) => {
title={getDisplayNameFromK8sResource(notebook)}
/>
))}
{connectedModels.map((model) => (
<ResourceLabel
key={model.metadata.name}
resourceType={ProjectObjectType.deployedModels}
title={getDisplayNameFromK8sResource(model)}
/>
))}
</LabelGroup>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from '~/pages/projects/notebook/useRelatedNotebooks';
import { useNotebooksStates } from '~/pages/projects/notebook/useNotebooksStates';
import { NotebookKind } from '~/k8sTypes';
import { useInferenceServicesForConnection } from '~/pages/projects/useInferenceServicesForConnection';

type Props = {
namespace: string;
Expand All @@ -35,12 +36,14 @@ export const ConnectionsDeleteModal: React.FC<Props> = ({
}) => {
const [isDeleting, setIsDeleting] = React.useState(false);
const [error, setError] = React.useState<Error>();
const { notebooks: connectedNotebooks, loaded: notebooksLoaded } = useRelatedNotebooks(
const { notebooks: connectedNotebooks, loaded } = useRelatedNotebooks(
ConnectedNotebookContext.EXISTING_DATA_CONNECTION,
deleteConnection.metadata.name,
);
const [notebookStates] = useNotebooksStates(connectedNotebooks, namespace);
const [notebooksExpanded, setNotebooksExpanded] = React.useState<boolean>(false);
const connectedModels = useInferenceServicesForConnection(deleteConnection);
const [modelsExpanded, setModelsExpanded] = React.useState<boolean>(false);

const getNotebookStatusText = React.useCallback(
(notebook: NotebookKind) =>
Expand Down Expand Up @@ -75,44 +78,85 @@ export const ConnectionsDeleteModal: React.FC<Props> = ({
>
The <b>{getDisplayNameFromK8sResource(deleteConnection)}</b> connection will be deleted, and
its dependent resources will stop working.
{notebooksLoaded && !connectedNotebooks.length ? null : (
{loaded && !connectedNotebooks.length && !connectedModels.length ? null : (
<div className="pf-v5-u-mt-md">
{!notebooksLoaded ? (
{!loaded ? (
<Bullseye>
<Spinner size="md" />
</Bullseye>
) : (
<>
{connectedNotebooks.length ? (
<>
<ExpandableSectionToggle
isExpanded={notebooksExpanded}
onToggle={setNotebooksExpanded}
id="expand-connected-notebooks-toggle"
contentId="expanded-connected-notebooks"
data-testid="connections-delete-notebooks-toggle"
>
<span>Workbenches </span>
<Badge isRead data-testid="connections-delete-notebooks-count">
{connectedNotebooks.length}
</Badge>
</ExpandableSectionToggle>
{notebooksExpanded ? (
<ExpandableSection
isExpanded
isDetached
contentId="expanded-connected-notebooks"
toggleId="expand-connected-notebooks-toggle"
>
<TextContent>
<TextList>
{connectedNotebooks.map((notebook) => (
<TextListItem
key={notebook.metadata.name}
data-testid="connections-delete-notebooks-item"
>
{getDisplayNameFromK8sResource(notebook)}
{getNotebookStatusText(notebook)}
</TextListItem>
))}
</TextList>
</TextContent>
</ExpandableSection>
) : null}
</>
) : null}
<ExpandableSectionToggle
isExpanded={notebooksExpanded}
onToggle={setNotebooksExpanded}
contentId="expanded-connected-notebooks"
data-testid="connections-delete-notebooks-toggle"
isExpanded={modelsExpanded}
onToggle={setModelsExpanded}
id="expand-connected-models-toggle"
contentId="expanded-connected-models"
data-testid="connections-delete-models-toggle"
>
<span>Workbenches </span>
<Badge isRead data-testid="connections-delete-notebooks-count">
{connectedNotebooks.length}
<span>Model deployments </span>
<Badge isRead data-testid="connections-delete-models-count">
{connectedModels.length}
</Badge>
</ExpandableSectionToggle>
<ExpandableSection
isExpanded={notebooksExpanded}
isDetached
contentId="expanded-connected-notebooks"
>
<TextContent>
<TextList>
{connectedNotebooks.map((notebook) => (
<TextListItem
key={notebook.metadata.name}
data-testid="connections-delete-notebooks-item"
>
{getDisplayNameFromK8sResource(notebook)}
{getNotebookStatusText(notebook)}
</TextListItem>
))}
</TextList>
</TextContent>
</ExpandableSection>
{modelsExpanded ? (
<ExpandableSection
isExpanded
isDetached
toggleId="expand-connected-models-toggle"
contentId="expanded-connected-models"
>
<TextContent>
<TextList>
{connectedModels.map((model) => (
<TextListItem
key={model.metadata.name}
data-testid="connections-delete-models-item"
>
{getDisplayNameFromK8sResource(model)}
</TextListItem>
))}
</TextList>
</TextContent>
</ExpandableSection>
) : null}
</>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import { fireEvent, render, screen } from '@testing-library/react';
import { ConnectionsDeleteModal } from '~/pages/projects/screens/detail/connections/ConnectionsDeleteModal';
import { mockConnection } from '~/__mocks__/mockConnection';
import { useRelatedNotebooks } from '~/pages/projects/notebook/useRelatedNotebooks';
import { mockNotebookK8sResource, mockNotebookState } from '~/__mocks__';
import {
mockInferenceServiceK8sResource,
mockNotebookK8sResource,
mockNotebookState,
} from '~/__mocks__';
import { useNotebooksStates } from '~/pages/projects/notebook/useNotebooksStates';
import { useInferenceServicesForConnection } from '~/pages/projects/useInferenceServicesForConnection';

jest.mock('~/pages/projects/notebook/useRelatedNotebooks', () => ({
...jest.requireActual('~/pages/projects/notebook/useRelatedNotebooks'),
Expand All @@ -16,8 +21,13 @@ jest.mock('~/pages/projects/notebook/useNotebooksStates', () => ({
useNotebooksStates: jest.fn(),
}));

jest.mock('~/pages/projects/useInferenceServicesForConnection', () => ({
useInferenceServicesForConnection: jest.fn(),
}));

const useRelatedNotebooksMock = useRelatedNotebooks as jest.Mock;
const useNotebooksStatesMock = useNotebooksStates as jest.Mock;
const useInferenceServicesForConnectionMock = useInferenceServicesForConnection as jest.Mock;

const mockNotebooks = [
mockNotebookK8sResource({ name: 'connected-notebook', displayName: 'Connected notebook' }),
Expand All @@ -38,6 +48,16 @@ describe('Delete connection modal', () => {
mockNotebookState(mockNotebooks[1], { isStopped: true }),
],
]);
useInferenceServicesForConnectionMock.mockReturnValue([
mockInferenceServiceK8sResource({
name: 'deployed-model-1',
displayName: 'Deployed model 1',
}),
mockInferenceServiceK8sResource({
name: 'deployed-model-2',
displayName: 'Deployed model 2',
}),
]);
});

it('should show related resources', async () => {
Expand All @@ -60,5 +80,14 @@ describe('Delete connection modal', () => {
expect(notebookItems).toHaveLength(2);
expect(notebookItems[0]).toHaveTextContent('Connected notebook (Running)');
expect(notebookItems[1]).toHaveTextContent('Another notebook');

const modelsCountBadge = screen.getByTestId('connections-delete-models-count');
expect(modelsCountBadge).toHaveTextContent('2');
await act(() => fireEvent.click(modelsCountBadge));

const modelsItems = screen.getAllByTestId('connections-delete-models-item');
expect(modelsItems).toHaveLength(2);
expect(modelsItems[0]).toHaveTextContent('Deployed model 1');
expect(modelsItems[1]).toHaveTextContent('Deployed model 2');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,30 @@ import { mockConnectionTypeConfigMapObj } from '~/__mocks__/mockConnectionType';
import { mockConnection } from '~/__mocks__/mockConnection';
import { mockNotebookK8sResource } from '~/__mocks__/mockNotebookK8sResource';
import { useRelatedNotebooks } from '~/pages/projects/notebook/useRelatedNotebooks';
import { useInferenceServicesForConnection } from '~/pages/projects/useInferenceServicesForConnection';
import { mockInferenceServiceK8sResource } from '~/__mocks__';

jest.mock('~/pages/projects/notebook/useRelatedNotebooks', () => ({
...jest.requireActual('~/pages/projects/notebook/useRelatedNotebooks'),
useRelatedNotebooks: jest.fn(),
}));

jest.mock('~/pages/projects/useInferenceServicesForConnection', () => ({
useInferenceServicesForConnection: jest.fn(),
}));

const useRelatedNotebooksMock = useRelatedNotebooks as jest.Mock;
const useInferenceServicesForConnectionMock = useInferenceServicesForConnection as jest.Mock;

const mockInferenceServices = [
mockInferenceServiceK8sResource({ name: 'deployed-model-1', displayName: 'Deployed model 1' }),
mockInferenceServiceK8sResource({ name: 'deployed-model-2', displayName: 'Deployed model 2' }),
];

describe('ConnectionsTable', () => {
beforeEach(() => {
useRelatedNotebooksMock.mockReturnValue({ notebooks: [], loaded: true });
useInferenceServicesForConnectionMock.mockReturnValue([]);
});

it('should render table', () => {
Expand Down Expand Up @@ -61,6 +74,7 @@ describe('ConnectionsTable', () => {
notebooks: [mockNotebookK8sResource({ displayName: 'Connected notebook' })],
loaded: true,
});
useInferenceServicesForConnectionMock.mockReturnValue(mockInferenceServices);

const connection = mockConnection({ displayName: 'connection1', description: 'desc1' });
render(
Expand All @@ -81,5 +95,7 @@ describe('ConnectionsTable', () => {
expect(screen.queryByText('s3')).toBeFalsy();
expect(screen.getByText('S3 Buckets')).toBeTruthy();
expect(screen.getByText('Connected notebook')).toBeTruthy();
expect(screen.getByText('Deployed model 1')).toBeTruthy();
expect(screen.getByText('Deployed model 2')).toBeTruthy();
});
});
17 changes: 17 additions & 0 deletions frontend/src/pages/projects/useInferenceServicesForConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from 'react';
import { InferenceServiceKind } from '~/k8sTypes';
import { Connection } from '~/concepts/connectionTypes/types';
import { ProjectDetailsContext } from '~/pages/projects/ProjectDetailsContext';

export const useInferenceServicesForConnection = (
connection: Connection,
): InferenceServiceKind[] => {
const {
inferenceServices: { data: inferenceServices },
} = React.useContext(ProjectDetailsContext);
const connectionName = connection.metadata.name;

return inferenceServices.filter(
(inferenceService) => inferenceService.spec.predictor.model?.storage?.key === connectionName,
);
};

0 comments on commit 9ab1076

Please sign in to comment.