/* global notify */ import moment from 'moment'; import React from 'react'; import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { Alert, Button, Col, Modal } from 'react-bootstrap'; import Select from 'react-select'; import { Table } from 'reactable'; import shortid from 'shortid'; import { getExploreUrl } from '../../explore/exploreUtils'; import * as actions from '../actions'; import { VISUALIZE_VALIDATION_ERRORS } from '../constants'; import { QUERY_TIMEOUT_THRESHOLD } from '../../constants'; const CHART_TYPES = [ { value: 'dist_bar', label: 'Distribution - Bar Chart', requiresTime: false }, { value: 'pie', label: 'Pie Chart', requiresTime: false }, { value: 'line', label: 'Time Series - Line Chart', requiresTime: true }, { value: 'bar', label: 'Time Series - Bar Chart', requiresTime: true }, ]; const propTypes = { actions: PropTypes.object.isRequired, onHide: PropTypes.func, query: PropTypes.object, show: PropTypes.bool, datasource: PropTypes.string, errorMessage: PropTypes.string, }; const defaultProps = { show: false, query: {}, onHide: () => {}, }; class VisualizeModal extends React.PureComponent { constructor(props) { super(props); this.state = { chartType: CHART_TYPES[0], datasourceName: this.datasourceName(), columns: this.getColumnFromProps(), hints: [], }; } componentDidMount() { this.validate(); } getColumnFromProps() { const props = this.props; if (!props || !props.query || !props.query.results || !props.query.results.columns) { return {}; } const columns = {}; props.query.results.columns.forEach((col) => { columns[col.name] = col; }); return columns; } datasourceName() { const { query } = this.props; const uniqueId = shortid.generate(); let datasourceName = uniqueId; if (query) { datasourceName = query.user ? `${query.user}-` : ''; datasourceName += query.db ? `${query.db}-` : ''; datasourceName += `${query.tab}-${uniqueId}`; } return datasourceName; } validate() { const hints = []; const cols = this.mergedColumns(); const re = /^\w+$/; Object.keys(cols).forEach((colName) => { if (!re.test(colName)) { hints.push(
"{colName}" is not right as a column name, please alias it (as in SELECT count(*) AS my_alias) using only alphanumeric characters and underscores
); } }); if (this.state.chartType === null) { hints.push(VISUALIZE_VALIDATION_ERRORS.REQUIRE_CHART_TYPE); } else if (this.state.chartType.requiresTime) { let hasTime = false; for (const colName in cols) { const col = cols[colName]; if (col.hasOwnProperty('is_date') && col.is_date) { hasTime = true; } } if (!hasTime) { hints.push(VISUALIZE_VALIDATION_ERRORS.REQUIRE_TIME); } } this.setState({ hints }); } changeChartType(option) { this.setState({ chartType: option }, this.validate); } mergedColumns() { const columns = Object.assign({}, this.state.columns); if (this.props.query && this.props.query.results.columns) { this.props.query.results.columns.forEach((col) => { if (columns[col.name] === undefined) { columns[col.name] = col; } }); } return columns; } buildVizOptions() { return { chartType: this.state.chartType.value, datasourceName: this.state.datasourceName, columns: this.state.columns, sql: this.props.query.sql, dbId: this.props.query.dbId, }; } buildVisualizeAdvise() { let advise; const queryDuration = moment.duration(this.props.query.endDttm - this.props.query.startDttm); if (Math.round(queryDuration.asMilliseconds()) > QUERY_TIMEOUT_THRESHOLD) { advise = ( This query took {Math.round(queryDuration.asSeconds())} seconds to run, and the explore view times out at {QUERY_TIMEOUT_THRESHOLD / 1000} seconds, following this flow will most likely lead to your query timing out. We recommend your summarize your data further before following that flow. If activated you can use the CREATE TABLE AS feature to store a summarized data set that you can then explore. ); } return advise; } visualize() { this.props.actions.createDatasource(this.buildVizOptions(), this) .done(() => { const columns = Object.keys(this.state.columns).map(k => this.state.columns[k]); const mainGroupBy = columns.filter(d => d.is_dim)[0]; const formData = { datasource: this.props.datasource, viz_type: this.state.chartType.value, since: '100 years ago', limit: '0', }; if (mainGroupBy) { formData.groupby = [mainGroupBy.name]; } notify.info('Creating a data source and popping a new tab'); window.open(getExploreUrl(formData)); }) .fail(() => { notify.error(this.props.errorMessage); }); } changeDatasourceName(event) { this.setState({ datasourceName: event.target.value }, this.validate); } changeCheckbox(attr, columnName, event) { let columns = this.mergedColumns(); const column = Object.assign({}, columns[columnName], { [attr]: event.target.checked }); columns = Object.assign({}, columns, { [columnName]: column }); this.setState({ columns }, this.validate); } changeAggFunction(columnName, option) { let columns = this.mergedColumns(); const val = (option) ? option.value : null; const column = Object.assign({}, columns[columnName], { agg: val }); columns = Object.assign({}, columns, { [columnName]: column }); this.setState({ columns }, this.validate); } render() { if (!(this.props.query) || !(this.props.query.results) || !(this.props.query.results.columns)) { return (
No results available for this query
); } const tableData = this.props.query.results.columns.map(col => ({ column: col.name, is_dimension: ( ), is_date: ( ), agg_func: ( Datasource Name
); return modal; } } VisualizeModal.propTypes = propTypes; VisualizeModal.defaultProps = defaultProps; function mapStateToProps(state) { return { datasource: state.datasource, errorMessage: state.errorMessage, }; } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators(actions, dispatch), }; } export { VisualizeModal }; export default connect(mapStateToProps, mapDispatchToProps)(VisualizeModal);