diff --git a/frontend/src/components/AbsoluteContainer.jsx b/frontend/src/components/AbsoluteContainer.jsx deleted file mode 100644 index b089af83826547492054ce95d69e270257d70bc1..0000000000000000000000000000000000000000 --- a/frontend/src/components/AbsoluteContainer.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React, { Component, PropTypes } from "react"; - -export default ComposedComponent => class extends Component { - static displayName = "AbsoluteContainer["+(ComposedComponent.displayName || ComposedComponent.name)+"]"; - - render() { - const { className, style, width, height } = this.props; - return ( - <div className={className} style={{ position: "relative", ...style }}> - <div style={{ position: "absolute", top: 0, left: 0, width: width, height: height }}> - { width != null && height != null && - <ComposedComponent - width={width} - height={height} - {...this.props} - /> - } - </div> - </div> - ); - } -} diff --git a/frontend/src/dashboard/components/DashCard.jsx b/frontend/src/dashboard/components/DashCard.jsx index ba77ea073ac762db4f3f44467262f95755ec71ac..9a6ee29f23f65be659402dc042d6875ca1d5a682 100644 --- a/frontend/src/dashboard/components/DashCard.jsx +++ b/frontend/src/dashboard/components/DashCard.jsx @@ -4,8 +4,6 @@ import ReactDOM from "react-dom"; import visualizations from "metabase/visualizations"; import Visualization from "metabase/visualizations/components/Visualization.jsx"; -import LegendHeader from "metabase/visualizations/components/LegendHeader.jsx"; -import LoadingSpinner from "metabase/components/LoadingSpinner.jsx"; import Icon from "metabase/components/Icon.jsx"; @@ -74,8 +72,9 @@ export default class DashCard extends Component { } } - renderCard(CardVisualization) { + render() { const { dashcard, cardData, isEditing, onAddSeries, onRemove } = this.props; + const cards = [dashcard.card].concat(dashcard.series || []); const series = cards .map(card => ({ @@ -86,28 +85,25 @@ export default class DashCard extends Component { const errors = series.map(s => s.error).filter(e => e); const error = errors[0] || this.state.error; + let errorMessage; if (error) { - let message; if (error.data) { - message = error.data.message; + errorMessage = error.data.message; } else if (error.status === 503) { - message = "I'm sorry, the server timed out while asking your question." + errorMessage = "I'm sorry, the server timed out while asking your question." } else if (typeof error === "string") { - message = error; + errorMessage = error; } else { - message = "Oh snap! Something went wrong loading this card :sad:"; + errorMessage = "Oh snap! Something went wrong loading this card :sad:"; } - return ( - <div className="p1 text-centered flex-full flex flex-column layout-centered"> - <h2 className="text-normal text-grey-2">{message}</h2> - </div> - ); } - if (series.length > 0 && _.every(series, (s) => s.data)) { - return ( + const CardVisualization = visualizations.get(series[0].card.display); + return ( + <div className={"Card bordered rounded flex flex-column " + cx({ "Card--recent": dashcard.isAdded })}> <Visualization className="flex-full" + error={errorMessage} series={series} isDashboard={true} isEditing={isEditing} @@ -115,32 +111,6 @@ export default class DashCard extends Component { actionButtons={isEditing ? <DashCardActionButtons series={series} visualization={CardVisualization} onRemove={onRemove} onAddSeries={onAddSeries} /> : undefined} onUpdateVisualizationSetting={this.props.onUpdateVisualizationSetting} /> - ); - } - - return ( - <div className="p1 text-brand text-centered flex-full flex flex-column layout-centered"> - <LoadingSpinner /> - <h1 className="ml1 text-normal text-grey-2">Loading...</h1> - </div> - ); - } - - render() { - const { dashcard, onAddSeries, onRemove, isEditing } = this.props; - const series = [dashcard.card].concat(dashcard.series || []).map(card => ({ card })); - const Viz = visualizations.get(series[0].card.display); - return ( - <div className={"Card bordered rounded flex flex-column " + cx({ "Card--recent": dashcard.isAdded })}> - { !Viz.noHeader && - <div className="p1"> - <LegendHeader - series={series} - actionButtons={isEditing ? <DashCardActionButtons visualization={Viz} series={series} onRemove={onRemove} onAddSeries={onAddSeries} /> : undefined} - /> - </div> - } - {this.renderCard(Viz)} </div> ); } diff --git a/frontend/src/visualizations/components/CardRenderer.jsx b/frontend/src/visualizations/components/CardRenderer.jsx index eec202f5357748567497218e70688d7750b30999..c7a007cdc71fa45b252fffef21f0470689293a0c 100644 --- a/frontend/src/visualizations/components/CardRenderer.jsx +++ b/frontend/src/visualizations/components/CardRenderer.jsx @@ -2,7 +2,6 @@ import React, { Component, PropTypes } from "react"; import ReactDOM from "react-dom"; import ExplicitSize from "metabase/components/ExplicitSize.jsx"; -import AbsoluteContainer from "metabase/components/AbsoluteContainer.jsx"; import * as charting from "metabase/visualizations/lib/CardRenderer"; @@ -10,9 +9,9 @@ import { isSameSeries } from "metabase/visualizations/lib/utils"; import { getSettingsForVisualization } from "metabase/lib/visualization_settings"; import dc from "dc"; +import cx from "classnames"; @ExplicitSize -@AbsoluteContainer export default class CardRenderer extends Component { static propTypes = { chartType: PropTypes.string.isRequired, @@ -27,8 +26,7 @@ export default class CardRenderer extends Component { } componentDidMount() { - // avoid race condition with initial layout - setTimeout(() => this.renderChart()); + this.renderChart(); } componentDidUpdate() { @@ -55,7 +53,11 @@ export default class CardRenderer extends Component { // reset the DOM: let element = parent.firstChild; - parent.removeChild(element); + if (element) { + parent.removeChild(element); + } + + // create a new container element element = document.createElement("div"); parent.appendChild(element); @@ -80,9 +82,7 @@ export default class CardRenderer extends Component { render() { return ( - <div className="Card-outer"> - <div ref="chart"></div> - </div> + <div className={cx(this.props.className, "Card-outer")}></div> ); } } diff --git a/frontend/src/visualizations/components/Legend.css b/frontend/src/visualizations/components/Legend.css index e2c2f1be89e3f476368560cd53cdf5f4735170e9..fa82edb740ab76933401d6d1abdea075e3c74997 100644 --- a/frontend/src/visualizations/components/Legend.css +++ b/frontend/src/visualizations/components/Legend.css @@ -13,7 +13,6 @@ fullscreen dashboard mode :local .LegendHeader { margin-top: 0.5em; margin-bottom: 0.5em; - height: 24px; } :local(.LegendItem).muted { diff --git a/frontend/src/visualizations/components/Visualization.jsx b/frontend/src/visualizations/components/Visualization.jsx index cca5d898f809a53d826631ef5d6b7273c859f398..f7a9cd733225038919377ad1b665af4569b657ca 100644 --- a/frontend/src/visualizations/components/Visualization.jsx +++ b/frontend/src/visualizations/components/Visualization.jsx @@ -1,10 +1,20 @@ import React, { Component, PropTypes } from "react"; +import ExplicitSize from "metabase/components/ExplicitSize.jsx"; +import LegendHeader from "metabase/visualizations/components/LegendHeader.jsx"; +import LoadingSpinner from "metabase/components/LoadingSpinner.jsx"; +import Icon from "metabase/components/Icon.jsx"; +import Tooltip from "metabase/components/Tooltip.jsx"; + import visualizations from "metabase/visualizations"; import i from "icepick"; import _ from "underscore"; +import cx from "classnames"; + +const ERROR_MESSAGE_GENERIC = "There was a problem displaying this chart."; +@ExplicitSize export default class Visualization extends Component { constructor(props, context) { super(props, context) @@ -36,29 +46,6 @@ export default class Visualization extends Component { onUpdateVisualizationSetting: (...args) => console.warn("onUpdateVisualizationSetting", args) }; - componentWillMount() { - this.componentWillReceiveProps(this.props); - } - - componentWillReceiveProps(newProps) { - let { card, data } = newProps.series[0] - if (!data) { - this.setState({ error: "No data (TODO)" }); - } else if (!card.display) { - this.setState({ error: "Chart type not set" }); - } else { - let CardVisualization = visualizations.get(card.display); - try { - if (CardVisualization.checkRenderable) { - CardVisualization.checkRenderable(data.cols, data.rows); - } - this.setState({ error: null }); - } catch (e) { - this.setState({ error: e.message || "Missing error message (TODO)" }); - } - } - } - onHoverChange(hovered) { const { renderInfo } = this.state; if (hovered) { @@ -80,33 +67,72 @@ export default class Visualization extends Component { } render() { + const { series, actionButtons, className, isDashboard, width } = this.props; + const CardVisualization = visualizations.get(series[0].card.display); + const small = width < 330; + let error = this.props.error || this.state.error; - if (error) { - return ( - <div className="QueryError flex full align-center text-error"> - <div className="QueryError-iconWrapper"> - <svg className="QueryError-icon" viewBox="0 0 32 32" width="64" height="64" fill="currentcolor"> - <path d="M4 8 L8 4 L16 12 L24 4 L28 8 L20 16 L28 24 L24 28 L16 20 L8 28 L4 24 L12 16 z "></path> - </svg> - </div> - <span className="QueryError-message">{error}</span> - </div> - ); - } else { - let { series } = this.props; - let CardVisualization = visualizations.get(series[0].card.display); - return ( - <CardVisualization - {...this.props} - series={series} - card={series[0].card} // convienence for single-series visualizations - data={series[0].data} // convienence for single-series visualizations - hovered={this.state.hovered} - onHoverChange={this.onHoverChange} - onRenderError={this.onRenderError} - onRender={this.onRender} - /> - ); + let loading = !(series.length > 0 && _.every(series, (s) => s.data)); + + if (!loading && !error) { + if (!CardVisualization) { + error = "Could not find visualization"; + } else { + try { + if (CardVisualization.checkRenderable) { + CardVisualization.checkRenderable(series[0].data.cols, series[0].data.rows); + } + } catch (e) { + error = e.message || "Could not display this chart with this data."; + } + } } + + return ( + <div className={cx(className, "flex flex-column")}> + { isDashboard && (loading || error || !CardVisualization.noHeader) ? + <div className="p1 flex-no-shrink"> + <LegendHeader + series={series} + actionButtons={actionButtons} + /> + </div> + : null + } + { error ? + <div className="flex-full px1 pb1 text-centered text-slate-light flex flex-column layout-centered"> + <Tooltip tooltip={isDashboard ? ERROR_MESSAGE_GENERIC : error} isEnabled={small}> + <div className="mb2"> + <Icon name="warning" width={50} height={50} /> + </div> + </Tooltip> + { !small && + <span className="h4 text-bold"> + { isDashboard ? ERROR_MESSAGE_GENERIC : error } + </span> + } + </div> + : loading ? + <div className="flex-full p1 text-centered text-brand flex flex-column layout-centered"> + <LoadingSpinner /> + <span className="h4 text-bold ml1 text-slate-light"> + Loading... + </span> + </div> + : + <CardVisualization + {...this.props} + className="flex-full" + series={series} + card={series[0].card} // convienence for single-series visualizations + data={series[0].data} // convienence for single-series visualizations + hovered={this.state.hovered} + onHoverChange={this.onHoverChange} + onRenderError={this.onRenderError} + onRender={this.onRender} + /> + } + </div> + ); } }