fabianmenges commented on a change in pull request #3518: Full Annotation
Framework
URL:
https://github.com/apache/incubator-superset/pull/3518#discussion_r157349698
##########
File path:
superset/assets/javascripts/explore/components/controls/AnnotationLayer.jsx
##########
@@ -0,0 +1,602 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { CompactPicker } from 'react-color';
+import { Button } from 'react-bootstrap';
+
+import $ from 'jquery';
+import mathjs from 'mathjs';
+
+import SelectControl from './SelectControl';
+import TextControl from './TextControl';
+import CheckboxControl from './CheckboxControl';
+
+import AnnotationTypes, {
+ DEFAULT_ANNOTATION_TYPE,
+ ANNOTATION_SOURCE_TYPES,
+ getAnnotationSourceTypeLabels,
+ getAnnotationTypeLabel,
+ getSupportedSourceTypes,
+ getSupportedAnnotationTypes,
+ requiresQuery,
+} from '../../../modules/AnnotationTypes';
+
+import { ALL_COLOR_SCHEMES } from '../../../modules/colors';
+import PopoverSection from '../../../components/PopoverSection';
+import ControlHeader from '../ControlHeader';
+import { nonEmpty } from '../../validators';
+import vizTypes from '../../stores/visTypes';
+
+const AUTOMATIC_COLOR = '';
+
+const propTypes = {
+ name: PropTypes.string,
+ annotationType: PropTypes.string,
+ sourceType: PropTypes.string,
+ color: PropTypes.string,
+ opacity: PropTypes.string,
+ style: PropTypes.string,
+ width: PropTypes.number,
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ overrides: PropTypes.object,
+ show: PropTypes.bool,
+ titleColumn: PropTypes.string,
+ descriptionColumns: PropTypes.arrayOf(PropTypes.string),
+ timeColumn: PropTypes.string,
+ intervalEndColumn: PropTypes.string,
+ vizType: PropTypes.string,
+
+ error: PropTypes.string,
+ colorScheme: PropTypes.string,
+
+ addAnnotationLayer: PropTypes.func,
+ removeAnnotationLayer: PropTypes.func,
+ close: PropTypes.func,
+};
+
+const defaultProps = {
+ name: '',
+ annotationType: DEFAULT_ANNOTATION_TYPE,
+ sourceType: '',
+ color: AUTOMATIC_COLOR,
+ opacity: '',
+ style: 'solid',
+ width: 1,
+ overrides: {},
+ colorScheme: 'd3Category10',
+ show: true,
+ titleColumn: '',
+ descriptionColumns: [],
+ timeColumn: '',
+ intervalEndColumn: '',
+
+ addAnnotationLayer: () => {},
+ removeAnnotationLayer: () => {},
+ close: () => {},
+};
+
+export default class AnnotationLayer extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ const { name, annotationType, sourceType,
+ color, opacity, style, width, value,
+ overrides, show, titleColumn, descriptionColumns,
+ timeColumn, intervalEndColumn } = props;
+ this.state = {
+ // base
+ name,
+ oldName: !this.props.name ? null : name,
+ annotationType,
+ sourceType,
+ value,
+ overrides,
+ show,
+ // slice
+ titleColumn,
+ descriptionColumns,
+ timeColumn,
+ intervalEndColumn,
+ // display
+ color: color || AUTOMATIC_COLOR,
+ opacity,
+ style,
+ width,
+ // refData
+ isNew: !this.props.name,
+ isLoadingOptions: true,
+ valueOptions: [],
+ };
+ this.submitAnnotation = this.submitAnnotation.bind(this);
+ this.deleteAnnotation = this.deleteAnnotation.bind(this);
+ this.applyAnnotation = this.applyAnnotation.bind(this);
+ this.fetchOptions = this.fetchOptions.bind(this);
+ this.handleAnnotationType = this.handleAnnotationType.bind(this);
+ this.handleAnnotationSourceType =
+ this.handleAnnotationSourceType.bind(this);
+ this.handleValue = this.handleValue.bind(this);
+ this.isValidForm = this.isValidForm.bind(this);
+ }
+
+ componentDidMount() {
+ const { annotationType, sourceType, isLoadingOptions } = this.state;
+ this.fetchOptions(annotationType, sourceType, isLoadingOptions);
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevState.sourceType !== this.state.sourceType) {
+ this.fetchOptions(this.state.annotationType, this.state.sourceType,
true);
+ }
+ }
+
+ isValidFormula(value, annotationType) {
+ if (annotationType === AnnotationTypes.FORMULA) {
+ try {
+ mathjs.parse(value).compile().eval({ x: 0 });
+ } catch (err) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ isValidForm() {
+ const {
+ name, annotationType, sourceType,
+ value, timeColumn, intervalEndColumn,
+ } = this.state;
+ const errors = [nonEmpty(name), nonEmpty(annotationType), nonEmpty(value)];
+ if (sourceType !== ANNOTATION_SOURCE_TYPES.NATIVE) {
+ if (annotationType === AnnotationTypes.EVENT) {
+ errors.push(nonEmpty(timeColumn));
+ }
+ if (annotationType === AnnotationTypes.INTERVAL) {
+ errors.push(nonEmpty(timeColumn));
+ errors.push(nonEmpty(intervalEndColumn));
+ }
+ }
+ errors.push(this.isValidFormula(value, annotationType));
+ return !errors.filter(x => x).length;
+ }
+
+
+ handleAnnotationType(annotationType) {
+ this.setState({
+ annotationType,
+ sourceType: null,
+ validationErrors: {},
+ value: null,
+ });
+ }
+
+ handleAnnotationSourceType(sourceType) {
+ this.setState({
+ sourceType,
+ isLoadingOptions: true,
+ validationErrors: {},
+ value: null,
+ });
+ }
+
+ handleValue(value) {
+ this.setState({
+ value,
+ descriptionColumns: null,
+ intervalEndColumn: null,
+ timeColumn: null,
+ titleColumn: null,
+ overrides: { since: null, until: null },
+ });
+ }
+
+ fetchOptions(annotationType, sourceType, isLoadingOptions) {
+ if (isLoadingOptions === true) {
+ if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) {
+ $.ajax({
+ type: 'GET',
+ url: '/annotationlayermodelview/api/read?',
+ }).then((data) => {
+ const layers = data ? data.result.map(layer => ({
+ value: layer.id,
+ label: layer.name,
+ })) : [];
+ this.setState({
+ isLoadingOptions: false,
+ valueOptions: layers,
+ });
+ });
+ } else if (requiresQuery(sourceType)) {
+ $.ajax({
+ type: 'GET',
+ url: '/superset/user_slices',
+ }).then(data =>
+ this.setState({
+ isLoadingOptions: false,
+ valueOptions: data.filter(
+ x => getSupportedSourceTypes(annotationType)
+ .find(v => v === x.viz_type))
+ .map(x => ({ value: x.id, label: x.title, slice: x }),
+ ),
+ }),
+ );
+ } else {
+ this.setState({
+ isLoadingOptions: false,
+ valueOptions: [],
+ });
+ }
+ }
+ }
+
+ deleteAnnotation() {
+ this.props.close();
+ if (!this.state.isNew) {
+ this.props.removeAnnotationLayer(this.state);
+ }
+ }
+
+ applyAnnotation() {
+ if (this.state.name.length) {
+ const annotation = { ...this.state };
+ annotation.color = annotation.color === AUTOMATIC_COLOR ? null :
annotation.color;
+ delete annotation.isNew;
+ delete annotation.valueOptions;
+ delete annotation.isLoadingOptions;
+ this.props.addAnnotationLayer(annotation);
+ this.setState({ isNew: false, oldName: this.state.name });
+ }
+ }
+
+ submitAnnotation() {
+ this.applyAnnotation();
+ this.props.close();
+ }
+
+ renderValueConfiguration() {
+ const { annotationType, sourceType, value,
+ valueOptions, isLoadingOptions } = this.state;
+ let label = '';
+ let description = '';
+ if (requiresQuery(sourceType)) {
+ if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) {
+ label = 'Annotation Layer';
+ description = 'Select the Annotation Layer you would like to use.';
+ } else {
+ label = 'Slice';
+ description = `Use a pre defined Superset Slice as a source for
annotations and overlays.
+ 'your Slice must be one of these visualization types:
+ '[${getSupportedSourceTypes(sourceType)
+ .map(x => vizTypes[x].label).join(', ')}]'`;
+ }
+ } else if (annotationType === AnnotationTypes.FORMULA) {
+ label = 'Formula';
+ description = `Expects a formula with depending time parameter 'x'
+ in milliseconds since epoch. mathjs is used to evaluate the formulas.
+ Example: '2x+5'`;
+ }
+ if (requiresQuery(sourceType)) {
+ return (
+ <SelectControl
Review comment:
I tried to used that but ran into issues with it. I can't remember what they
were since I wrote this 4 month ago.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
With regards,
Apache Git Services