diff --git a/.coveralls.yml b/.coveralls.yml index 273f84b694e76..e916d8ed05cc3 100644 --- a/.coveralls.yml +++ b/.coveralls.yml @@ -1 +1 @@ -repo_token: eESbYiv4An6KEvjpmguDs4L7YkubXbqn1 +repo_token: 4P9MpvLrZfJKzHdGZsdV3MzO43OZJgYFn diff --git a/.travis.yml b/.travis.yml index dcf700fd0c8cd..914c4b63b4482 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ cache: env: global: - TRAVIS_CACHE=$HOME/.travis_cache/ - - TRAVIS_NODE_VERSION="6.10.2" + - TRAVIS_NODE_VERSION="7.10.0" matrix: - TOX_ENV=javascript - TOX_ENV=pylint @@ -19,7 +19,7 @@ env: - TOX_ENV=py27-mysql - TOX_ENV=py27-sqlite before_install: - - npm install -g npm@'>=4.5.0' + - npm install -g npm@'>=5.0.3' before_script: - mysql -e 'drop database if exists superset; create database superset DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci' -u root - mysql -u root -e "CREATE USER 'mysqluser'@'localhost' IDENTIFIED BY 'mysqluserpassword';" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d909d5d4a1c27..27c70dd208e25 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ meets these guidelines: ## Documentation -The latest documentation and tutorial are available [here](http://airbnb.io/superset). +The latest documentation and tutorial are available [here](https://superset.incubator.apache.org/). Contributing to the official documentation is relatively easy, once you've setup your environment and done an edit end-to-end. The docs can be found in the @@ -144,7 +144,7 @@ referenced in the rst, e.g. aren't actually included in that directory. _Instead_, you'll want to add and commit images (and any other static assets) to the _superset/assets/images_ directory. -When the docs are being pushed to [airbnb.io](http://airbnb.io/superset/), images +When the docs are being pushed to [Apache Superset (incubating)](https://superset.incubator.apache.org/), images will be moved from there to the _\_static/img_ directory, just like they're referenced in the docs. @@ -161,12 +161,12 @@ instead. ## Setting up a Python development environment -Check the [OS dependencies](http://airbnb.io/superset/installation.html#os-dependencies) before follows these steps. +Check the [OS dependencies](https://superset.incubator.apache.org/installation.html#os-dependencies) before follows these steps. # fork the repo on GitHub and then clone it # alternatively you may want to clone the main repo but that won't work # so well if you are planning on sending PRs - # git clone git@github.com:airbnb/superset.git + # git clone git@github.com:apache/incubator-superset.git # [optional] setup a virtual env and activate it virtualenv env @@ -223,8 +223,13 @@ To install third party libraries defined in `package.json`, run the following within the `superset/assets/` directory which will install them in a new `node_modules/` folder within `assets/`. -``` -npm install +```bash +# from the root of the repository, move to where our JS package.json lives +cd superset/assets/ +# install yarn, a replacement for `npm install` that is faster and more deterministic +npm install -g yarn +# run yarn to fetch all the dependencies +yarn ``` To parse and generate bundled files for superset, run either of the @@ -342,7 +347,7 @@ new language dictionary, run the following command: pybabel init -i ./babel/messages.pot -d superset/translations -l es -Then it's a matter of running the statement below to gather all stings that +Then it's a matter of running the statement below to gather all strings that need translation fabmanager babel-extract --target superset/translations/ @@ -374,4 +379,4 @@ to take effect, they need to be compiled using this command: Here's an example as a Github PR with comments that describe what the different sections of the code do: -https://github.com/airbnb/superset/pull/3013 +https://github.com/apache/incubator-superset/pull/3013 diff --git a/README.md b/README.md index 013ec74f9473a..7382789f7159c 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,14 @@ Superset ========= -[![Build Status](https://travis-ci.org/airbnb/superset.svg?branch=master)](https://travis-ci.org/airbnb/superset) +[![Build Status](https://travis-ci.org/apache/incubator-superset.svg?branch=master)](https://travis-ci.org/apache/incubator-superset) [![PyPI version](https://badge.fury.io/py/superset.svg)](https://badge.fury.io/py/superset) -[![Coverage Status](https://coveralls.io/repos/airbnb/superset/badge.svg?branch=master&service=github)](https://coveralls.io/github/airbnb/superset?branch=master) -[![JS Test Coverage](https://codeclimate.com/github/airbnb/superset/badges/coverage.svg)](https://codeclimate.com/github/airbnb/superset/coverage) -[![Code Health](https://landscape.io/github/airbnb/superset/master/landscape.svg?style=flat)](https://landscape.io/github/airbnb/superset/master) -[![Code Climate](https://codeclimate.com/github/airbnb/superset/badges/gpa.svg)](https://codeclimate.com/github/airbnb/superset) +[![Coverage Status](https://coveralls.io/repos/apache/incubator-superset/badge.svg?branch=master&service=github)](https://coveralls.io/github/apache/incubator-superset?branch=master) [![PyPI](https://img.shields.io/pypi/pyversions/superset.svg?maxAge=2592000)](https://pypi.python.org/pypi/superset) -[![Requirements Status](https://requires.io/github/airbnb/superset/requirements.svg?branch=master)](https://requires.io/github/airbnb/superset/requirements/?branch=master) -[![Join the chat at https://gitter.im/airbnb/superset](https://badges.gitter.im/airbnb/superset.svg)](https://gitter.im/airbnb/superset?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Documentation](https://img.shields.io/badge/docs-airbnb.io-blue.svg)](http://airbnb.io/superset/) -[![dependencies Status](https://david-dm.org/airbnb/superset/status.svg?path=superset/assets)](https://david-dm.org/airbnb/superset?path=superset/assets) +[![Requirements Status](https://requires.io/github/apache/incubator-superset/requirements.svg?branch=master)](https://requires.io/github/apache/incubator-superset/requirements/?branch=master) +[![Join the chat at https://gitter.im/apache/incubator-superset](https://badges.gitter.im/apache/incubator-superset.svg)](https://gitter.im/apache/incubator-superset?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Documentation](https://img.shields.io/badge/docs-apache.org-blue.svg)](https://superset.incubator.apache.org) +[![dependencies Status](https://david-dm.org/apache/incubator-superset/status.svg?path=superset/assets)](https://david-dm.org/apache/incubator-superset?path=superset/assets) =0.3.0', + 'PyHive>=0.4.0', 'python-dateutil==2.6.0', 'requests==2.17.3', 'simplejson==3.10.0', diff --git a/superset/assets/backendSync.json b/superset/assets/backendSync.json index 1d8ba55fa746d..71e7130328492 100644 --- a/superset/assets/backendSync.json +++ b/superset/assets/backendSync.json @@ -750,6 +750,12 @@ "default": false, "description": "Sort bars by x labels." }, + "combine_metric": { + "type": "CheckboxControl", + "label": "Combine Metrics", + "default": false, + "description": "Display metrics side by side within each column, as opposed to each column being displayed side by side for each metric." + }, "show_controls": { "type": "CheckboxControl", "label": "Extra Controls", diff --git a/superset/assets/images/viz_thumbnails/chord.png b/superset/assets/images/viz_thumbnails/chord.png new file mode 100644 index 0000000000000..a4a30b6aebc63 Binary files /dev/null and b/superset/assets/images/viz_thumbnails/chord.png differ diff --git a/superset/assets/images/viz_thumbnails/event_flow.png b/superset/assets/images/viz_thumbnails/event_flow.png new file mode 100644 index 0000000000000..45765295be003 Binary files /dev/null and b/superset/assets/images/viz_thumbnails/event_flow.png differ diff --git a/superset/assets/javascripts/SqlLab/components/ResultSet.jsx b/superset/assets/javascripts/SqlLab/components/ResultSet.jsx index c9814ec214adc..79c6d9c8c4355 100644 --- a/superset/assets/javascripts/SqlLab/components/ResultSet.jsx +++ b/superset/assets/javascripts/SqlLab/components/ResultSet.jsx @@ -35,7 +35,7 @@ export default class ResultSet extends React.PureComponent { this.state = { searchText: '', showModal: false, - data: [], + data: null, height: props.search ? props.height - RESULT_SET_CONTROLS_HEIGHT : props.height, }; } @@ -146,30 +146,12 @@ export default class ResultSet extends React.PureComponent { const query = this.props.query; let sql; - if (query.state === 'stopped') { - return Query was stopped; - } - if (this.props.showSql) { sql = ; } - if (['running', 'pending', 'fetching'].indexOf(query.state) > -1) { - let progressBar; - if (query.progress > 0 && query.state === 'running') { - progressBar = ( - ); - } - return ( -
- Loading... - - {progressBar} -
- ); + + if (query.state === 'stopped') { + return Query was stopped; } else if (query.state === 'failed') { return {query.errorMessage}; } else if (query.state === 'success' && query.ctas) { @@ -192,10 +174,10 @@ export default class ResultSet extends React.PureComponent { let data; if (this.props.cache && query.cached) { data = this.state.data; - } else { - data = results ? results.data : []; + } else if (results && results.data) { + data = results.data; } - if (results && data.length > 0) { + if (data && data.length > 0) { return (
); + } else if (data && data.length === 0) { + return The query returned no data; } } if (query.cached) { @@ -226,7 +210,36 @@ export default class ResultSet extends React.PureComponent { ); } - return The query returned no data; + let progressBar; + let trackingUrl; + if (query.progress > 0 && query.state === 'running') { + progressBar = ( + ); + } + if (query.trackingUrl) { + trackingUrl = ( + + ); + } + return ( +
+ Loading... + + {progressBar} +
+ {trackingUrl} +
+
+ ); } } ResultSet.propTypes = propTypes; diff --git a/superset/assets/javascripts/SqlLab/components/VisualizeModal.jsx b/superset/assets/javascripts/SqlLab/components/VisualizeModal.jsx index 2ebfef9318a1a..dce820cd677a5 100644 --- a/superset/assets/javascripts/SqlLab/components/VisualizeModal.jsx +++ b/superset/assets/javascripts/SqlLab/components/VisualizeModal.jsx @@ -146,7 +146,6 @@ class VisualizeModal extends React.PureComponent { this.props.actions.createDatasource(this.buildVizOptions(), this) .done(() => { const columns = Object.keys(this.state.columns).map(k => this.state.columns[k]); - const mainMetric = columns.filter(d => d.agg)[0]; const mainGroupBy = columns.filter(d => d.is_dim)[0]; const formData = { datasource: this.props.datasource, @@ -154,10 +153,6 @@ class VisualizeModal extends React.PureComponent { since: '100 years ago', limit: '0', }; - if (mainMetric) { - formData.metrics = [mainMetric.name]; - formData.metric = mainMetric.name; - } if (mainGroupBy) { formData.groupby = [mainGroupBy.name]; } diff --git a/superset/assets/javascripts/SqlLab/index.jsx b/superset/assets/javascripts/SqlLab/index.jsx index e292c2576c1ae..ba09924720177 100644 --- a/superset/assets/javascripts/SqlLab/index.jsx +++ b/superset/assets/javascripts/SqlLab/index.jsx @@ -11,7 +11,7 @@ import App from './components/App'; import { appSetup } from '../common'; import './main.css'; -import './reactable-pagination.css'; +import '../../stylesheets/reactable-pagination.css'; import '../components/FilterableTable/FilterableTableStyles.css'; appSetup(); diff --git a/superset/assets/javascripts/SqlLab/main.css b/superset/assets/javascripts/SqlLab/main.css index a3ad7dbe6b445..ad2bb37c0e3e6 100644 --- a/superset/assets/javascripts/SqlLab/main.css +++ b/superset/assets/javascripts/SqlLab/main.css @@ -265,7 +265,7 @@ div.tablePopover:hover { } .QueryTable .label { - margin-top: 5px; + display: inline-block; } .ResultsModal .modal-body { diff --git a/superset/assets/javascripts/components/EditableTitle.jsx b/superset/assets/javascripts/components/EditableTitle.jsx index 70046f87ca1e2..9d71388828ad2 100644 --- a/superset/assets/javascripts/components/EditableTitle.jsx +++ b/superset/assets/javascripts/components/EditableTitle.jsx @@ -23,6 +23,7 @@ class EditableTitle extends React.PureComponent { this.handleClick = this.handleClick.bind(this); this.handleBlur = this.handleBlur.bind(this); this.handleChange = this.handleChange.bind(this); + this.handleKeyPress = this.handleKeyPress.bind(this); } handleClick() { if (!this.props.canEdit) { @@ -58,6 +59,13 @@ class EditableTitle extends React.PureComponent { title: ev.target.value, }); } + handleKeyPress(ev) { + if (ev.key === 'Enter') { + ev.preventDefault(); + + this.handleBlur(); + } + } render() { return ( @@ -72,6 +80,7 @@ class EditableTitle extends React.PureComponent { onChange={this.handleChange} onBlur={this.handleBlur} onClick={this.handleClick} + onKeyPress={this.handleKeyPress} /> diff --git a/superset/assets/javascripts/components/InfoTooltipWithTrigger.jsx b/superset/assets/javascripts/components/InfoTooltipWithTrigger.jsx index 07b4db473e3a6..85bc7fb50d1af 100644 --- a/superset/assets/javascripts/components/InfoTooltipWithTrigger.jsx +++ b/superset/assets/javascripts/components/InfoTooltipWithTrigger.jsx @@ -8,18 +8,23 @@ const propTypes = { tooltip: PropTypes.string.isRequired, icon: PropTypes.string, className: PropTypes.string, + onClick: PropTypes.func, }; const defaultProps = { icon: 'question-circle-o', }; -export default function InfoTooltipWithTrigger({ label, tooltip, icon, className }) { +export default function InfoTooltipWithTrigger({ label, tooltip, icon, className, onClick }) { return ( {tooltip}} > - + ); } diff --git a/superset/assets/javascripts/dashboard/Dashboard.jsx b/superset/assets/javascripts/dashboard/Dashboard.jsx index 8b0a0e1c69114..b6c97b68ecf6d 100644 --- a/superset/assets/javascripts/dashboard/Dashboard.jsx +++ b/superset/assets/javascripts/dashboard/Dashboard.jsx @@ -245,15 +245,18 @@ export function dashboardContainer(dashboard, datasources, userid) { startPeriodicRender(interval) { this.stopPeriodicRender(); const dash = this; + const immune = this.metadata.timed_refresh_immune_slices || []; const maxRandomDelay = Math.max(interval * 0.2, 5000); const refreshAll = () => { dash.sliceObjects.forEach((slice) => { const force = !dash.firstLoad; - setTimeout(() => { - slice.render(force); - }, - // Randomize to prevent all widgets refreshing at the same time - maxRandomDelay * Math.random()); + if (immune.indexOf(slice.data.slice_id) === -1) { + setTimeout(() => { + slice.render(force); + }, + // Randomize to prevent all widgets refreshing at the same time + maxRandomDelay * Math.random()); + } }); dash.firstLoad = false; }; diff --git a/superset/assets/javascripts/dashboard/components/SliceAdder.jsx b/superset/assets/javascripts/dashboard/components/SliceAdder.jsx index a96effef4b196..cb9206619e87b 100644 --- a/superset/assets/javascripts/dashboard/components/SliceAdder.jsx +++ b/superset/assets/javascripts/dashboard/components/SliceAdder.jsx @@ -129,9 +129,14 @@ class SliceAdder extends React.Component { height="auto" >