diff --git a/frontend/src/__tests__/cypress/cypress/pages/connectionTypes.ts b/frontend/src/__tests__/cypress/cypress/pages/connectionTypes.ts index 31fede4fcf..14315355f2 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/connectionTypes.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/connectionTypes.ts @@ -111,4 +111,75 @@ class ConnectionTypesPage { } } +class CreateConnectionTypeTableRow extends TableRow { + findSectionHeading() { + return this.find().findByTestId('section-heading'); + } + + findName() { + return this.find().findByTestId('field-name'); + } + + findType() { + return this.find().findByTestId('field-type'); + } + + findDefault() { + return this.find().findByTestId('field-default'); + } + + findEnvVar() { + return this.find().findByTestId('field-env'); + } + + findRequired() { + return this.find().findByTestId('field-required'); + } +} + +class CreateConnectionTypePage { + visitCreatePage() { + cy.visitWithLogin('/connectionTypes/create'); + cy.findAllByText('Create connection type').should('exist'); + } + + visitDuplicatePage(name = 'existing') { + cy.visitWithLogin(`/connectionTypes/duplicate/${name}`); + cy.findAllByText('Create connection type').should('exist'); + } + + findConnectionTypeName() { + return cy.findByTestId('connection-type-name'); + } + + findConnectionTypeDesc() { + return cy.findByTestId('connection-type-description'); + } + + findConnectionTypeEnableCheckbox() { + return cy.findByTestId('connection-type-enable'); + } + + findConnectionTypePreviewToggle() { + return cy.findByTestId('preview-drawer-toggle-button'); + } + + findFieldsTable() { + return cy.findByTestId('connection-type-fields-table'); + } + + findAllFieldsTableRows() { + return this.findFieldsTable().findAllByTestId('row'); + } + + getFieldsTableRow(index: number) { + return new CreateConnectionTypeTableRow(() => this.findAllFieldsTableRows().eq(index)); + } + + findSubmitButton() { + return cy.findByTestId('submit-button'); + } +} + export const connectionTypesPage = new ConnectionTypesPage(); +export const createConnectionTypePage = new CreateConnectionTypePage(); diff --git a/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts b/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts index f3e1b12bb0..e46ee31387 100644 --- a/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts +++ b/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts @@ -51,6 +51,7 @@ import type { } from '~/concepts/pipelines/kfTypes'; import type { GrpcResponse } from '~/__mocks__/mlmd/utils'; import type { BuildMockPipelinveVersionsType } from '~/__mocks__'; +import type { ArtifactStorage } from '~/concepts/pipelines/types'; type SuccessErrorResponse = { success: boolean; @@ -567,6 +568,14 @@ declare global { }, response: OdhResponse<{ notebook: NotebookKind; isRunning: boolean }>, ) => Cypress.Chainable) & + (( + type: 'GET /api/service/pipelines/:namespace/:serviceName/apis/v2beta1/artifacts/:artifactId', + options: { + query: { view: string }; + path: { namespace: string; serviceName: string; artifactId: number }; + }, + response: OdhResponse, + ) => Cypress.Chainable) & (( type: 'GET /api/storage/:namespace', options: { diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/connectionTypes/connectionTypes.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/connectionTypes/connectionTypes.cy.ts index a0f74f3bbe..5e17a352e8 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/connectionTypes/connectionTypes.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/connectionTypes/connectionTypes.cy.ts @@ -6,6 +6,7 @@ import { import { connectionTypesPage } from '~/__tests__/cypress/cypress/pages/connectionTypes'; import { mockDashboardConfig } from '~/__mocks__'; import { mockConnectionTypeConfigMap } from '~/__mocks__/mockConnectionType'; +import { deleteModal } from '~/__tests__/cypress/cypress/pages/components/DeleteModal'; it('Connection types should not be available for non product admins', () => { asProjectAdminUser(); @@ -103,4 +104,22 @@ describe('Connection types', () => { row2.findEnableSwitch().click(); row2.findEnableStatus().should('have.text', 'Enabling...'); }); + + it('should delete connection type', () => { + // cy.interceptOdh( + // 'DELETE /api/connection-types/:name', + // { + // path: { name: 'test-2' }, + // }, + // { success: true }, + // ).as('delete'); + + connectionTypesPage.visit(); + connectionTypesPage.shouldHaveConnectionTypes(); + connectionTypesPage.getConnectionTypeRow('Test display name').findKebabAction('Delete').click(); + deleteModal.findSubmitButton().should('be.disabled'); + deleteModal.findInput().fill('Test display name'); + deleteModal.findSubmitButton().should('be.enabled').click(); + cy.wait('@delete'); + }); }); diff --git a/frontend/src/pages/connectionTypes/ConnectionTypesTable.tsx b/frontend/src/pages/connectionTypes/ConnectionTypesTable.tsx index 830ed8f7e3..c3688b7cf5 100644 --- a/frontend/src/pages/connectionTypes/ConnectionTypesTable.tsx +++ b/frontend/src/pages/connectionTypes/ConnectionTypesTable.tsx @@ -6,11 +6,12 @@ import ConnectionTypesTableRow from '~/pages/connectionTypes/ConnectionTypesTabl import ConnectionTypesTableToolbar from '~/pages/connectionTypes/ConnectionTypesTableToolbar'; import { ConnectionTypeConfigMapObj } from '~/concepts/connectionTypes/types'; import { Table } from '~/components/table'; +import DeleteConnectionTypeModal from '~/pages/connectionTypes/DeleteConnectionTypeModal'; -interface ConnectionTypesTableProps { +type ConnectionTypesTableProps = { connectionTypes: ConnectionTypeConfigMapObj[]; onUpdate: () => void; -} +}; const ConnectionTypesTable: React.FC = ({ connectionTypes, @@ -19,6 +20,10 @@ const ConnectionTypesTable: React.FC = ({ const [filterData, setFilterData] = React.useState(initialFilterData); const onClearFilters = React.useCallback(() => setFilterData(initialFilterData), [setFilterData]); + const [deleteConnectionType, setDeleteConnectionType] = React.useState< + ConnectionTypeConfigMapObj | undefined + >(); + const filteredConnectionTypes = connectionTypes.filter((connectionType) => { const keywordFilter = filterData.Keyword?.toLowerCase(); const createFilter = filterData['Created by']?.toLowerCase(); @@ -51,30 +56,42 @@ const ConnectionTypesTable: React.FC = ({ }; return ( - ( - - )} - toolbarContent={ - - } - disableItemCount - emptyTableView={} - id="connectionTypes-list-table" - /> + <> +
( + setDeleteConnectionType(connection)} + /> + )} + toolbarContent={ + + } + disableItemCount + emptyTableView={} + id="connectionTypes-list-table" + /> + { + if (deleted) { + onUpdate(); + } + setDeleteConnectionType(undefined); + }} + /> + ); }; diff --git a/frontend/src/pages/connectionTypes/ConnectionTypesTableRow.tsx b/frontend/src/pages/connectionTypes/ConnectionTypesTableRow.tsx index 758d21ed38..78466ffda5 100644 --- a/frontend/src/pages/connectionTypes/ConnectionTypesTableRow.tsx +++ b/frontend/src/pages/connectionTypes/ConnectionTypesTableRow.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Td, Tr } from '@patternfly/react-table'; +import { ActionsColumn, Td, Tr } from '@patternfly/react-table'; import { Flex, Icon, @@ -19,9 +19,14 @@ import { updateConnectionTypeEnabled } from '~/services/connectionTypesService'; type ConnectionTypesTableRowProps = { obj: ConnectionTypeConfigMapObj; onUpdate: () => void; + handleDelete: (cr: ConnectionTypeConfigMapObj) => void; }; -const ConnectionTypesTableRow: React.FC = ({ obj, onUpdate }) => { +const ConnectionTypesTableRow: React.FC = ({ + obj, + onUpdate, + handleDelete, +}) => { const [statusMessage, setStatusMessage] = React.useState(); const [errorMessage, setErrorMessage] = React.useState(); const pendingEnabledState = React.useRef<'true' | 'false' | undefined>(); @@ -112,6 +117,16 @@ const ConnectionTypesTableRow: React.FC = ({ obj, ) : null} + ); }; diff --git a/frontend/src/pages/connectionTypes/DeleteConnectionTypeModal.tsx b/frontend/src/pages/connectionTypes/DeleteConnectionTypeModal.tsx new file mode 100644 index 0000000000..2947d274a1 --- /dev/null +++ b/frontend/src/pages/connectionTypes/DeleteConnectionTypeModal.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { ConnectionTypeConfigMapObj } from '~/concepts/connectionTypes/types'; +import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils'; +import DeleteModal from '~/pages/projects/components/DeleteModal'; +import { deleteConnectionType } from '~/services/connectionTypesService'; + +type DeleteConnectionTypeModalProps = { + connectionType?: ConnectionTypeConfigMapObj; + onClose: (deleted: boolean) => void; +}; + +const DeleteConnectionTypeModal: React.FC = ({ + connectionType, + onClose, +}) => { + const [isDeleting, setIsDeleting] = React.useState(false); + const [error, setError] = React.useState(); + + const onBeforeClose = (deleted: boolean) => { + onClose(deleted); + setIsDeleting(false); + setError(undefined); + }; + + const deleteName = connectionType + ? getDisplayNameFromK8sResource(connectionType) + : 'this connection type'; + + return ( + onBeforeClose(false)} + submitButtonLabel="Delete" + onDelete={() => { + if (connectionType) { + setIsDeleting(true); + deleteConnectionType(connectionType.metadata.name) + .then(() => { + onBeforeClose(true); + }) + .catch((e) => { + setError(e); + setIsDeleting(false); + }); + } + }} + deleting={isDeleting} + error={error} + deleteName={deleteName} + > + The {deleteName} connection type will be deleted. Existing connections of this type + will not be affected. + + ); +}; + +export default DeleteConnectionTypeModal;
+ handleDelete(obj), + }, + ]} + /> +