From d1a7a7b85c77f23c16db3e5a97b570adb1b4a6cf Mon Sep 17 00:00:00 2001 From: Grace Guo Date: Wed, 27 Sep 2017 20:40:07 -0700 Subject: [PATCH] Time Series Annotation Layers (#3521) * Adding annotations to backend * Auto fetching Annotations on the backend * Closing the loop * Adding missing files * annotation layers UI for https://github.com/apache/incubator-superset/issues/3502 * a few fixes per code review. - add annotation input sanity check before add and before update. - make SelectAsyncControl component statelesis, and generic - add annotation description in d3 tool tip - use less variable to replace hard-coded color --- .../javascripts/components/AsyncSelect.jsx | 6 +- .../explore/components/Control.jsx | 4 +- .../controls/SelectAsyncControl.jsx | 53 ++++++++++++++ superset/assets/javascripts/explore/main.css | 5 +- .../javascripts/explore/stores/controls.jsx | 17 +++++ .../javascripts/explore/stores/visTypes.js | 12 +++ superset/assets/stylesheets/less/index.less | 2 + superset/assets/stylesheets/superset.less | 14 ++++ superset/assets/visualizations/nvd3_vis.js | 73 +++++++++++++++++++ superset/migrations/versions/d39b1e37131d_.py | 22 ++++++ .../versions/ddd6ebdd853b_annotations.py | 56 ++++++++++++++ superset/migrations/versions/f959a6652acd_.py | 22 ++++++ superset/models/annotations.py | 57 +++++++++++++++ superset/models/helpers.py | 6 ++ superset/views/__init__.py | 1 + superset/views/annotations.py | 59 +++++++++++++++ superset/views/core.py | 3 +- superset/viz.py | 26 +++++++ 18 files changed, 434 insertions(+), 4 deletions(-) create mode 100644 superset/assets/javascripts/explore/components/controls/SelectAsyncControl.jsx create mode 100644 superset/migrations/versions/d39b1e37131d_.py create mode 100644 superset/migrations/versions/ddd6ebdd853b_annotations.py create mode 100644 superset/migrations/versions/f959a6652acd_.py create mode 100644 superset/models/annotations.py create mode 100644 superset/views/annotations.py diff --git a/superset/assets/javascripts/components/AsyncSelect.jsx b/superset/assets/javascripts/components/AsyncSelect.jsx index 007281a116a04..69b4216a9c955 100644 --- a/superset/assets/javascripts/components/AsyncSelect.jsx +++ b/superset/assets/javascripts/components/AsyncSelect.jsx @@ -10,7 +10,10 @@ const propTypes = { onChange: PropTypes.func.isRequired, mutator: PropTypes.func.isRequired, onAsyncError: PropTypes.func, - value: PropTypes.number, + value: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.arrayOf(PropTypes.number), + ]), valueRenderer: PropTypes.func, placeholder: PropTypes.string, autoSelect: PropTypes.bool, @@ -63,6 +66,7 @@ class AsyncSelect extends React.PureComponent { isLoading={this.state.isLoading} onChange={this.onChange.bind(this)} valueRenderer={this.props.valueRenderer} + {...this.props} /> ); diff --git a/superset/assets/javascripts/explore/components/Control.jsx b/superset/assets/javascripts/explore/components/Control.jsx index 5c644c38f7906..972ff0d3c19d4 100644 --- a/superset/assets/javascripts/explore/components/Control.jsx +++ b/superset/assets/javascripts/explore/components/Control.jsx @@ -3,15 +3,16 @@ import PropTypes from 'prop-types'; import BoundsControl from './controls/BoundsControl'; import CheckboxControl from './controls/CheckboxControl'; +import ColorSchemeControl from './controls/ColorSchemeControl'; import DatasourceControl from './controls/DatasourceControl'; import DateFilterControl from './controls/DateFilterControl'; import FilterControl from './controls/FilterControl'; import HiddenControl from './controls/HiddenControl'; +import SelectAsyncControl from './controls/SelectAsyncControl'; import SelectControl from './controls/SelectControl'; import TextAreaControl from './controls/TextAreaControl'; import TextControl from './controls/TextControl'; import VizTypeControl from './controls/VizTypeControl'; -import ColorSchemeControl from './controls/ColorSchemeControl'; const controlMap = { BoundsControl, @@ -25,6 +26,7 @@ const controlMap = { TextControl, VizTypeControl, ColorSchemeControl, + SelectAsyncControl, }; const controlTypes = Object.keys(controlMap); diff --git a/superset/assets/javascripts/explore/components/controls/SelectAsyncControl.jsx b/superset/assets/javascripts/explore/components/controls/SelectAsyncControl.jsx new file mode 100644 index 0000000000000..173a275ca9c33 --- /dev/null +++ b/superset/assets/javascripts/explore/components/controls/SelectAsyncControl.jsx @@ -0,0 +1,53 @@ +/* global notify */ +import React from 'react'; +import PropTypes from 'prop-types'; +import Select from '../../../components/AsyncSelect'; +import { t } from '../../../locales'; + +const propTypes = { + dataEndpoint: PropTypes.string.isRequired, + multi: PropTypes.bool, + mutator: PropTypes.func, + onAsyncErrorMessage: PropTypes.string, + onChange: PropTypes.func, + placeholder: PropTypes.string, + value: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + PropTypes.arrayOf(PropTypes.string), + PropTypes.arrayOf(PropTypes.number), + ]), +}; + +const defaultProps = { + multi: true, + onAsyncErrorMessage: t('Error while fetching data'), + onChange: () => {}, + placeholder: t('Select ...'), +}; + +const SelectAsyncControl = ({ value, onChange, dataEndpoint, + multi, mutator, placeholder, onAsyncErrorMessage }) => { + const onSelectionChange = (options) => { + const optionValues = options.map(option => option.value); + onChange(optionValues); + }; + + return ( +