Skip to content

Commit

Permalink
List connected notebooks for each connection table row
Browse files Browse the repository at this point in the history
  • Loading branch information
jeff-phillips-18 committed Sep 25, 2024
1 parent fcc1173 commit 04fc681
Show file tree
Hide file tree
Showing 18 changed files with 327 additions and 11 deletions.
44 changes: 44 additions & 0 deletions frontend/src/concepts/design/TypedObjectIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as React from 'react';
import { SVGIconProps } from '@patternfly/react-icons/dist/esm/createIcon';
import { ProjectObjectType, typedColor } from '~/concepts/design/utils';
import NotebookIcon from '~/images/icons/NotebookIcon';
import ModelIcon from '~/images/icons/ModelsIcon';

type TypedObjectIconProps = SVGIconProps & {
resourceType: ProjectObjectType;
useTypedColor?: boolean;
size?: number;
};
const TypedObjectIcon: React.FC<TypedObjectIconProps> = ({
resourceType,
useTypedColor,
style,
...rest
}) => {
switch (resourceType) {
case ProjectObjectType.notebook:
return (
<NotebookIcon
style={{
color: useTypedColor ? typedColor(resourceType) : undefined,
...(style || {}),
}}
{...rest}
/>
);
case ProjectObjectType.deployedModels:
return (
<ModelIcon
style={{
color: useTypedColor ? typedColor(resourceType) : undefined,
...(style || {}),
}}
{...rest}
/>
);
default:
return null;
}
};

export default TypedObjectIcon;
30 changes: 30 additions & 0 deletions frontend/src/concepts/design/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,36 @@ export const typedBackgroundColor = (objectType: ProjectObjectType): string => {
}
};

export const typedColor = (objectType: ProjectObjectType): string => {
switch (objectType) {
case ProjectObjectType.project:
return 'var(--ai-project--Color)';
case ProjectObjectType.notebook:
return 'var(--ai-notebook--Color)';
case ProjectObjectType.pipeline:
case ProjectObjectType.pipelineRun:
return 'var(--ai-pipeline--Color)';
case ProjectObjectType.pipelineSetup:
return 'var(--ai-set-up--Color)';
case ProjectObjectType.clusterStorage:
return 'var(--ai-cluster-storage--Color)';
case ProjectObjectType.modelServer:
case ProjectObjectType.registeredModels:
case ProjectObjectType.deployedModels:
case ProjectObjectType.deployingModels:
return 'var(--ai-model-server--Color)';
case ProjectObjectType.dataConnection:
case ProjectObjectType.connections:
return 'var(--ai-data-connection--Color)';
case ProjectObjectType.user:
return 'var(--ai-user--Color)';
case ProjectObjectType.group:
return 'var(--ai-group--Color)';
default:
return '';
}
};

export const typedObjectImage = (objectType: ProjectObjectType): string => {
switch (objectType) {
case ProjectObjectType.project:
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/concepts/design/vars.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,40 @@
--ai-training--BorderColor: #9ad8d8;
--ai-serving--BorderColor: #92c5f9;


--ai-project--BackgroundColor: var(--ai-organize--BackgroundColor);
--ai-project--BorderColor: var(--ai-organize--BorderColor);
--ai-project--Color: var(--ai-organize--BorderColor);

--ai-data-connection--BackgroundColor: var(--ai-organize--BackgroundColor);
--ai-data-connection--BorderColor: var(--ai-organize--BorderColor);
--ai-data-connection--Color: var(--ai-organize--BorderColor);

--ai-cluster-storage--BackgroundColor: var(--ai-organize--BackgroundColor);
--ai-cluster-storage--BorderColor: var(--ai-organize--BorderColor);
--ai-cluster-storage--Color: var(--ai-organize--BorderColor);

--ai-notebook--BackgroundColor: var(--ai-training--BackgroundColor);
--ai-notebook--BorderColor: var(--ai-training--BorderColor);
--ai-notebook--Color: #37a3a3;

--ai-model--BackgroundColor: var(--ai-serving--BackgroundColor);
--ai-model--BorderColor: var(--ai-serviing--BorderColor);
--ai-model--Color: var(--ai-serviing--BorderColor);

--ai-pipeline--BackgroundColor: var(--ai-training--BackgroundColor);
--ai-pipeline--BorderColor: var(--ai-training--BorderColor);
--ai-pipeline--Color: var(--ai-training--BorderColor);

--ai-model-server--BackgroundColor: var(--ai-serving--BackgroundColor);
--ai-model-server--BorderColor: var(--ai-serving--BorderColor);
--ai-model-server--Color: #5E40BE;

--ai-user--BackgroundColor: var(--ai-set-up--BackgroundColor);
--ai-user--BorderColor: var(--ai-set-up--BorderColor);
--ai-user--Color: var(--ai-set-up--BorderColor);

--ai-group--BackgroundColor: var(--ai-set-up--BackgroundColor);
--ai-group--BorderColor: var(--ai-set-up--BorderColor);
--ai-group--Color: var(--ai-set-up--BorderColor);
}
13 changes: 13 additions & 0 deletions frontend/src/images/icons/ModelsIcon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createIcon } from '@patternfly/react-icons/dist/esm/createIcon';

const ModelIcon = createIcon({
name: 'ModelIcon',
width: 32,
height: 32,
svgPath:
'M19.5 11H12.5C11.6729 11 11 10.3271 11 9.5V2.5C11 1.6729 11.6729 1 12.5 1H19.5C20.3271 1 21 1.6729 21 2.5V9.5C21 10.3271 20.3271 11 19.5 11ZM19 3H13V9H19V3ZM12.5 25H5.5C4.6729 25 4 24.3271 4 23.5V16.5C4 15.6729 4.6729 15 5.5 15H12.5C13.3271 15 14 15.6729 14 16.5V23.5C14 24.3271 13.3271 25 12.5 25ZM12 17H6V23H12V17ZM2 29H30C30.5527 29 31 29.4478 31 30C31 30.5522 30.5527 31 30 31H2C1.4473 31 1 30.5522 1 30C1 29.4478 1.4473 29 2 29ZM18 16.5V23.5C18 24.3271 18.6729 25 19.5 25H26.5C27.3271 25 28 24.3271 28 23.5V16.5C28 15.6729 27.3271 15 26.5 15H19.5C18.6729 15 18 15.6729 18 16.5ZM20 17H26V23H20V17Z',
xOffset: 0,
yOffset: 0,
});

export default ModelIcon;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import { Label, LabelGroup, Spinner } from '@patternfly/react-core';
import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils';
import useRelatedNotebooks, { ConnectedNotebookContext } from './useRelatedNotebooks';
import { useRelatedNotebooks, ConnectedNotebookContext } from './useRelatedNotebooks';

type ConnectedNotebookNamesProps = {
context: ConnectedNotebookContext;
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/pages/projects/notebook/useRelatedNotebooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export enum ConnectedNotebookContext {
POSSIBLE_DATA_CONNECTION = 'data-connection-possible',
}

const useRelatedNotebooks = (
export const useRelatedNotebooks = (
context: ConnectedNotebookContext,
resourceName?: string,
): { notebooks: NotebookKind[]; loaded: boolean; error: Error | undefined } => {
Expand Down Expand Up @@ -88,5 +88,3 @@ const useRelatedNotebooks = (
error,
};
};

export default useRelatedNotebooks;
3 changes: 2 additions & 1 deletion frontend/src/pages/projects/pvc/DeletePVCModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as React from 'react';
import { PersistentVolumeClaimKind } from '~/k8sTypes';
import { deletePvc, removeNotebookPVC } from '~/api';
import useRelatedNotebooks, {
import {
useRelatedNotebooks,
ConnectedNotebookContext,
} from '~/pages/projects/notebook/useRelatedNotebooks';
import DeleteModal from '~/pages/projects/components/DeleteModal';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as React from 'react';
import { LabelGroup, Spinner } from '@patternfly/react-core';
import {
useRelatedNotebooks,
ConnectedNotebookContext,
} from '~/pages/projects/notebook/useRelatedNotebooks';
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';

type Props = {
connection: Connection;
};

const ConnectedResources: React.FC<Props> = ({ connection }) => {
const { notebooks: connectedNotebooks, loaded: notebooksLoaded } = useRelatedNotebooks(
ConnectedNotebookContext.EXISTING_DATA_CONNECTION,
connection.metadata.name,
);

if (!connectedNotebooks.length) {
return '-';
}

if (!notebooksLoaded) {
return <Spinner size="sm" />;
}

return (
<LabelGroup>
{connectedNotebooks.map((notebook) => (
<ResourceLabel
key={notebook.metadata.name}
resourceType={ProjectObjectType.notebook}
title={getDisplayNameFromK8sResource(notebook)}
/>
))}
</LabelGroup>
);
};

export default ConnectedResources;
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import React from 'react';
import { K8sStatus } from '@openshift/dynamic-plugin-sdk-utils';
import {
Badge,
Bullseye,
ExpandableSection,
ExpandableSectionToggle,
Spinner,
TextContent,
TextList,
TextListItem,
} from '@patternfly/react-core';
import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils';
import { Connection } from '~/concepts/connectionTypes/types';
import DeleteModal from '~/pages/projects/components/DeleteModal';
import {
useRelatedNotebooks,
ConnectedNotebookContext,
} from '~/pages/projects/notebook/useRelatedNotebooks';

type Props = {
deleteConnection: Connection;
Expand All @@ -17,6 +31,11 @@ export const ConnectionsDeleteModal: React.FC<Props> = ({
}) => {
const [isDeleting, setIsDeleting] = React.useState(false);
const [error, setError] = React.useState<Error>();
const { notebooks: connectedNotebooks, loaded: notebooksLoaded } = useRelatedNotebooks(
ConnectedNotebookContext.EXISTING_DATA_CONNECTION,
deleteConnection.metadata.name,
);
const [notebooksExpanded, setNotebooksExpanded] = React.useState<boolean>(false);

return (
<DeleteModal
Expand All @@ -43,6 +62,47 @@ 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 : (
<div className="pf-v5-u-mt-md">
{!notebooksLoaded ? (
<Bullseye>
<Spinner size="md" />
</Bullseye>
) : (
<>
<ExpandableSectionToggle
isExpanded={notebooksExpanded}
onToggle={setNotebooksExpanded}
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>
<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)}
</TextListItem>
))}
</TextList>
</TextContent>
</ExpandableSection>
</>
)}
</div>
)}
</DeleteModal>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { TableRowTitleDescription } from '~/components/table';
import { getDescriptionFromK8sResource, getDisplayNameFromK8sResource } from '~/concepts/k8s/utils';
import { getCompatibleTypes } from '~/concepts/connectionTypes/utils';
import CompatibilityLabel from '~/concepts/connectionTypes/CompatibilityLabel';
import ConnectedResources from '~/pages/projects/screens/detail/connections/ConnectedResources';

type ConnectionsTableRowProps = {
obj: Connection;
Expand Down Expand Up @@ -62,7 +63,9 @@ const ConnectionsTableRow: React.FC<ConnectionsTableRowProps> = ({
'-'
)}
</Td>
<Td dataLabel="Connected resources">-</Td>
<Td dataLabel="Connected resources">
<ConnectedResources connection={obj} />
</Td>
<Td isActionCell>
<ActionsColumn
items={[
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from 'react';
import { Label } from '@patternfly/react-core';
import { ProjectObjectType } from '~/concepts/design/utils';
import TypedObjectIcon from '~/concepts/design/TypedObjectIcon';

type Props = {
title: string;
resourceType: ProjectObjectType;
};

const ResourceLabel: React.FC<Props> = ({ title, resourceType }) => (
<Label variant="outline" icon={<TypedObjectIcon resourceType={resourceType} useTypedColor />}>
{title}
</Label>
);

export default ResourceLabel;
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { act } from 'react';
import '@testing-library/jest-dom';
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 } from '~/__mocks__';

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

const useRelatedNotebooksMock = useRelatedNotebooks as jest.Mock;

describe('Delete connection modal', () => {
const onDelete = jest.fn();
const onClose = jest.fn();

beforeEach(() => {
useRelatedNotebooksMock.mockReturnValue({
notebooks: [
mockNotebookK8sResource({ name: 'connected-notebook', displayName: 'Connected notebook' }),
mockNotebookK8sResource({ name: 'another-notebook', displayName: 'Another notebook' }),
],
loaded: true,
});
});

it('should show related resources', async () => {
const deleteConnection = mockConnection({ displayName: 'connection1', description: 'desc1' });

render(
<ConnectionsDeleteModal
deleteConnection={deleteConnection}
onClose={onClose}
onDelete={onDelete}
/>,
);

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

expect(screen.getAllByTestId('connections-delete-notebooks-item')).toHaveLength(2);
});
});
Loading

0 comments on commit 04fc681

Please sign in to comment.