/* eslint camelcase: 0 */ import React from 'react'; import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import ExploreChartPanel from './ExploreChartPanel'; import ControlPanelsContainer from './ControlPanelsContainer'; import SaveModal from './SaveModal'; import QueryAndSaveBtns from './QueryAndSaveBtns'; import { getExploreUrlAndPayload, getExploreLongUrl } from '../exploreUtils'; import { areObjectsEqual } from '../../reduxUtils'; import { getFormDataFromControls } from '../stores/store'; import { chartPropType } from '../../chart/chartReducer'; import * as exploreActions from '../actions/exploreActions'; import * as saveModalActions from '../actions/saveModalActions'; import * as chartActions from '../../chart/chartAction'; import { Logger, ActionLog, LOG_ACTIONS_PAGE_LOAD, LOG_ACTIONS_LOAD_EVENT, LOG_ACTIONS_RENDER_EVENT } from '../../logger'; const propTypes = { actions: PropTypes.object.isRequired, datasource_type: PropTypes.string.isRequired, isDatasourceMetaLoading: PropTypes.bool.isRequired, chart: PropTypes.shape(chartPropType).isRequired, slice: PropTypes.object, controls: PropTypes.object.isRequired, forcedHeight: PropTypes.string, form_data: PropTypes.object.isRequired, standalone: PropTypes.bool.isRequired, timeout: PropTypes.number, impressionId: PropTypes.string, }; class ExploreViewContainer extends React.Component { constructor(props) { super(props); this.firstLoad = true; this.loadingLog = new ActionLog({ impressionId: props.impressionId, actionType: LOG_ACTIONS_PAGE_LOAD, source: 'slice', sourceId: props.slice ? props.slice.slice_id : 0, eventNames: [LOG_ACTIONS_LOAD_EVENT, LOG_ACTIONS_RENDER_EVENT], }); Logger.start(this.loadingLog); this.state = { height: this.getHeight(), width: this.getWidth(), showModal: false, }; this.addHistory = this.addHistory.bind(this); this.handleResize = this.handleResize.bind(this); this.handlePopstate = this.handlePopstate.bind(this); } componentDidMount() { window.addEventListener('resize', this.handleResize); window.addEventListener('popstate', this.handlePopstate); this.addHistory({ isReplace: true }); } componentWillReceiveProps(np) { if (this.firstLoad && ['rendered', 'failed', 'stopped'].indexOf(np.chart.chartStatus) > -1) { Logger.end(this.loadingLog); this.firstLoad = false; } if (np.controls.viz_type.value !== this.props.controls.viz_type.value) { this.props.actions.resetControls(); this.props.actions.triggerQuery(true, this.props.chart.chartKey); } if (np.controls.datasource.value !== this.props.controls.datasource.value) { this.props.actions.fetchDatasourceMetadata(np.form_data.datasource, true); } // if any control value changed and it's an instant control if (this.instantControlChanged(this.props.controls, np.controls)) { this.props.actions.updateQueryFormData( getFormDataFromControls(np.controls), this.props.chart.chartKey); this.props.actions.renderTriggered(new Date().getTime(), this.props.chart.chartKey); } } /* eslint no-unused-vars: 0 */ componentDidUpdate(prevProps, prevState) { this.triggerQueryIfNeeded(); if (this.instantControlChanged(prevProps.controls, this.props.controls)) { this.addHistory({}); } } componentWillUnmount() { window.removeEventListener('resize', this.handleResize); window.removeEventListener('popstate', this.handlePopstate); } onQuery() { // remove alerts when query this.props.actions.removeControlPanelAlert(); this.props.actions.triggerQuery(true, this.props.chart.chartKey); this.addHistory({}); } onStop() { return this.props.chart.queryRequest.abort(); } getWidth() { return `${window.innerWidth}px`; } getHeight() { if (this.props.forcedHeight) { return this.props.forcedHeight + 'px'; } const navHeight = this.props.standalone ? 0 : 90; return `${window.innerHeight - navHeight}px`; } instantControlChanged(prevControls, currentControls) { return Object.keys(currentControls).some(key => ( currentControls[key].renderTrigger && typeof prevControls[key] !== 'undefined' && !areObjectsEqual(currentControls[key].value, prevControls[key].value) )); } triggerQueryIfNeeded() { if (this.props.chart.triggerQuery && !this.hasErrors()) { this.props.actions.runQuery(this.props.form_data, false, this.props.timeout, this.props.chart.chartKey); } } addHistory({ isReplace = false, title }) { const { payload } = getExploreUrlAndPayload({ formData: this.props.form_data }); const longUrl = getExploreLongUrl(this.props.form_data); if (isReplace) { history.replaceState( payload, title, longUrl); } else { history.pushState( payload, title, longUrl); } // it seems some browsers don't support pushState title attribute if (title) { document.title = title; } } handleResize() { clearTimeout(this.resizeTimer); this.resizeTimer = setTimeout(() => { this.setState({ height: this.getHeight(), width: this.getWidth() }); }, 250); } handlePopstate() { const formData = history.state; if (formData && Object.keys(formData).length) { this.props.actions.setExploreControls(formData); this.props.actions.runQuery( formData, false, this.props.timeout, this.props.chart.chartKey, ); } } toggleModal() { this.setState({ showModal: !this.state.showModal }); } hasErrors() { const ctrls = this.props.controls; return Object.keys(ctrls).some( k => ctrls[k].validationErrors && ctrls[k].validationErrors.length > 0); } renderErrorMessage() { // Returns an error message as a node if any errors are in the store const errors = []; for (const controlName in this.props.controls) { const control = this.props.controls[controlName]; if (control.validationErrors && control.validationErrors.length > 0) { errors.push(