From 324ea079a03ae79e0360662760afbb76f9b9c358 Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Tue, 10 Sep 2019 20:53:59 -0300 Subject: [PATCH 01/10] npm install react-pivottable --- package-lock.json | 86 ++++++++++++++++++++++++++++++++++------------- package.json | 1 + 2 files changed, 63 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index ec83da854f..7b6a020259 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1945,11 +1945,6 @@ "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", "dev": true }, - "async-validator": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.11.5.tgz", - "integrity": "sha512-XNtCsMAeAH1pdLMEg1z8/Bb3a8cdCbui9QbJATRFHHHW5kT6+NPI3zSVQUXgikTFITzsg+kYY5NTWhM2Orwt9w==" - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2531,7 +2526,7 @@ }, "bl": { "version": "1.2.2", - "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "requires": { "readable-stream": "^2.3.5", @@ -2545,7 +2540,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -2763,7 +2758,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -6378,7 +6373,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "dev": true, "requires": { @@ -8884,7 +8879,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -9216,7 +9211,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -9310,7 +9305,7 @@ }, "html-webpack-plugin": { "version": "3.2.0", - "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "dev": true, "requires": { @@ -9565,6 +9560,14 @@ "dev": true, "optional": true }, + "immutability-helper": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/immutability-helper/-/immutability-helper-2.9.1.tgz", + "integrity": "sha512-r/RmRG8xO06s/k+PIaif2r5rGc3j4Yhc01jSBfwPCXDLYZwp/yxralI37Df1mwmuzcCsen/E/ITKcTEvc1PQmQ==", + "requires": { + "invariant": "^2.2.0" + } + }, "immutable": { "version": "3.7.6", "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz", @@ -9957,7 +9960,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "is-observable": { @@ -11515,7 +11518,7 @@ }, "magic-string": { "version": "0.22.5", - "resolved": "http://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", "requires": { "vlq": "^0.2.2" @@ -11627,12 +11630,12 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -11831,7 +11834,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -12027,7 +12030,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -12528,7 +12531,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -12930,7 +12933,7 @@ "dependencies": { "minimist": { "version": "0.0.10", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, @@ -14535,6 +14538,16 @@ "hoist-non-react-statics": "^3.3.0", "lodash": "^4.17.4", "warning": "^4.0.3" + }, + "dependencies": { + "async-validator": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.8.5.tgz", + "integrity": "sha512-tXBM+1m056MAX0E8TL2iCjg8WvSyXu0Zc8LNtYqrVeyoL3+esHRZ4SieE9fKQyyU09uONjnMEjrNBMqT0mbvmA==", + "requires": { + "babel-runtime": "6.x" + } + } } }, "rc-hammerjs": { @@ -14965,6 +14978,18 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-pivottable": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/react-pivottable/-/react-pivottable-0.9.0.tgz", + "integrity": "sha512-fs1pGV5z4BvOXL4iLu79kKCLgR5XINW2ZredJHoPqXEMbJaIv50Eoec1XowWf3i3Dvdb8EYgvNqlF2ggC4GJOw==", + "requires": { + "immutability-helper": "^2.3.1", + "prop-types": "^15.5.10", + "react-draggable": "^3.0.3", + "react-sortablejs": "^1.3.4", + "sortablejs": "^1.6.1" + } + }, "react-resizable": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-1.7.5.tgz", @@ -15011,6 +15036,14 @@ } } }, + "react-sortablejs": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/react-sortablejs/-/react-sortablejs-1.5.1.tgz", + "integrity": "sha512-bKIc1UVhjZt55Nb6WZFxZ8Jwyngg8CTt+w+iG1pA5k9LQsg1J0X6nLppHatSSDZDECtRZKp2z47tmmhPRJNj4g==", + "requires": { + "prop-types": ">=15.0.0" + } + }, "react-test-renderer": { "version": "16.8.3", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.3.tgz", @@ -16141,7 +16174,7 @@ "dependencies": { "minimist": { "version": "0.0.5", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz", "integrity": "sha1-16oye87PUY+RBqxrjwA/o7zqhWY=" } } @@ -16481,6 +16514,11 @@ "sort-desc": "^0.1.1" } }, + "sortablejs": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.9.0.tgz", + "integrity": "sha512-Ot6bYJ6PoqPmpsqQYXjn1+RKrY2NWQvQt/o4jfd/UYwVWndyO5EPO8YHbnm5HIykf8ENsm4JUrdAvolPT86yYA==" + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -16629,7 +16667,7 @@ }, "split": { "version": "0.2.10", - "resolved": "http://registry.npmjs.org/split/-/split-0.2.10.tgz", + "resolved": "https://registry.npmjs.org/split/-/split-0.2.10.tgz", "integrity": "sha1-Zwl8YB1pfOE2j0GPBs0gHPBSGlc=", "requires": { "through": "2" @@ -16987,7 +17025,7 @@ "dependencies": { "readable-stream": { "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { "core-util-is": "~1.0.0", @@ -17121,7 +17159,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { diff --git a/package.json b/package.json index c30a8f3b5c..814f78a073 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "react-ace": "^6.1.0", "react-dom": "^16.8.3", "react-grid-layout": "git+https://github.com/getredash/react-grid-layout.git", + "react-pivottable": "^0.9.0", "react-sortable-hoc": "^1.9.1", "react2angular": "^3.2.1", "ui-select": "^0.19.8" From 489c8d9dbbc7c459bef6aa0cf994ec2f9b3ba2bd Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Tue, 10 Sep 2019 20:55:16 -0300 Subject: [PATCH 02/10] Initiate Pivot Table Migration --- client/app/components/Filters.jsx | 27 ++---- client/app/filters/index.js | 16 ++++ client/app/visualizations/pivot/Renderer.jsx | 54 ++++++++++++ client/app/visualizations/pivot/index.js | 6 +- client/app/visualizations/pivot/renderer.less | 83 +++++++++++++++++++ 5 files changed, 162 insertions(+), 24 deletions(-) create mode 100644 client/app/visualizations/pivot/Renderer.jsx create mode 100644 client/app/visualizations/pivot/renderer.less diff --git a/client/app/components/Filters.jsx b/client/app/components/Filters.jsx index c9c2b78df6..0c6307ea72 100644 --- a/client/app/components/Filters.jsx +++ b/client/app/components/Filters.jsx @@ -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###'; @@ -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; @@ -99,7 +84,7 @@ export function Filters({ filters, onChange }) {
{map(filters, (filter) => { const options = map(filter.values, (value, index) => ( - {formatValue(value, get(filter, 'column.type'))} + {formatColumnValue(value, get(filter, 'column.type'))} )); return ( @@ -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)} > diff --git a/client/app/filters/index.js b/client/app/filters/index.js index 623db500ad..a7231ce640 100644 --- a/client/app/filters/index.js +++ b/client/app/filters/index.js @@ -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', @@ -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; +} diff --git a/client/app/visualizations/pivot/Renderer.jsx b/client/app/visualizations/pivot/Renderer.jsx new file mode 100644 index 0000000000..ba824e6107 --- /dev/null +++ b/client/app/visualizations/pivot/Renderer.jsx @@ -0,0 +1,54 @@ +import React, { useState, useEffect } from 'react'; +import { 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', +]; + +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]); + + const onChange = (updatedOptions) => { + const validOptions = pick(updatedOptions, VALID_OPTIONS); + setConfig(validOptions); + onOptionsChange(validOptions); + }; + + return ( +
+ +
+ ); +} + +Renderer.propTypes = RendererPropTypes; +Renderer.defaultProps = { onOptionsChange: () => {} }; diff --git a/client/app/visualizations/pivot/index.js b/client/app/visualizations/pivot/index.js index 398cb238e7..cc8e7b16f5 100644 --- a/client/app/visualizations/pivot/index.js +++ b/client/app/visualizations/pivot/index.js @@ -3,11 +3,11 @@ import angular from 'angular'; import $ from 'jquery'; import 'pivottable'; import 'pivottable/dist/pivot.css'; -import { angular2react } from 'angular2react'; import { registerVisualization } from '@/visualizations'; import './pivot.less'; +import Renderer from './Renderer'; import Editor from './Editor'; const DEFAULT_OPTIONS = { @@ -67,12 +67,12 @@ const PivotTableRenderer = { export default function init(ngModule) { ngModule.component('pivotTableRenderer', PivotTableRenderer); - ngModule.run(($injector) => { + ngModule.run(() => { registerVisualization({ type: 'PIVOT', name: 'Pivot Table', getOptions: options => merge({}, DEFAULT_OPTIONS, options), - Renderer: angular2react('pivotTableRenderer', PivotTableRenderer, $injector), + Renderer, Editor, defaultRows: 10, diff --git a/client/app/visualizations/pivot/renderer.less b/client/app/visualizations/pivot/renderer.less new file mode 100644 index 0000000000..4385b94c91 --- /dev/null +++ b/client/app/visualizations/pivot/renderer.less @@ -0,0 +1,83 @@ +@redash-gray: rgba(102, 136, 153, 1); + +// TODO: Change .pivot-table-renderer to .pivot-table-visualization-container +.pivot-table-renderer { + &.hide-controls { + .pvtAxisContainer, .pvtRenderer, .pvtVals { + display: none !important; + } + } +} + +.pvtAxisContainer, .pvtVals { + border: 1px solid fade(@redash-gray, 15%); + 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%); + border-radius: 3px; +} + +.pvtCheckContainer { + border-top: 1px solid fade(@redash-gray, 15%); + border-bottom: 1px solid fade(@redash-gray, 15%); +} + +.pvtCheckContainer p { + margin: 2px; + line-height: 1; +} + +.pvtTriangle { + color: fade(@redash-gray, 90%); +} + +table.pvtTable thead tr th, table.pvtTable tbody tr th { + background-color: fade(@redash-gray, 10%); + border: 1px solid #ced8dc; +} + +table.pvtTable tbody tr td { + border: 1px solid #ced8dc; +} + +.pvtFilterBox { + border: 1px solid fade(@redash-gray, 15%); + border-radius: 3px; + box-shadow: fade(@redash-gray, 15%) 0px 4px 9px -3px; + + button { + background-color: rgba(102, 136, 153, 0.15); + margin-right: 5px; + border: 1px solid transparent; + padding: 3px 6px; + font-size: 13px; + line-height: 1.42857143; + border-radius: 3px; + + &:hover { + background-color: rgba(102, 136, 153, 0.25); + } + } + + input[type='text'] { + width: 90%; + margin: 0 auto 10px; + height: 35px; + padding: 6px 12px; + border: 1px solid #e8e8e8; + border-radius: 3px; + } +} From bace703648ac92869f3db311687b939eac3669a1 Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Thu, 12 Sep 2019 20:32:17 -0300 Subject: [PATCH 03/10] Update renderer with editor options --- client/app/visualizations/pivot/Renderer.jsx | 13 +++++++++-- client/app/visualizations/pivot/renderer.less | 22 ++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/client/app/visualizations/pivot/Renderer.jsx b/client/app/visualizations/pivot/Renderer.jsx index ba824e6107..a5c86999ee 100644 --- a/client/app/visualizations/pivot/Renderer.jsx +++ b/client/app/visualizations/pivot/Renderer.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { find, pick, map, mapValues } from 'lodash'; +import { get, find, pick, map, mapValues } from 'lodash'; import PivotTableUI from 'react-pivottable/PivotTableUI'; import { RendererPropTypes } from '@/visualizations'; import { formatColumnValue } from '@/filters'; @@ -24,6 +24,7 @@ const VALID_OPTIONS = [ 'hiddenFromDragDrop', 'menuLimit', 'unusedOrientationCutoff', + 'rendererOptions', ]; function formatRows({ rows, columns }) { @@ -43,8 +44,16 @@ export default function Renderer({ data, options, onOptionsChange }) { onOptionsChange(validOptions); }; + const hideControls = get(options, 'controls.enabled'); + const hideRowTotals = !get(options, 'rendererOptions.table.rowTotals'); + const hideColumnTotals = !get(options, 'rendererOptions.table.colTotals'); return ( -
+
); diff --git a/client/app/visualizations/pivot/renderer.less b/client/app/visualizations/pivot/renderer.less index 4385b94c91..cb397c8ac4 100644 --- a/client/app/visualizations/pivot/renderer.less +++ b/client/app/visualizations/pivot/renderer.less @@ -2,9 +2,25 @@ // TODO: Change .pivot-table-renderer to .pivot-table-visualization-container .pivot-table-renderer { - &.hide-controls { - .pvtAxisContainer, .pvtRenderer, .pvtVals { - display: none !important; + &[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; + } } } } From c82a4c6a5ceeec89708447638ec46cd57eb7ea63 Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Fri, 13 Sep 2019 19:15:53 -0300 Subject: [PATCH 04/10] Clean up --- .../less/inc/visualizations/pivot-table.less | 2 +- client/app/assets/less/redash/query.less | 2 +- client/app/pages/dashboards/dashboard.less | 2 +- client/app/visualizations/pivot/Renderer.jsx | 3 +- client/app/visualizations/pivot/index.js | 74 +++-------------- client/app/visualizations/pivot/pivot.less | 83 ------------------- .../pivot/pivottable-editor.html | 10 --- client/app/visualizations/pivot/renderer.less | 13 +-- 8 files changed, 18 insertions(+), 171 deletions(-) delete mode 100644 client/app/visualizations/pivot/pivot.less delete mode 100644 client/app/visualizations/pivot/pivottable-editor.html diff --git a/client/app/assets/less/inc/visualizations/pivot-table.less b/client/app/assets/less/inc/visualizations/pivot-table.less index 7400914f47..11c44cdb6d 100644 --- a/client/app/assets/less/inc/visualizations/pivot-table.less +++ b/client/app/assets/less/inc/visualizations/pivot-table.less @@ -1,3 +1,3 @@ -.pivot-table-renderer > table, grid-renderer > div, visualization-renderer > div { +.pivot-table-visualization-container > table, grid-renderer > div, visualization-renderer > div { overflow: auto; } diff --git a/client/app/assets/less/redash/query.less b/client/app/assets/less/redash/query.less index 3b3404d415..b08e5c2e5a 100644 --- a/client/app/assets/less/redash/query.less +++ b/client/app/assets/less/redash/query.less @@ -343,7 +343,7 @@ a.label-tag { border-bottom: 1px solid #efefef; } - .pivot-table-renderer > table, grid-renderer > div, visualization-renderer > div { + .pivot-table-visualization-container > table, grid-renderer > div, visualization-renderer > div { overflow: visible; } diff --git a/client/app/pages/dashboards/dashboard.less b/client/app/pages/dashboards/dashboard.less index 97e039c625..0bcf022fe0 100644 --- a/client/app/pages/dashboards/dashboard.less +++ b/client/app/pages/dashboards/dashboard.less @@ -22,7 +22,7 @@ padding: 0; } - .pivot-table-renderer > table, grid-renderer > div, visualization-renderer > div { + .pivot-table-visualization-container > table, grid-renderer > div, visualization-renderer > div { overflow: visible; } diff --git a/client/app/visualizations/pivot/Renderer.jsx b/client/app/visualizations/pivot/Renderer.jsx index a5c86999ee..be82b27ace 100644 --- a/client/app/visualizations/pivot/Renderer.jsx +++ b/client/app/visualizations/pivot/Renderer.jsx @@ -44,12 +44,13 @@ export default function Renderer({ data, options, onOptionsChange }) { 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 (
- `, - 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(() => { - registerVisualization({ - type: 'PIVOT', - name: 'Pivot Table', - getOptions: options => merge({}, DEFAULT_OPTIONS, options), - Renderer, - 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, }); } diff --git a/client/app/visualizations/pivot/pivot.less b/client/app/visualizations/pivot/pivot.less deleted file mode 100644 index efe0c5eff5..0000000000 --- a/client/app/visualizations/pivot/pivot.less +++ /dev/null @@ -1,83 +0,0 @@ -@redash-gray: rgba(102, 136, 153, 1); - -.pivot-table-renderer { - &.hide-controls { - .pvtAxisContainer, .pvtRenderer, .pvtVals { - display: none !important; - } - } -} - -.pvtAxisContainer, .pvtVals { - border: 1px solid fade(@redash-gray, 15%); - 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%); - border-radius: 3px; -} - -.pvtCheckContainer { - border-top: 1px solid fade(@redash-gray, 15%); - border-bottom: 1px solid fade(@redash-gray, 15%); -} - -.pvtCheckContainer p { - margin: 2px; - line-height: 1; -} - -.pvtTriangle { - color: fade(@redash-gray, 90%); -} - -table.pvtTable thead tr th, table.pvtTable tbody tr th { - background-color: fade(@redash-gray, 10%); - border: 1px solid #ced8dc; -} - -table.pvtTable tbody tr td { - border: 1px solid #ced8dc; -} - -.pvtFilterBox { - border: 1px solid fade(@redash-gray, 15%); - border-radius: 3px; - box-shadow: fade(@redash-gray, 15%) 0px 4px 9px -3px; - - button { - background-color: rgba(102, 136, 153, 0.15); - margin-right: 5px; - border: 1px solid transparent; - padding: 3px 6px; - font-size: 13px; - line-height: 1.42857143; - border-radius: 3px; - - &:hover { - background-color: rgba(102, 136, 153, 0.25); - } - } - - input[type='text'] { - width: 90%; - margin: 0 auto 10px; - height: 35px; - padding: 6px 12px; - border: 1px solid #e8e8e8; - border-radius: 3px; - } -} - diff --git a/client/app/visualizations/pivot/pivottable-editor.html b/client/app/visualizations/pivot/pivottable-editor.html deleted file mode 100644 index 51ae6d194b..0000000000 --- a/client/app/visualizations/pivot/pivottable-editor.html +++ /dev/null @@ -1,10 +0,0 @@ -
-
-
- -
-
-
diff --git a/client/app/visualizations/pivot/renderer.less b/client/app/visualizations/pivot/renderer.less index cb397c8ac4..3702e06167 100644 --- a/client/app/visualizations/pivot/renderer.less +++ b/client/app/visualizations/pivot/renderer.less @@ -1,7 +1,6 @@ @redash-gray: rgba(102, 136, 153, 1); -// TODO: Change .pivot-table-renderer to .pivot-table-visualization-container -.pivot-table-renderer { +.pivot-table-visualization-container { &[data-hide-controls] { .pvtAxisContainer, .pvtRenderers, .pvtVals { display: none; @@ -30,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%); From 4032c1cef904ecc8fd181110087d5eb483df14df Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Fri, 13 Sep 2019 19:23:34 -0300 Subject: [PATCH 05/10] Remove old pivottable from package.json --- client/app/visualizations/pivot/index.js | 2 -- package-lock.json | 8 -------- package.json | 1 - 3 files changed, 11 deletions(-) diff --git a/client/app/visualizations/pivot/index.js b/client/app/visualizations/pivot/index.js index 7e84112333..d6113d6de0 100644 --- a/client/app/visualizations/pivot/index.js +++ b/client/app/visualizations/pivot/index.js @@ -1,6 +1,4 @@ import { merge } from 'lodash'; -import 'pivottable'; -import 'pivottable/dist/pivot.css'; import { registerVisualization } from '@/visualizations'; import Renderer from './Renderer'; diff --git a/package-lock.json b/package-lock.json index 7b6a020259..02ff4e93e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13354,14 +13354,6 @@ "node-modules-regexp": "^1.0.0" } }, - "pivottable": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/pivottable/-/pivottable-2.23.0.tgz", - "integrity": "sha512-6WRaiiI0mU5JxzNMWbtf3vfrBvBhBPIUbwu2Q7Nv7fVCxIvlmFqXSldMwmHAsiEFwdZdUrpQHqIu+N3jZUezyg==", - "requires": { - "jquery": ">=1.9.0" - } - }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", diff --git a/package.json b/package.json index 814f78a073..bc91cd77f3 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,6 @@ "mustache": "^2.3.0", "numeral": "^2.0.6", "pace-progress": "git+https://github.com/getredash/pace.git", - "pivottable": "^2.15.0", "plotly.js": "1.41.3", "prop-types": "^15.6.1", "qs": "^6.7.0", From 9a3c74b7ff57108f9ac5756853a5059a39831c85 Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Mon, 16 Sep 2019 12:04:24 -0300 Subject: [PATCH 06/10] Test Percy Snapshot with Pivot Table in a Dashboard --- .../integration/visualizations/pivot_spec.js | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/client/cypress/integration/visualizations/pivot_spec.js b/client/cypress/integration/visualizations/pivot_spec.js index d1003a86c7..3b69be85a1 100644 --- a/client/cypress/integration/visualizations/pivot_spec.js +++ b/client/cypress/integration/visualizations/pivot_spec.js @@ -1,6 +1,6 @@ /* global cy, Cypress */ -import { createQuery } from '../../support/redash-api'; +import { createQuery, createVisualization, createDashboard, addWidget } from '../../support/redash-api'; const { get } = Cypress._; @@ -15,19 +15,21 @@ const SQL = ` SELECT 'c' AS stage1, 'c1' AS stage2, 19 AS value UNION ALL SELECT 'c' AS stage1, 'c2' AS stage2, 92 AS value UNION ALL SELECT 'c' AS stage1, 'c3' AS stage2, 63 AS value UNION ALL - SELECT 'c' AS stage1, 'c4' AS stage2, 44 AS v + SELECT 'c' AS stage1, 'c4' AS stage2, 44 AS value `; describe('Pivot', () => { beforeEach(() => { cy.login(); - createQuery({ query: SQL }).then(({ id }) => { - cy.visit(`queries/${id}/source`); - cy.getByTestId('ExecuteButton').click(); - }); + createQuery({ name: 'Pivot Visualization', query: SQL }) + .its('id') + .as('queryId'); }); - it('creates Pivot with controls', () => { + it('creates Pivot with controls', function () { + cy.visit(`queries/${this.queryId}/source`); + cy.getByTestId('ExecuteButton').click(); + const visualizationName = 'Pivot'; cy.getByTestId('NewVisualization').click(); @@ -39,7 +41,10 @@ describe('Pivot', () => { cy.getByTestId('QueryPageVisualizationTabs').contains('li', visualizationName).should('exist'); }); - it('creates Pivot without controls', () => { + it('creates Pivot without controls', function () { + cy.visit(`queries/${this.queryId}/source`); + cy.getByTestId('ExecuteButton').click(); + const visualizationName = 'Pivot'; cy.server(); @@ -66,4 +71,37 @@ describe('Pivot', () => { .should('be.not.visible'); }); }); + + it('takes a snapshot with different configured Pivots', function () { + const options = { + aggregatorName: 'Sum', + controls: { enabled: true }, + cols: ['stage1'], + rows: ['stage2'], + vals: ['value'], + }; + + const pivotWithControls = { ...options, controls: { enabled: false } }; + const pivotWithoutRowTotals = { ...options, rendererOptions: { table: { rowTotals: false } } }; + const pivotWithoutColTotals = { ...options, rendererOptions: { table: { colTotals: false } } }; + + createVisualization(this.queryId, 'PIVOT', 'Pivot', options) + .then((visualization) => { this.pivotId = visualization.id; }) + .then(() => createVisualization(this.queryId, 'PIVOT', 'Pivot with Controls', pivotWithControls)) + .then((visualization) => { this.pivotWithControlsId = visualization.id; }) + .then(() => createVisualization(this.queryId, 'PIVOT', 'Pivot without Row Totals', pivotWithoutRowTotals)) + .then((visualization) => { this.pivotWithoutRowTotals = visualization.id; }) + .then(() => createVisualization(this.queryId, 'PIVOT', 'Pivot without Col Totals', pivotWithoutColTotals)) + .then((visualization) => { this.pivotWithoutColTotals = visualization.id; }) + .then(() => createDashboard('Pivot Visualization')) + .then(({ slug, id }) => { + addWidget(id, this.pivotId, { position: { autoHeight: false, sizeY: 10, sizeX: 2 } }); + addWidget(id, this.pivotWithoutRowTotals, { position: { autoHeight: false, col: 2, sizeY: 10, sizeX: 2 } }); + addWidget(id, this.pivotWithoutColTotals, { position: { autoHeight: false, col: 4, sizeY: 10, sizeX: 2 } }); + addWidget(id, this.pivotWithControlsId, { position: { autoHeight: false, row: 9, sizeY: 13 } }); + cy.visit(`/dashboard/${slug}`); + cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting + cy.percySnapshot('Visualizations - Pivot Table'); + }); + }); }); From 26090dda94d61109665a120254d5b494b39a584b Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Mon, 16 Sep 2019 12:29:50 -0300 Subject: [PATCH 07/10] Tmp: use cy.wait to make sure dashboard is loaded --- client/cypress/integration/visualizations/pivot_spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/cypress/integration/visualizations/pivot_spec.js b/client/cypress/integration/visualizations/pivot_spec.js index 3b69be85a1..43fe64400a 100644 --- a/client/cypress/integration/visualizations/pivot_spec.js +++ b/client/cypress/integration/visualizations/pivot_spec.js @@ -100,7 +100,9 @@ describe('Pivot', () => { addWidget(id, this.pivotWithoutColTotals, { position: { autoHeight: false, col: 4, sizeY: 10, sizeX: 2 } }); addWidget(id, this.pivotWithControlsId, { position: { autoHeight: false, row: 9, sizeY: 13 } }); cy.visit(`/dashboard/${slug}`); - cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting + + // temporary waiting for testing purposes + cy.wait(10000); // eslint-disable-line cypress/no-unnecessary-waiting cy.percySnapshot('Visualizations - Pivot Table'); }); }); From b7b325e09441ae95f39cc8ce07ea8402baae2135 Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Mon, 16 Sep 2019 13:56:59 -0300 Subject: [PATCH 08/10] Clean up Percy snapshot test --- client/app/visualizations/pivot/Renderer.jsx | 1 + .../integration/visualizations/pivot_spec.js | 52 +++++++++++-------- client/cypress/support/commands.js | 11 ++++ 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/client/app/visualizations/pivot/Renderer.jsx b/client/app/visualizations/pivot/Renderer.jsx index be82b27ace..7eb67c625d 100644 --- a/client/app/visualizations/pivot/Renderer.jsx +++ b/client/app/visualizations/pivot/Renderer.jsx @@ -54,6 +54,7 @@ export default function Renderer({ data, options, onOptionsChange }) { data-hide-controls={hideControls || null} data-hide-row-totals={hideRowTotals || null} data-hide-column-totals={hideColumnTotals || null} + data-test="PivotTableVisualization" >
diff --git a/client/cypress/integration/visualizations/pivot_spec.js b/client/cypress/integration/visualizations/pivot_spec.js index 43fe64400a..3b8dc265bd 100644 --- a/client/cypress/integration/visualizations/pivot_spec.js +++ b/client/cypress/integration/visualizations/pivot_spec.js @@ -1,6 +1,7 @@ /* global cy, Cypress */ import { createQuery, createVisualization, createDashboard, addWidget } from '../../support/redash-api'; +import { getWidgetTestId } from '../../support/dashboard'; const { get } = Cypress._; @@ -81,28 +82,35 @@ describe('Pivot', () => { vals: ['value'], }; - const pivotWithControls = { ...options, controls: { enabled: false } }; - const pivotWithoutRowTotals = { ...options, rendererOptions: { table: { rowTotals: false } } }; - const pivotWithoutColTotals = { ...options, rendererOptions: { table: { colTotals: false } } }; - - createVisualization(this.queryId, 'PIVOT', 'Pivot', options) - .then((visualization) => { this.pivotId = visualization.id; }) - .then(() => createVisualization(this.queryId, 'PIVOT', 'Pivot with Controls', pivotWithControls)) - .then((visualization) => { this.pivotWithControlsId = visualization.id; }) - .then(() => createVisualization(this.queryId, 'PIVOT', 'Pivot without Row Totals', pivotWithoutRowTotals)) - .then((visualization) => { this.pivotWithoutRowTotals = visualization.id; }) - .then(() => createVisualization(this.queryId, 'PIVOT', 'Pivot without Col Totals', pivotWithoutColTotals)) - .then((visualization) => { this.pivotWithoutColTotals = visualization.id; }) - .then(() => createDashboard('Pivot Visualization')) - .then(({ slug, id }) => { - addWidget(id, this.pivotId, { position: { autoHeight: false, sizeY: 10, sizeX: 2 } }); - addWidget(id, this.pivotWithoutRowTotals, { position: { autoHeight: false, col: 2, sizeY: 10, sizeX: 2 } }); - addWidget(id, this.pivotWithoutColTotals, { position: { autoHeight: false, col: 4, sizeY: 10, sizeX: 2 } }); - addWidget(id, this.pivotWithControlsId, { position: { autoHeight: false, row: 9, sizeY: 13 } }); - cy.visit(`/dashboard/${slug}`); - - // temporary waiting for testing purposes - cy.wait(10000); // eslint-disable-line cypress/no-unnecessary-waiting + const pivotTables = [ + { name: 'Pivot', + options, + position: { autoHeight: false, sizeY: 10, sizeX: 2 } }, + { name: 'Pivot without Row Totals', + options: { ...options, rendererOptions: { table: { rowTotals: false } } }, + position: { autoHeight: false, col: 2, sizeY: 10, sizeX: 2 } }, + { name: 'Pivot without Col Totals', + options: { ...options, rendererOptions: { table: { colTotals: false } } }, + position: { autoHeight: false, col: 4, sizeY: 10, sizeX: 2 } }, + { name: 'Pivot with Controls', + options: { ...options, controls: { enabled: false } }, + position: { autoHeight: false, row: 9, sizeY: 13 } }, + ]; + + createDashboard('Pivot Visualization') + .then((dashboard) => { + this.dashboardUrl = `/dashboard/${dashboard.slug}`; + return cy.all( + pivotTables.map(pivot => () => createVisualization(this.queryId, 'PIVOT', pivot.name, pivot.options) + .then(visualization => addWidget(dashboard.id, visualization.id, { position: pivot.position }))), + ); + }) + .then((widgets) => { + cy.visit(this.dashboardUrl); + widgets.forEach((widget) => { + cy.getByTestId(getWidgetTestId(widget)) + .within(() => cy.getByTestId('PivotTableVisualization').should('exist')); + }); cy.percySnapshot('Visualizations - Pivot Table'); }); }); diff --git a/client/cypress/support/commands.js b/client/cypress/support/commands.js index 67fd12244d..94997a4f6c 100644 --- a/client/cypress/support/commands.js +++ b/client/cypress/support/commands.js @@ -65,3 +65,14 @@ Cypress.Commands.add('dragBy', { prevSubject: true }, (subject, offsetLeft, offs .trigger('mousemove', offsetLeft, offsetTop, { force }) .trigger('mouseup', { force }); }); + +Cypress.Commands.add('all', (fns) => { + const results = []; + + fns.reduce((prev, fn) => { + fn().then(result => results.push(result)); + return results; + }, results); + + return cy.wrap(results); +}); From 88096e2b5564c777656aa8a2d868a4dcaa19179b Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Mon, 16 Sep 2019 18:39:50 -0300 Subject: [PATCH 09/10] Small improvements - cy.all with multiple args - add controls to pivot valid options --- client/app/visualizations/pivot/Renderer.jsx | 1 + client/cypress/support/commands.js | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/client/app/visualizations/pivot/Renderer.jsx b/client/app/visualizations/pivot/Renderer.jsx index 7eb67c625d..d1cc5e8c30 100644 --- a/client/app/visualizations/pivot/Renderer.jsx +++ b/client/app/visualizations/pivot/Renderer.jsx @@ -24,6 +24,7 @@ const VALID_OPTIONS = [ 'hiddenFromDragDrop', 'menuLimit', 'unusedOrientationCutoff', + 'controls', 'rendererOptions', ]; diff --git a/client/cypress/support/commands.js b/client/cypress/support/commands.js index 94997a4f6c..a401a804af 100644 --- a/client/cypress/support/commands.js +++ b/client/cypress/support/commands.js @@ -66,7 +66,12 @@ Cypress.Commands.add('dragBy', { prevSubject: true }, (subject, offsetLeft, offs .trigger('mouseup', { force }); }); -Cypress.Commands.add('all', (fns) => { +Cypress.Commands.add('all', (...functions) => { + if (Cypress._.isEmpty(functions)) { + return []; + } + + const fns = Cypress._.isArray(functions[0]) ? functions[0] : functions; const results = []; fns.reduce((prev, fn) => { From d8a9a890b8720cbadc037b409724ab18c1c1651d Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Mon, 16 Sep 2019 19:14:52 -0300 Subject: [PATCH 10/10] Watch for options in the Renderer --- client/app/visualizations/pivot/Renderer.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/app/visualizations/pivot/Renderer.jsx b/client/app/visualizations/pivot/Renderer.jsx index d1cc5e8c30..8209db956c 100644 --- a/client/app/visualizations/pivot/Renderer.jsx +++ b/client/app/visualizations/pivot/Renderer.jsx @@ -37,7 +37,7 @@ export default function Renderer({ data, options, onOptionsChange }) { useEffect(() => { setConfig({ data: formatRows(data), ...options }); - }, [data]); + }, [data, options]); const onChange = (updatedOptions) => { const validOptions = pick(updatedOptions, VALID_OPTIONS);