Skip to content

Commit

Permalink
Add delete modal for project connections (#3204)
Browse files Browse the repository at this point in the history
  • Loading branch information
emilys314 authored Sep 13, 2024
1 parent cc7aaf0 commit 353a156
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 22 deletions.
15 changes: 15 additions & 0 deletions frontend/src/__tests__/cypress/cypress/pages/connections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { TableRow } from './components/table';

class ConnectionsPage {
findTable() {
return cy.findByTestId('connection-table');
}

getConnectionRow(name: string) {
return new TableRow(() =>
this.findTable().findAllByTestId(`table-row-title`).contains(name).parents('tr'),
);
}
}

export const connectionsPage = new ConnectionsPage();
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
mock200Status,
mockDashboardConfig,
mockK8sResourceList,
mockProjectK8sResource,
Expand All @@ -7,6 +8,8 @@ import {
import { mockConnectionTypeConfigMap } from '~/__mocks__/mockConnectionType';
import { projectDetails } from '~/__tests__/cypress/cypress/pages/projects';
import { ProjectModel, SecretModel } from '~/__tests__/cypress/cypress/utils/models';
import { connectionsPage } from '~/__tests__/cypress/cypress/pages/connections';
import { deleteModal } from '~/__tests__/cypress/cypress/pages/components/DeleteModal';

const initIntercepts = (isEmpty = false) => {
cy.interceptK8sList(
Expand Down Expand Up @@ -51,9 +54,31 @@ describe('Connections', () => {
initIntercepts();
projectDetails.visitSection('test-project', 'connections');
projectDetails.shouldBeEmptyState('Connections', 'connections', false);
cy.findByTestId('connection-table').findByText('test1').should('exist');
cy.findByTestId('connection-table').findByText('s3').should('exist');
cy.findByTestId('connection-table').findByText('test2').should('exist');
cy.findByTestId('connection-table').findByText('postgres').should('exist');
connectionsPage.findTable().findByText('test1').should('exist');
connectionsPage.findTable().findByText('s3').should('exist');
connectionsPage.findTable().findByText('test2').should('exist');
connectionsPage.findTable().findByText('postgres').should('exist');
});

it('Delete a connection', () => {
initIntercepts();
cy.interceptK8s(
'DELETE',
{
model: SecretModel,
ns: 'test-project',
name: 'test1',
},
mock200Status({}),
).as('deleteConnection');

projectDetails.visitSection('test-project', 'connections');

connectionsPage.getConnectionRow('test1').findKebabAction('Delete').click();
deleteModal.findSubmitButton().should('be.disabled');
deleteModal.findInput().fill('test1');
deleteModal.findSubmitButton().should('be.enabled').click();

cy.wait('@deleteConnection');
});
});
3 changes: 3 additions & 0 deletions frontend/src/pages/projects/ProjectDetailsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ const ProjectDetailsContextProvider: React.FC = () => {
const notebookRefresh = notebooks.refresh;
const pvcRefresh = pvcs.refresh;
const dataConnectionRefresh = dataConnections.refresh;
const connectionRefresh = connections.refresh;
const servingRuntimeRefresh = servingRuntimes.refresh;
const servingRuntimeTemplateOrderRefresh = servingRuntimeTemplateOrder.refresh;
const servingRuntimeTemplateDisablementRefresh = servingRuntimeTemplateDisablement.refresh;
Expand All @@ -112,6 +113,7 @@ const ProjectDetailsContextProvider: React.FC = () => {
setTimeout(notebookRefresh, 2000);
pvcRefresh();
dataConnectionRefresh();
connectionRefresh();
servingRuntimeRefresh();
inferenceServiceRefresh();
projectSharingRefresh();
Expand All @@ -122,6 +124,7 @@ const ProjectDetailsContextProvider: React.FC = () => {
notebookRefresh,
pvcRefresh,
dataConnectionRefresh,
connectionRefresh,
servingRuntimeRefresh,
servingRuntimeTemplateOrderRefresh,
servingRuntimeTemplateDisablementRefresh,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { K8sStatus } from '@openshift/dynamic-plugin-sdk-utils';
import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils';
import { Connection } from '~/concepts/connectionTypes/types';
import DeleteModal from '~/pages/projects/components/DeleteModal';

type Props = {
deleteConnection: Connection;
onClose: (deleted?: boolean) => void;
onDelete: () => Promise<K8sStatus>;
};

export const ConnectionsDeleteModal: React.FC<Props> = ({
deleteConnection,
onClose,
onDelete,
}) => {
const [isDeleting, setIsDeleting] = React.useState(false);
const [error, setError] = React.useState<Error>();

return (
<DeleteModal
title="Delete connection?"
isOpen
onClose={onClose}
submitButtonLabel="Delete"
onDelete={() => {
setIsDeleting(true);
setError(undefined);

onDelete()
.then(() => {
onClose(true);
})
.catch((e) => {
setError(e);
setIsDeleting(false);
});
}}
deleting={isDeleting}
error={error}
deleteName={getDisplayNameFromK8sResource(deleteConnection)}
>
The <b>{getDisplayNameFromK8sResource(deleteConnection)}</b> connection will be deleted, and
its dependent resources will stop working.
</DeleteModal>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const ConnectionsDescription =

const ConnectionsList: React.FC = () => {
const {
connections: { data: connections, loaded, error },
connections: { data: connections, loaded, error, refresh: refreshConnections },
} = React.useContext(ProjectDetailsContext);
const [connectionTypes, connectionTypesLoaded, connectionTypesError] = useWatchConnectionTypes();

Expand Down Expand Up @@ -60,7 +60,11 @@ const ConnectionsList: React.FC = () => {
/>
}
>
<ConnectionsTable connections={connections} connectionTypes={connectionTypes} />
<ConnectionsTable
connections={connections}
connectionTypes={connectionTypes}
refreshConnections={refreshConnections}
/>
</DetailsSection>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,56 @@
import * as React from 'react';
import { Connection, ConnectionTypeConfigMapObj } from '~/concepts/connectionTypes/types';
import { deleteSecret } from '~/api';
import { Table } from '~/components/table';
import ConnectionsTableRow from './ConnectionsTableRow';
import { columns } from './connectionsTableColumns';
import { ConnectionsDeleteModal } from './ConnectionsDeleteModal';

type ConnectionsTableProps = {
connections: Connection[];
connectionTypes?: ConnectionTypeConfigMapObj[];
refreshConnections: () => void;
};

const ConnectionsTable: React.FC<ConnectionsTableProps> = ({ connections, connectionTypes }) => (
<Table
data={connections}
data-testid="connection-table"
columns={columns}
rowRenderer={(connection) => (
<ConnectionsTableRow
key={connection.metadata.name}
obj={connection}
connectionTypes={connectionTypes}
onEditConnection={() => undefined}
onDeleteConnection={() => undefined}
const ConnectionsTable: React.FC<ConnectionsTableProps> = ({
connections,
connectionTypes,
refreshConnections,
}) => {
const [deleteConnection, setDeleteConnection] = React.useState<Connection>();

return (
<>
<Table
data={connections}
data-testid="connection-table"
columns={columns}
rowRenderer={(connection) => (
<ConnectionsTableRow
key={connection.metadata.name}
obj={connection}
connectionTypes={connectionTypes}
onEditConnection={() => undefined}
onDeleteConnection={() => setDeleteConnection(connection)}
/>
)}
isStriped
/>
)}
isStriped
/>
);
{deleteConnection && (
<ConnectionsDeleteModal
deleteConnection={deleteConnection}
onClose={(deleted) => {
setDeleteConnection(undefined);
if (deleted) {
refreshConnections();
}
}}
onDelete={() =>
deleteSecret(deleteConnection.metadata.namespace, deleteConnection.metadata.name)
}
/>
)}
</>
);
};
export default ConnectionsTable;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe('ConnectionsTable', () => {
render(
<ConnectionsTable
connections={[mockConnection({ displayName: 'connection1', description: 'desc1' })]}
refreshConnections={() => undefined}
/>,
);

Expand All @@ -25,6 +26,7 @@ describe('ConnectionsTable', () => {
connectionTypes={[
mockConnectionTypeConfigMapObj({ name: 's3', displayName: 'S3 Buckets' }),
]}
refreshConnections={() => undefined}
/>,
);

Expand Down

0 comments on commit 353a156

Please sign in to comment.