-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate Table visualization to React Part 2: Editor (#4175)
* Migrate table editor to React: skeleton, Grid tab * Columns tab * Cleanup * Columns tab: DnD column sorting * Columns types should be JSX * New Columns tab UI/X * Use Sortable component on Columns tab * Tests: Grid Settings * Tests: Columns Settings * Tests: Editors for Text, Number, Boolean and Date/Time columns * Tests: Editors for Image and Link columns * Minor UI fix * Trigger build * Debounce inputs
- Loading branch information
1 parent
9f78446
commit 7157244
Showing
40 changed files
with
1,631 additions
and
580 deletions.
There are no files selected for viewing
104 changes: 104 additions & 0 deletions
104
client/app/visualizations/table/Editor/ColumnEditor.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { map, keys } from 'lodash'; | ||
import React from 'react'; | ||
import { useDebouncedCallback } from 'use-debounce'; | ||
import PropTypes from 'prop-types'; | ||
import * as Grid from 'antd/lib/grid'; | ||
import Input from 'antd/lib/input'; | ||
import Radio from 'antd/lib/radio'; | ||
import Checkbox from 'antd/lib/checkbox'; | ||
import Select from 'antd/lib/select'; | ||
import Icon from 'antd/lib/icon'; | ||
import Tooltip from 'antd/lib/tooltip'; | ||
|
||
import ColumnTypes from '../columns'; | ||
|
||
export default function ColumnEditor({ column, onChange }) { | ||
function handleChange(changes) { | ||
onChange({ ...column, ...changes }); | ||
} | ||
|
||
const [handleChangeDebounced] = useDebouncedCallback(handleChange, 200); | ||
|
||
const AdditionalOptions = ColumnTypes[column.displayAs].Editor || null; | ||
|
||
return ( | ||
<div className="table-visualization-editor-column"> | ||
<Grid.Row gutter={15} type="flex" align="middle" className="m-b-15"> | ||
<Grid.Col span={16}> | ||
<Input | ||
data-test={`Table.Column.${column.name}.Title`} | ||
defaultValue={column.title} | ||
onChange={event => handleChangeDebounced({ title: event.target.value })} | ||
/> | ||
</Grid.Col> | ||
<Grid.Col span={8}> | ||
<Radio.Group | ||
className="table-visualization-editor-column-align-content" | ||
defaultValue={column.alignContent} | ||
onChange={event => handleChange({ alignContent: event.target.value })} | ||
> | ||
<Tooltip title="Align left" mouseEnterDelay={0} mouseLeaveDelay={0}> | ||
<Radio.Button value="left" data-test={`Table.Column.${column.name}.AlignLeft`}> | ||
<Icon type="align-left" /> | ||
</Radio.Button> | ||
</Tooltip> | ||
<Tooltip title="Align center" mouseEnterDelay={0} mouseLeaveDelay={0}> | ||
<Radio.Button value="center" data-test={`Table.Column.${column.name}.AlignCenter`}> | ||
<Icon type="align-center" /> | ||
</Radio.Button> | ||
</Tooltip> | ||
<Tooltip title="Align right" mouseEnterDelay={0} mouseLeaveDelay={0}> | ||
<Radio.Button value="right" data-test={`Table.Column.${column.name}.AlignRight`}> | ||
<Icon type="align-right" /> | ||
</Radio.Button> | ||
</Tooltip> | ||
</Radio.Group> | ||
</Grid.Col> | ||
</Grid.Row> | ||
|
||
<div className="m-b-15"> | ||
<label htmlFor={`table-column-editor-${column.name}-allow-search`}> | ||
<Checkbox | ||
id={`table-column-editor-${column.name}-allow-search`} | ||
data-test={`Table.Column.${column.name}.UseForSearch`} | ||
defaultChecked={column.allowSearch} | ||
onChange={event => handleChange({ allowSearch: event.target.checked })} | ||
/> | ||
<span>Use for search</span> | ||
</label> | ||
</div> | ||
|
||
<div className="m-b-15"> | ||
<label htmlFor={`table-column-editor-${column.name}-display-as`}>Display as:</label> | ||
<Select | ||
id={`table-column-editor-${column.name}-display-as`} | ||
data-test={`Table.Column.${column.name}.DisplayAs`} | ||
className="w-100" | ||
defaultValue={column.displayAs} | ||
onChange={displayAs => handleChange({ displayAs })} | ||
> | ||
{map(ColumnTypes, ({ friendlyName }, key) => ( | ||
<Select.Option key={key} data-test={`Table.Column.${column.name}.DisplayAs.${key}`}>{friendlyName}</Select.Option> | ||
))} | ||
</Select> | ||
</div> | ||
|
||
{AdditionalOptions && <AdditionalOptions column={column} onChange={handleChange} />} | ||
</div> | ||
); | ||
} | ||
|
||
ColumnEditor.propTypes = { | ||
column: PropTypes.shape({ | ||
name: PropTypes.string.isRequired, | ||
title: PropTypes.string, | ||
visible: PropTypes.bool, | ||
alignContent: PropTypes.oneOf(['left', 'center', 'right']), | ||
displayAs: PropTypes.oneOf(keys(ColumnTypes)), | ||
}).isRequired, | ||
onChange: PropTypes.func, | ||
}; | ||
|
||
ColumnEditor.defaultProps = { | ||
onChange: () => {}, | ||
}; |
78 changes: 78 additions & 0 deletions
78
client/app/visualizations/table/Editor/ColumnsSettings.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { map } from 'lodash'; | ||
import React from 'react'; | ||
import Collapse from 'antd/lib/collapse'; | ||
import Icon from 'antd/lib/icon'; | ||
import Tooltip from 'antd/lib/tooltip'; | ||
import Typography from 'antd/lib/typography'; | ||
import { sortableElement } from 'react-sortable-hoc'; | ||
import { SortableContainer, DragHandle } from '@/components/sortable'; | ||
import { EditorPropTypes } from '@/visualizations'; | ||
|
||
import ColumnEditor from './ColumnEditor'; | ||
|
||
const { Text } = Typography; | ||
|
||
const SortableItem = sortableElement(Collapse.Panel); | ||
|
||
export default function ColumnsSettings({ options, onOptionsChange }) { | ||
function handleColumnChange(newColumn, event) { | ||
if (event) { | ||
event.stopPropagation(); | ||
} | ||
const columns = map(options.columns, c => (c.name === newColumn.name ? newColumn : c)); | ||
onOptionsChange({ columns }); | ||
} | ||
|
||
function handleColumnsReorder({ oldIndex, newIndex }) { | ||
const columns = [...options.columns]; | ||
columns.splice(newIndex, 0, ...columns.splice(oldIndex, 1)); | ||
onOptionsChange({ columns }); | ||
} | ||
|
||
return ( | ||
<SortableContainer | ||
axis="y" | ||
lockAxis="y" | ||
useDragHandle | ||
helperClass="table-editor-columns-dragged-item" | ||
helperContainer={container => container.firstChild} | ||
onSortEnd={handleColumnsReorder} | ||
containerProps={{ | ||
className: 'table-visualization-editor-columns', | ||
}} | ||
> | ||
<Collapse bordered={false} defaultActiveKey={[]} expandIconPosition="right"> | ||
{map(options.columns, (column, index) => ( | ||
<SortableItem | ||
key={column.name} | ||
index={index} | ||
header={( | ||
<React.Fragment> | ||
<DragHandle /> | ||
<span data-test={`Table.Column.${column.name}.Name`}> | ||
{column.name} | ||
{(column.title !== '') && (column.title !== column.name) && ( | ||
<Text type="secondary" className="m-l-5"><i>({column.title})</i></Text> | ||
)} | ||
</span> | ||
</React.Fragment> | ||
)} | ||
extra={( | ||
<Tooltip title="Toggle visibility" mouseEnterDelay={0} mouseLeaveDelay={0}> | ||
<Icon | ||
data-test={`Table.Column.${column.name}.Visibility`} | ||
type={column.visible ? 'eye' : 'eye-invisible'} | ||
onClick={event => handleColumnChange({ ...column, visible: !column.visible }, event)} | ||
/> | ||
</Tooltip> | ||
)} | ||
> | ||
<ColumnEditor column={column} onChange={handleColumnChange} /> | ||
</SortableItem> | ||
))} | ||
</Collapse> | ||
</SortableContainer> | ||
); | ||
} | ||
|
||
ColumnsSettings.propTypes = EditorPropTypes; |
67 changes: 67 additions & 0 deletions
67
client/app/visualizations/table/Editor/ColumnsSettings.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import React from 'react'; | ||
import enzyme from 'enzyme'; | ||
|
||
import getOptions from '../getOptions'; | ||
import ColumnsSettings from './ColumnsSettings'; | ||
|
||
function findByTestID(wrapper, testId) { | ||
return wrapper.find(`[data-test="${testId}"]`); | ||
} | ||
|
||
function mount(options, done) { | ||
const data = { | ||
columns: [{ name: 'a', type: 'string' }], | ||
rows: [{ a: 'test' }], | ||
}; | ||
options = getOptions(options, data); | ||
return enzyme.mount(( | ||
<ColumnsSettings | ||
visualizationName="Test" | ||
data={data} | ||
options={options} | ||
onOptionsChange={(changedOptions) => { | ||
expect(changedOptions).toMatchSnapshot(); | ||
done(); | ||
}} | ||
/> | ||
)); | ||
} | ||
|
||
describe('Visualizations -> Table -> Editor -> Columns Settings', () => { | ||
test('Toggles column visibility', (done) => { | ||
const el = mount({}, done); | ||
|
||
findByTestID(el, 'Table.Column.a.Visibility').first().simulate('click'); | ||
}); | ||
|
||
test('Changes column title', (done) => { | ||
const el = mount({}, done); | ||
findByTestID(el, 'Table.Column.a.Name').first().simulate('click'); // expand settings | ||
|
||
findByTestID(el, 'Table.Column.a.Title').first().simulate('change', { target: { value: 'test' } }); | ||
}); | ||
|
||
test('Changes column alignment', (done) => { | ||
const el = mount({}, done); | ||
findByTestID(el, 'Table.Column.a.Name').first().simulate('click'); // expand settings | ||
|
||
findByTestID(el, 'Table.Column.a.AlignRight').first().find('input') | ||
.simulate('change', { target: { checked: true } }); | ||
}); | ||
|
||
test('Enables search by column data', (done) => { | ||
const el = mount({}, done); | ||
findByTestID(el, 'Table.Column.a.Name').first().simulate('click'); // expand settings | ||
|
||
findByTestID(el, 'Table.Column.a.UseForSearch').first().find('input') | ||
.simulate('change', { target: { checked: true } }); | ||
}); | ||
|
||
test('Changes column display type', (done) => { | ||
const el = mount({}, done); | ||
findByTestID(el, 'Table.Column.a.Name').first().simulate('click'); // expand settings | ||
|
||
findByTestID(el, 'Table.Column.a.DisplayAs').first().simulate('click'); | ||
findByTestID(el, 'Table.Column.a.DisplayAs.number').first().simulate('click'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { map } from 'lodash'; | ||
import React from 'react'; | ||
import Select from 'antd/lib/select'; | ||
import { EditorPropTypes } from '@/visualizations'; | ||
|
||
const ALLOWED_ITEM_PER_PAGE = [5, 10, 15, 20, 25, 50, 100, 150, 200, 250]; | ||
|
||
export default function GridSettings({ options, onOptionsChange }) { | ||
return ( | ||
<div className="m-b-15"> | ||
<label htmlFor="table-editor-items-per-page">Items per page</label> | ||
<Select | ||
id="table-editor-items-per-page" | ||
data-test="Table.ItemsPerPage" | ||
className="w-100" | ||
defaultValue={options.itemsPerPage} | ||
onChange={itemsPerPage => onOptionsChange({ itemsPerPage })} | ||
> | ||
{map(ALLOWED_ITEM_PER_PAGE, value => ( | ||
<Select.Option key={`ipp${value}`} value={value} data-test={`Table.ItemsPerPage.${value}`}>{value}</Select.Option> | ||
))} | ||
</Select> | ||
</div> | ||
); | ||
} | ||
|
||
GridSettings.propTypes = EditorPropTypes; |
36 changes: 36 additions & 0 deletions
36
client/app/visualizations/table/Editor/GridSettings.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import React from 'react'; | ||
import enzyme from 'enzyme'; | ||
|
||
import getOptions from '../getOptions'; | ||
import GridSettings from './GridSettings'; | ||
|
||
function findByTestID(wrapper, testId) { | ||
return wrapper.find(`[data-test="${testId}"]`); | ||
} | ||
|
||
function mount(options, done) { | ||
const data = { columns: [], rows: [] }; | ||
options = getOptions(options, data); | ||
return enzyme.mount(( | ||
<GridSettings | ||
visualizationName="Test" | ||
data={data} | ||
options={options} | ||
onOptionsChange={(changedOptions) => { | ||
expect(changedOptions).toMatchSnapshot(); | ||
done(); | ||
}} | ||
/> | ||
)); | ||
} | ||
|
||
describe('Visualizations -> Table -> Editor -> Grid Settings', () => { | ||
test('Changes items per page', (done) => { | ||
const el = mount({ | ||
itemsPerPage: 25, | ||
}, done); | ||
|
||
findByTestID(el, 'Table.ItemsPerPage').first().simulate('click'); | ||
findByTestID(el, 'Table.ItemsPerPage.100').first().simulate('click'); | ||
}); | ||
}); |
Oops, something went wrong.