From 66403f1876bb7bcf31eb8187517c116a2d5b6b96 Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Tue, 30 May 2017 11:05:18 -0700 Subject: [PATCH] [explore] viz type selector as modal (#2787) * [explore] viz type selector as modal * Addressing comments * Adressing comments --- .../explore/components/Control.jsx | 2 + .../components/controls/VizTypeControl.jsx | 108 ++++++++++++++++++ superset/assets/javascripts/explore/main.css | 25 ++++ .../javascripts/explore/stores/controls.jsx | 9 +- .../javascripts/explore/stores/visTypes.js | 2 +- superset/assets/package.json | 8 +- .../components/VizTypeControl_spec.jsx | 37 ++++++ .../stylesheets/less/cosmo/bootswatch.less | 5 + 8 files changed, 184 insertions(+), 12 deletions(-) create mode 100644 superset/assets/javascripts/explore/components/controls/VizTypeControl.jsx create mode 100644 superset/assets/spec/javascripts/explorev2/components/VizTypeControl_spec.jsx diff --git a/superset/assets/javascripts/explore/components/Control.jsx b/superset/assets/javascripts/explore/components/Control.jsx index cd9e732dfd998..ec7d1169ac009 100644 --- a/superset/assets/javascripts/explore/components/Control.jsx +++ b/superset/assets/javascripts/explore/components/Control.jsx @@ -7,6 +7,7 @@ import HiddenControl from './controls/HiddenControl'; import SelectControl from './controls/SelectControl'; import TextAreaControl from './controls/TextAreaControl'; import TextControl from './controls/TextControl'; +import VizTypeControl from './controls/VizTypeControl'; const controlMap = { CheckboxControl, @@ -15,6 +16,7 @@ const controlMap = { SelectControl, TextAreaControl, TextControl, + VizTypeControl, }; const controlTypes = Object.keys(controlMap); diff --git a/superset/assets/javascripts/explore/components/controls/VizTypeControl.jsx b/superset/assets/javascripts/explore/components/controls/VizTypeControl.jsx new file mode 100644 index 0000000000000..807aeac335bff --- /dev/null +++ b/superset/assets/javascripts/explore/components/controls/VizTypeControl.jsx @@ -0,0 +1,108 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Label, Row, Col, FormControl, Modal } from 'react-bootstrap'; +import visTypes from '../../stores/visTypes'; +import ControlHeader from '../ControlHeader'; + +const propTypes = { + description: PropTypes.string, + label: PropTypes.string, + name: PropTypes.string.isRequired, + onChange: PropTypes.func, + value: PropTypes.string.isRequired, +}; + +const defaultProps = { + onChange: () => {}, +}; + +export default class VizTypeControl extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + showModal: false, + filter: '', + }; + this.toggleModal = this.toggleModal.bind(this); + this.changeSearch = this.changeSearch.bind(this); + } + onChange(vizType) { + this.props.onChange(vizType); + this.setState({ showModal: false }); + } + toggleModal() { + this.setState({ showModal: !this.state.showModal }); + } + changeSearch(event) { + this.setState({ filter: event.target.value }); + } + renderVizType(vizType) { + const vt = vizType; + return ( +
+ {`viz-type-${vt}`} +
+ {visTypes[vt].label} +
+
); + } + render() { + const filter = this.state.filter; + const filteredVizTypes = Object.keys(visTypes) + .filter(vt => filter.length === 0 || visTypes[vt].label.toLowerCase().includes(filter)); + + const imgPerRow = 4; + const rows = []; + for (let i = 0; i <= filteredVizTypes.length; i += imgPerRow) { + rows.push( + + {filteredVizTypes.slice(i, i + imgPerRow).map(vt => ( + + {this.renderVizType(vt)} + + ))} + ); + } + return ( +
+ edit + } + /> + + + + Select a visualization type + + +
+ +
+ {rows} +
+
+
); + } +} + +VizTypeControl.propTypes = propTypes; +VizTypeControl.defaultProps = defaultProps; diff --git a/superset/assets/javascripts/explore/main.css b/superset/assets/javascripts/explore/main.css index 403221cb3de6b..ab04eaa45688a 100644 --- a/superset/assets/javascripts/explore/main.css +++ b/superset/assets/javascripts/explore/main.css @@ -34,3 +34,28 @@ .background-transparent { background-color: transparent !important; } + +.viztype-label { + text-align: center; +} + +.viztype-selector-container { + cursor: pointer; + margin-top: 10px; + margin-bottom: 10px; + padding: 5px; + border: 1px solid #aaa; +} + +.viztype-selector-container:hover { + border: 1px solid #000; +} + +.viztype-selector-container.selected { + cursor: not-allowed; + opacity: 0.5; +} +.viztype-selector-container.selected { + cursor: not-allowed; + border: 1px solid #aaa; +} diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx index 693201d5c4914..9a96bbbb43d35 100644 --- a/superset/assets/javascripts/explore/stores/controls.jsx +++ b/superset/assets/javascripts/explore/stores/controls.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { formatSelectOptionsForRange, formatSelectOptions } from '../../modules/utils'; -import visTypes from './visTypes'; import * as v from '../validators'; const D3_FORMAT_DOCS = 'D3 format syntax: https://github.com/d3/d3-format'; @@ -48,15 +47,9 @@ export const controls = { }, viz_type: { - type: 'SelectControl', + type: 'VizTypeControl', label: 'Visualization Type', - clearable: false, default: 'table', - choices: Object.keys(visTypes).map(vt => [ - vt, - visTypes[vt].label, - `/static/assets/images/viz_thumbnails/${vt}.png`, - ]), description: 'The type of visualization to display', }, diff --git a/superset/assets/javascripts/explore/stores/visTypes.js b/superset/assets/javascripts/explore/stores/visTypes.js index 83a5afb3d8ccb..8e57633c89126 100644 --- a/superset/assets/javascripts/explore/stores/visTypes.js +++ b/superset/assets/javascripts/explore/stores/visTypes.js @@ -141,7 +141,7 @@ const visTypes = { }, dual_line: { - label: 'Time Series - Dual Axis Line Chart', + label: 'Dual Axis Line Chart', requiresTime: true, controlPanelSections: [ { diff --git a/superset/assets/package.json b/superset/assets/package.json index ca453c0408b00..37f784959822e 100644 --- a/superset/assets/package.json +++ b/superset/assets/package.json @@ -9,7 +9,7 @@ }, "scripts": { "test": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/browser.js --recursive spec/**/*_spec.*", - "cover": "babel-node ./node_modules/.bin/istanbul cover _mocha -- --require spec/helpers/browser.js --recursive spec/**/*_spec.*", + "cover": "babel-node node_modules/.bin/babel-istanbul cover _mocha -- --require spec/helpers/browser.js --recursive spec/**/*_spec.*", "dev": "NODE_ENV=dev webpack --watch --colors --progress --debug --output-pathinfo --devtool inline-source-map", "prod": "NODE_ENV=production node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js -p --colors --progress", "build": "NODE_ENV=production webpack --colors --progress", @@ -59,6 +59,7 @@ "immutability-helper": "^2.0.0", "immutable": "^3.8.1", "jquery": "^3.2.1", + "jsdom": "9.12.0", "lodash.throttle": "^4.1.1", "mapbox-gl": "^0.26.0", "moment": "^2.14.1", @@ -98,9 +99,10 @@ "devDependencies": { "babel-cli": "^6.14.0", "babel-core": "^6.10.4", + "babel-istanbul": "^0.12.2", "babel-loader": "^6.2.4", "babel-plugin-css-modules-transform": "^1.1.0", - "babel-polyfill": "^6.14.0", + "babel-polyfill": "^6.23.0", "babel-preset-airbnb": "^2.1.1", "babel-preset-es2015": "^6.14.0", "babel-preset-react": "^6.11.1", @@ -119,7 +121,7 @@ "ignore-styles": "^5.0.1", "imports-loader": "^0.7.1", "istanbul": "^1.0.0-alpha", - "jsdom": "^9.12.0", + "jsdom": "9.12.0", "json-loader": "^0.5.4", "less": "^2.6.1", "less-loader": "^4.0.3", diff --git a/superset/assets/spec/javascripts/explorev2/components/VizTypeControl_spec.jsx b/superset/assets/spec/javascripts/explorev2/components/VizTypeControl_spec.jsx new file mode 100644 index 0000000000000..ba0ba5814630f --- /dev/null +++ b/superset/assets/spec/javascripts/explorev2/components/VizTypeControl_spec.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import sinon from 'sinon'; +import { expect } from 'chai'; +import { describe, it, beforeEach } from 'mocha'; +import { shallow } from 'enzyme'; +import { Modal } from 'react-bootstrap'; +import VizTypeControl from '../../../../javascripts/explore/components/controls/VizTypeControl'; + +const defaultProps = { + name: 'viz_type', + label: 'Visualization Type', + value: 'table', + onChange: sinon.spy(), +}; + +describe('VizTypeControl', () => { + let wrapper; + + beforeEach(() => { + wrapper = shallow(); + }); + + it('renders a Modal', () => { + expect(wrapper.find(Modal)).to.have.lengthOf(1); + }); + + it('calls onChange when toggled', () => { + const select = wrapper.find('.viztype-selector-container').first(); + select.simulate('click'); + expect(defaultProps.onChange.called).to.equal(true); + }); + it('filters images based on text input', () => { + expect(wrapper.find('img').length).to.be.above(20); + wrapper.setState({ filter: 'time' }); + expect(wrapper.find('img').length).to.be.below(10); + }); +}); diff --git a/superset/assets/stylesheets/less/cosmo/bootswatch.less b/superset/assets/stylesheets/less/cosmo/bootswatch.less index 7cb1eb4641b67..402dd5dcdb24a 100644 --- a/superset/assets/stylesheets/less/cosmo/bootswatch.less +++ b/superset/assets/stylesheets/less/cosmo/bootswatch.less @@ -422,3 +422,8 @@ hr { } } .space-loop(6); + +a { + cursor: pointer; +} +