Skip to content

Commit

Permalink
Migrate Pivot Table visualization to React (#4133)
Browse files Browse the repository at this point in the history
* npm install react-pivottable

* Initiate Pivot Table Migration

* Update renderer with editor options

* Clean up

* Remove old pivottable from package.json

* Test Percy Snapshot with Pivot Table in a Dashboard

* Tmp: use cy.wait to make sure dashboard is loaded

* Clean up Percy snapshot test

* Small improvements
- cy.all with multiple args
- add controls to pivot valid options

* Watch for options in the Renderer
  • Loading branch information
gabrieldutra authored and arikfr committed Sep 22, 2019
1 parent cb654b3 commit fd435d2
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 155 deletions.
2 changes: 1 addition & 1 deletion client/app/assets/less/inc/visualizations/pivot-table.less
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.pivot-table-renderer > table,
.pivot-table-visualization-container > table,
.visualization-renderer > .visualization-renderer-wrapper {
overflow: auto;
}
2 changes: 1 addition & 1 deletion client/app/assets/less/redash/query.less
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ a.label-tag {
border-bottom: 1px solid #efefef;
}

.pivot-table-renderer > table,
.pivot-table-visualization-container > table,
.visualization-renderer > .visualization-renderer-wrapper {
overflow: visible;
}
Expand Down
27 changes: 6 additions & 21 deletions client/app/components/Filters.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { isArray, indexOf, get, map, includes, every, some, toNumber, toLower } from 'lodash';
import { isArray, indexOf, get, map, includes, every, some, toNumber } from 'lodash';
import moment from 'moment';
import React from 'react';
import PropTypes from 'prop-types';
import { react2angular } from 'react2angular';
import Select from 'antd/lib/select';
import { formatDateTime, formatDate } from '@/filters/datetime';
import { formatColumnValue } from '@/filters';

const ALL_VALUES = '###Redash::Filters::SelectAll###';
const NONE_VALUES = '###Redash::Filters::Clear###';
Expand Down Expand Up @@ -71,21 +71,6 @@ export function filterData(rows, filters = []) {
return result;
}

function formatValue(value, columnType) {
if (moment.isMoment(value)) {
if (columnType === 'date') {
return formatDate(value);
}
return formatDateTime(value);
}

if (typeof value === 'boolean') {
return value.toString();
}

return value;
}

export function Filters({ filters, onChange }) {
if (filters.length === 0) {
return null;
Expand All @@ -99,7 +84,7 @@ export function Filters({ filters, onChange }) {
<div className="row">
{map(filters, (filter) => {
const options = map(filter.values, (value, index) => (
<Select.Option key={index}>{formatValue(value, get(filter, 'column.type'))}</Select.Option>
<Select.Option key={index}>{formatColumnValue(value, get(filter, 'column.type'))}</Select.Option>
));

return (
Expand All @@ -115,10 +100,10 @@ export function Filters({ filters, onChange }) {
mode={filter.multiple ? 'multiple' : 'default'}
value={isArray(filter.current) ?
map(filter.current,
value => ({ key: `${indexOf(filter.values, value)}`, label: formatValue(value) })) :
({ key: `${indexOf(filter.values, filter.current)}`, label: formatValue(filter.current) })}
value => ({ key: `${indexOf(filter.values, value)}`, label: formatColumnValue(value) })) :
({ key: `${indexOf(filter.values, filter.current)}`, label: formatColumnValue(filter.current) })}
allowClear={filter.multiple}
filterOption={(searchText, option) => includes(toLower(option.props.children), toLower(searchText))}
optionFilterProp="children"
showSearch
onChange={values => onChange(filter, values)}
>
Expand Down
16 changes: 16 additions & 0 deletions client/app/filters/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import moment from 'moment';
import { capitalize as _capitalize, isEmpty } from 'lodash';
import { formatDate, formatDateTime } from './datetime';

export const IntervalEnum = {
NEVER: 'Never',
Expand Down Expand Up @@ -168,3 +169,18 @@ export function join(arr) {

return arr.join(' / ');
}

export function formatColumnValue(value, columnType = null) {
if (moment.isMoment(value)) {
if (columnType === 'date') {
return formatDate(value);
}
return formatDateTime(value);
}

if (typeof value === 'boolean') {
return value.toString();
}

return value;
}
2 changes: 1 addition & 1 deletion client/app/pages/dashboards/dashboard.less
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
padding: 0;
}

.pivot-table-renderer > table,
.pivot-table-visualization-container > table,
.visualization-renderer > .visualization-renderer-wrapper {
overflow: visible;
}
Expand Down
66 changes: 66 additions & 0 deletions client/app/visualizations/pivot/Renderer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, { useState, useEffect } from 'react';
import { get, find, pick, map, mapValues } from 'lodash';
import PivotTableUI from 'react-pivottable/PivotTableUI';
import { RendererPropTypes } from '@/visualizations';
import { formatColumnValue } from '@/filters';

import 'react-pivottable/pivottable.css';
import './renderer.less';

const VALID_OPTIONS = [
'data',
'rows',
'cols',
'vals',
'aggregatorName',
'valueFilter',
'sorters',
'rowOrder',
'colOrder',
'derivedAttributes',
'rendererName',
'hiddenAttributes',
'hiddenFromAggregators',
'hiddenFromDragDrop',
'menuLimit',
'unusedOrientationCutoff',
'controls',
'rendererOptions',
];

function formatRows({ rows, columns }) {
return map(rows, row => mapValues(row, (value, key) => formatColumnValue(value, find(columns, { name: key }).type)));
}

export default function Renderer({ data, options, onOptionsChange }) {
const [config, setConfig] = useState({ data: formatRows(data), ...options });

useEffect(() => {
setConfig({ data: formatRows(data), ...options });
}, [data, options]);

const onChange = (updatedOptions) => {
const validOptions = pick(updatedOptions, VALID_OPTIONS);
setConfig(validOptions);
onOptionsChange(validOptions);
};

// Legacy behavior: hideControls when controls.enabled is true
const hideControls = get(options, 'controls.enabled');
const hideRowTotals = !get(options, 'rendererOptions.table.rowTotals');
const hideColumnTotals = !get(options, 'rendererOptions.table.colTotals');
return (
<div
className="pivot-table-visualization-container"
data-hide-controls={hideControls || null}
data-hide-row-totals={hideRowTotals || null}
data-hide-column-totals={hideColumnTotals || null}
data-test="PivotTableVisualization"
>
<PivotTableUI {...pick(config, VALID_OPTIONS)} onChange={onChange} />
</div>
);
}

Renderer.propTypes = RendererPropTypes;
Renderer.defaultProps = { onOptionsChange: () => {} };
78 changes: 13 additions & 65 deletions client/app/visualizations/pivot/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { merge, omit } from 'lodash';
import angular from 'angular';
import $ from 'jquery';
import 'pivottable';
import 'pivottable/dist/pivot.css';
import { angular2react } from 'angular2react';
import { merge } from 'lodash';
import { registerVisualization } from '@/visualizations';

import './pivot.less';

import Renderer from './Renderer';
import Editor from './Editor';

const DEFAULT_OPTIONS = {
Expand All @@ -22,63 +16,17 @@ const DEFAULT_OPTIONS = {
},
};

const PivotTableRenderer = {
template: `
<div class="pivot-table-renderer" ng-class="{'hide-controls': $ctrl.options.controls.enabled}"></div>
`,
bindings: {
data: '<',
options: '<',
onOptionsChange: '<',
},
controller($scope, $element) {
const update = () => {
// We need to give the pivot table its own copy of the data, because it changes
// it which interferes with other visualizations.
const data = angular.copy(this.data.rows);
const options = {
renderers: $.pivotUtilities.renderers,
onRefresh: (config) => {
if (this.onOptionsChange) {
config = omit(config, [
// delete some values which are functions
'aggregators',
'renderers',
'onRefresh',
// delete some bulky de
'localeStrings',
]);
this.onOptionsChange(config);
}
},
...this.options,
};

$('.pivot-table-renderer', $element).pivotUI(data, options, true);
};

$scope.$watch('$ctrl.data', update);
// `options.controls.enabled` is not related to pivot renderer, it's handled by `ng-if`,
// so re-render only if other options changed
$scope.$watch(() => omit(this.options, 'controls'), update, true);
},
};

export default function init(ngModule) {
ngModule.component('pivotTableRenderer', PivotTableRenderer);

ngModule.run(($injector) => {
registerVisualization({
type: 'PIVOT',
name: 'Pivot Table',
getOptions: options => merge({}, DEFAULT_OPTIONS, options),
Renderer: angular2react('pivotTableRenderer', PivotTableRenderer, $injector),
Editor,

defaultRows: 10,
defaultColumns: 3,
minColumns: 2,
});
export default function init() {
registerVisualization({
type: 'PIVOT',
name: 'Pivot Table',
getOptions: options => merge({}, DEFAULT_OPTIONS, options),
Renderer,
Editor,

defaultRows: 10,
defaultColumns: 3,
minColumns: 2,
});
}

Expand Down
10 changes: 0 additions & 10 deletions client/app/visualizations/pivot/pivottable-editor.html

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
@redash-gray: rgba(102, 136, 153, 1);

.pivot-table-renderer {
&.hide-controls {
.pvtAxisContainer, .pvtRenderer, .pvtVals {
display: none !important;
.pivot-table-visualization-container {
&[data-hide-controls] {
.pvtAxisContainer, .pvtRenderers, .pvtVals {
display: none;
}
}

&[data-hide-row-totals] {
td:last-child, th:last-child {
&.pvtTotalLabel:not(:empty), &.pvtTotal, &.pvtGrandTotal {
display: none;
}
}
}

&[data-hide-column-totals] {
tbody > tr:last-child {
& > .pvtTotalLabel, & > .pvtTotal, & > .pvtGrandTotal {
display: none;
}
}
}
}
Expand All @@ -13,16 +29,6 @@
background: #fff;
}

.pvtUi {
td, th {
padding: 5px;
}

li.ui-sortable-handle {
padding: 5px 5px 5px 0;
}
}

.pvtAxisContainer li span.pvtAttr {
background: fade(@redash-gray, 10%);
border: 1px solid fade(@redash-gray, 15%);
Expand Down Expand Up @@ -80,4 +86,3 @@ table.pvtTable tbody tr td {
border-radius: 3px;
}
}

Loading

0 comments on commit fd435d2

Please sign in to comment.