Skip to content

Commit

Permalink
Add drag and drop to connection type fields table (#3099)
Browse files Browse the repository at this point in the history
* Add drag and drop

* Update description truncate and hook naming

* Fix useDraggableTableControlled type name

* Add drag and drop cypress test

* Update draggable Row type
  • Loading branch information
emilys314 authored Aug 16, 2024
1 parent 1391418 commit c94f59d
Show file tree
Hide file tree
Showing 5 changed files with 297 additions and 20 deletions.
16 changes: 16 additions & 0 deletions frontend/src/__tests__/cypress/cypress/pages/connectionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ class CreateConnectionTypeTableRow extends TableRow {
findRequired() {
return this.find().findByTestId('field-required');
}

dragToIndex(i: number) {
const dataTransfer = new DataTransfer();
this.find().trigger('dragstart', { dataTransfer });
createConnectionTypePage
.getFieldsTableRow(i)
.find()
.trigger('dragover', { dataTransfer })
.trigger('drop', { dataTransfer })
.trigger('dragend');
}
}

class CreateConnectionTypePage {
Expand All @@ -39,6 +50,11 @@ class CreateConnectionTypePage {
cy.findAllByText('Create connection type').should('exist');
}

visitEditPage(name = 'existing') {
cy.visitWithLogin(`/connectionTypes/edit/${name}`);
cy.findAllByText('Create connection type').should('exist');
}

findConnectionTypeName() {
return cy.findByTestId('connection-type-name');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
import { createConnectionTypePage } from '~/__tests__/cypress/cypress/pages/connectionTypes';
import { asProductAdminUser } from '~/__tests__/cypress/cypress/utils/mockUsers';
import { mockDashboardConfig } from '~/__mocks__';
import type { ConnectionTypeField } from '~/concepts/connectionTypes/types';
import { toConnectionTypeConfigMap } from '~/concepts/connectionTypes/utils';

describe('create', () => {
beforeEach(() => {
Expand Down Expand Up @@ -99,3 +101,65 @@ describe('duplicate', () => {
row2.findRequired().should('be.checked');
});
});

describe('edit', () => {
const existing = mockConnectionTypeConfigMapObj({
name: 'existing',
fields: [
{
type: 'section',
name: 'header1',
},
{
type: 'short-text',
name: 'field1',
envVar: 'short-text-1',
required: false,
properties: {},
},
{
type: 'short-text',
name: 'field2',
envVar: 'short-text-2',
required: true,
properties: {},
},
] as ConnectionTypeField[],
});

beforeEach(() => {
asProductAdminUser();

cy.interceptOdh(
'GET /api/config',
mockDashboardConfig({
disableConnectionTypes: false,
}),
);
cy.interceptOdh(
'GET /api/connection-types/:name',
{ path: { name: 'existing' } },
toConnectionTypeConfigMap(existing),
);
});

it('Drag and drop field rows in table', () => {
createConnectionTypePage.visitEditPage('existing');

createConnectionTypePage.getFieldsTableRow(0).findName().should('contain.text', 'header1');
createConnectionTypePage.getFieldsTableRow(1).findName().should('contain.text', 'field1');
createConnectionTypePage.getFieldsTableRow(2).findName().should('contain.text', 'field2');

createConnectionTypePage.getFieldsTableRow(0).dragToIndex(2);

createConnectionTypePage.getFieldsTableRow(0).findName().should('contain.text', 'field1');
createConnectionTypePage.getFieldsTableRow(1).findName().should('contain.text', 'field2');
createConnectionTypePage.getFieldsTableRow(2).findName().should('contain.text', 'header1');

createConnectionTypePage.getFieldsTableRow(1).dragToIndex(0);

createConnectionTypePage.getFieldsTableRow(0).findName().should('contain.text', 'field2');
createConnectionTypePage.getFieldsTableRow(1).findName().should('contain.text', 'field1');
createConnectionTypePage.getFieldsTableRow(2).findName().should('contain.text', 'header1');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { PlusCircleIcon } from '@patternfly/react-icons';
import { Table, Thead, Tbody, Tr, Th } from '@patternfly/react-table';
import { ConnectionTypeField, ConnectionTypeFieldType } from '~/concepts/connectionTypes/types';
import useDraggableTableControlled from '~/utilities/useDraggableTableControlled';
import ConnectionTypeFieldModal from './ConnectionTypeFieldModal';
import ManageConnectionTypeFieldsTableRow from './ManageConnectionTypeFieldsTableRow';

Expand Down Expand Up @@ -63,21 +64,26 @@ const ManageConnectionTypeFieldsTable: React.FC<Props> = ({ fields, onFieldsChan
'Required',
];

// TODO: drag and drop rows
const { tableProps, rowsToRender } = useDraggableTableControlled<ConnectionTypeField>(
fields,
onFieldsChange,
);

return (
<>
{fields.length > 0 ? (
<>
<Table data-testid="connection-type-fields-table">
<Table data-testid="connection-type-fields-table" className={tableProps.className}>
<Thead>
<Tr>
<Th screenReaderText="Drag and drop" />
{columns.map((column, columnIndex) => (
<Th key={columnIndex}>{column}</Th>
))}
</Tr>
</Thead>
<Tbody>
{fields.map((row, index) => (
<Tbody {...tableProps.tbodyProps}>
{rowsToRender.map(({ data: row, rowProps }, index) => (
<ManageConnectionTypeFieldsTableRow
key={index}
row={row}
Expand All @@ -86,9 +92,10 @@ const ManageConnectionTypeFieldsTable: React.FC<Props> = ({ fields, onFieldsChan
setModalField({
field: row,
isEdit: true,
index,
});
}}
onDelete={() => onFieldsChange(fields.filter((f) => f !== row))}
onDelete={() => onFieldsChange(fields.filter((f, i) => i !== index))}
onDuplicate={(field) => {
setModalField({
field: structuredClone(field),
Expand All @@ -111,6 +118,7 @@ const ManageConnectionTypeFieldsTable: React.FC<Props> = ({ fields, onFieldsChan
...fields.slice(index + 1),
]);
}}
{...rowProps}
/>
))}
</Tbody>
Expand Down Expand Up @@ -148,10 +156,13 @@ const ManageConnectionTypeFieldsTable: React.FC<Props> = ({ fields, onFieldsChan
onClose={() => setModalField(undefined)}
isOpen
onSubmit={(field) => {
const i = modalField.field ? fields.indexOf(modalField.field) : -1;
if (i >= 0) {
if (modalField.field && modalField.isEdit && modalField.index !== undefined) {
// update
onFieldsChange([...fields.slice(0, i), field, ...fields.slice(i + 1)]);
onFieldsChange([
...fields.slice(0, modalField.index),
field,
...fields.slice(modalField.index + 1),
]);
} else if (modalField.index != null) {
// insert
onFieldsChange([
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as React from 'react';
import { ActionsColumn, Td, Tr } from '@patternfly/react-table';
import { Button, Label, Switch, Text, TextContent, Truncate } from '@patternfly/react-core';
import { Button, Label, Switch, Truncate } from '@patternfly/react-core';
import {
ConnectionTypeField,
ConnectionTypeFieldType,
SectionField,
} from '~/concepts/connectionTypes/types';
import { defaultValueToString, fieldTypeToString } from '~/concepts/connectionTypes/utils';
import type { RowProps } from '~/utilities/useDraggableTableControlled';

type Props = {
row: ConnectionTypeField;
Expand All @@ -16,7 +17,7 @@ type Props = {
onDuplicate: (field: ConnectionTypeField) => void;
onAddField: (parentSection: SectionField) => void;
onChange: (updatedField: ConnectionTypeField) => void;
};
} & RowProps;

const ManageConnectionTypeFieldsTableRow: React.FC<Props> = ({
row,
Expand All @@ -26,18 +27,24 @@ const ManageConnectionTypeFieldsTableRow: React.FC<Props> = ({
onDuplicate,
onAddField,
onChange,
...props
}) => {
if (row.type === ConnectionTypeFieldType.Section) {
return (
<Tr id={row.name} isStriped data-testid="row">
<Tr draggable isStriped data-testid="row" {...props}>
<Td
draggableRow={{
id: `draggable-row-${props.id}`,
}}
/>
<Td dataLabel={columns[0]} colSpan={5} data-testid="field-name">
{row.name}{' '}
<Label color="blue" data-testid="section-heading">
Section heading
</Label>
<TextContent>
<Text className="pf-v5-u-color-200">{row.description}</Text>
</TextContent>
<div className="pf-v5-u-color-200">
<Truncate content={row.description ?? ''} />
</div>
</Td>
<Td isActionCell modifier="nowrap">
<Button variant="secondary" onClick={() => onAddField(row)}>
Expand Down Expand Up @@ -65,14 +72,17 @@ const ManageConnectionTypeFieldsTableRow: React.FC<Props> = ({
}

return (
<Tr id={row.name} data-testid="row">
<Tr draggable data-testid="row" {...props}>
<Td
draggableRow={{
id: `draggable-row-${props.id}`,
}}
/>
<Td dataLabel={columns[0]} data-testid="field-name">
{row.name}
<TextContent>
<Text className="pf-v5-u-color-200">
<Truncate content={row.description ?? ''} />
</Text>
</TextContent>
<div className="pf-v5-u-color-200">
<Truncate content={row.description ?? ''} />
</div>
</Td>
<Td dataLabel={columns[1]} data-testid="field-type">
{fieldTypeToString(row)}
Expand Down
Loading

0 comments on commit c94f59d

Please sign in to comment.