diff --git a/.babelrc b/.babelrc index 8d1f42041c21a72e2d6bc945d149d64720addc27..d4a4a37fbc546a9ef6d1541bb9451c385cd2c27f 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,13 @@ { - "plugins": ["transform-flow-strip-types", "add-react-displayname", "transform-decorators-legacy"], + "plugins": [ + "transform-flow-strip-types", + "add-react-displayname", + "transform-decorators-legacy", + ["transform-builtin-extend", { + "globals": ["Error"], + "approximate": true + }] + ], "presets": ["es2015", "stage-0", "react"], "env": { "development": { diff --git a/frontend/src/metabase/lib/visualization_settings.js b/frontend/src/metabase/lib/visualization_settings.js index ec1b54dc69204aee700bfda26783e014053281d1..ab59f1ef047d92154563930105420173c07bc182 100644 --- a/frontend/src/metabase/lib/visualization_settings.js +++ b/frontend/src/metabase/lib/visualization_settings.js @@ -662,7 +662,7 @@ const SETTINGS = { }, "funnel.dimension": { section: "Data", - title: "Dimension", + title: "Step", widget: ChartSettingSelect, isValid: ([{ card, data }], vizSettings) => columnsAreValid(card.visualization_settings["funnel.dimension"], data, isDimension), diff --git a/frontend/src/metabase/query_builder/components/DataSelector.jsx b/frontend/src/metabase/query_builder/components/DataSelector.jsx index 3df0d6dfed5d4629b45995324d7b2db57857e067..55f12b87d360e6489575e18f85f9c97422267001 100644 --- a/frontend/src/metabase/query_builder/components/DataSelector.jsx +++ b/frontend/src/metabase/query_builder/components/DataSelector.jsx @@ -396,6 +396,7 @@ export default class DataSelector extends Component { <PopoverWithTrigger id="DataPopover" ref="popover" + sizeToFit isInitiallyOpen={this.props.isInitiallyOpen} triggerElement={triggerElement} triggerClasses="flex align-center" diff --git a/frontend/src/metabase/visualizations/Funnel.jsx b/frontend/src/metabase/visualizations/Funnel.jsx index b9d09edcd2de2955454955ffdcbfb29c063774b8..b638f2a9c572e626e5bcc7ba894148ada3169fbe 100644 --- a/frontend/src/metabase/visualizations/Funnel.jsx +++ b/frontend/src/metabase/visualizations/Funnel.jsx @@ -32,19 +32,27 @@ export default class Funnel extends Component<*, VisualizationProps, *> { return cols.length === 2; } - static checkRenderable(cols, rows, settings) { - if (rows.length < 1) { throw new MinRowsError(1, rows.length); } - if ((rows.length > 1 || cols.length > 2) && (!settings["funnel.dimension"] || !settings["funnel.metric"])) { - throw new ChartSettingsError("Which fields do you want to use for the X and Y axes?", "Data", "Choose fields"); + static checkRenderable([{ data: { cols, rows} }], settings) { + if (rows.length < 1) { + throw new MinRowsError(1, rows.length); + } + if (!settings["funnel.dimension"] || !settings["funnel.metric"]) { + throw new ChartSettingsError("Which fields do you want to use?", "Data", "Choose fields"); } } static transformSeries(series) { let [{ card, data: { rows, cols }}] = series; - if (!card._transformed && series.length === 1 && rows.length > 1) { - const settings = getSettings(series); - const dimensionIndex = _.findIndex(cols, (col) => col.name === settings["funnel.dimension"]); - const metricIndex = _.findIndex(cols, (col) => col.name === settings["funnel.metric"]); + + const settings = getSettings(series); + + const dimensionIndex = _.findIndex(cols, (col) => col.name === settings["funnel.dimension"]); + const metricIndex = _.findIndex(cols, (col) => col.name === settings["funnel.metric"]); + + if (!card._transformed && + series.length === 1 && rows.length > 1 && + dimensionIndex >= 0 && metricIndex >= 0 + ) { return rows.map(row => ({ card: { ...card, diff --git a/frontend/src/metabase/visualizations/Map.jsx b/frontend/src/metabase/visualizations/Map.jsx index 381cf5b7e9491e71d0e196988b77cf7b1e7f0409..c209c22d095ceccedc16d93a1571e89450dab6d3 100644 --- a/frontend/src/metabase/visualizations/Map.jsx +++ b/frontend/src/metabase/visualizations/Map.jsx @@ -22,7 +22,7 @@ export default class Map extends Component<*, VisualizationProps, *> { return true; } - static checkRenderable(cols, rows, settings) { + static checkRenderable([{ data: { cols, rows} }], settings) { if (settings["map.type"] === "pin") { if (!settings["map.longitude_column"] || !settings["map.latitude_column"]) { throw new ChartSettingsError("Please select longitude and latitude columns in the chart settings.", "Data"); diff --git a/frontend/src/metabase/visualizations/PieChart.jsx b/frontend/src/metabase/visualizations/PieChart.jsx index 29a20604ce5bcc6945a842ee0699f90ad2980319..88b3ec6ba34561bc5fbfee585b9d3849cf7deffc 100644 --- a/frontend/src/metabase/visualizations/PieChart.jsx +++ b/frontend/src/metabase/visualizations/PieChart.jsx @@ -43,7 +43,7 @@ export default class PieChart extends Component<*, Props, *> { return cols.length === 2; } - static checkRenderable(cols, rows, settings) { + static checkRenderable([{ data: { cols, rows} }], settings) { if (!settings["pie.dimension"] || !settings["pie.metric"]) { throw new ChartSettingsError("Which columns do want to use?", "Data"); } diff --git a/frontend/src/metabase/visualizations/PinMap.jsx b/frontend/src/metabase/visualizations/PinMap.jsx index 25b7b7926083fc7dd5e92e28d2c5d53c0fd87311..df30ad4e47b8d411ea9ff7555c6679dc791e7323 100644 --- a/frontend/src/metabase/visualizations/PinMap.jsx +++ b/frontend/src/metabase/visualizations/PinMap.jsx @@ -39,7 +39,7 @@ export default class PinMap extends Component<*, Props, State> { return hasLatitudeAndLongitudeColumns(cols); } - static checkRenderable(cols, rows) { + static checkRenderable([{ data: { cols, rows} }]) { if (!hasLatitudeAndLongitudeColumns(cols)) { throw new LatitudeLongitudeError(); } } diff --git a/frontend/src/metabase/visualizations/Progress.jsx b/frontend/src/metabase/visualizations/Progress.jsx index 7a674ff0624d145409c150612488b16c0885069d..186421ee7e1f4d7e0d40d8869c3822124cb753ab 100644 --- a/frontend/src/metabase/visualizations/Progress.jsx +++ b/frontend/src/metabase/visualizations/Progress.jsx @@ -27,7 +27,7 @@ export default class Progress extends Component<*, VisualizationProps, *> { return rows.length === 1 && cols.length === 1; } - static checkRenderable(cols, rows) { + static checkRenderable([{ data: { cols, rows} }]) { if (!isNumeric(cols[0])) { throw new Error("Progress visualization requires a number."); } diff --git a/frontend/src/metabase/visualizations/Scalar.jsx b/frontend/src/metabase/visualizations/Scalar.jsx index b6664810ea1bfa51d012dd6535b450575ffef729..4e353aa692593fc558da4f238a67abccbb7f59d3 100644 --- a/frontend/src/metabase/visualizations/Scalar.jsx +++ b/frontend/src/metabase/visualizations/Scalar.jsx @@ -31,7 +31,7 @@ export default class Scalar extends Component<*, VisualizationProps, *> { return rows.length === 1 && cols.length === 1; } - static checkRenderable(cols, rows) { + static checkRenderable([{ data: { cols, rows} }]) { // scalar can always be rendered, nothing needed here } diff --git a/frontend/src/metabase/visualizations/Table.jsx b/frontend/src/metabase/visualizations/Table.jsx index 6f4b3663289b3944086306a3a6027217d5e50392..7bec7cd8bf6f7d44b7450d8cb7b71ff350bdd1b5 100644 --- a/frontend/src/metabase/visualizations/Table.jsx +++ b/frontend/src/metabase/visualizations/Table.jsx @@ -39,7 +39,7 @@ export default class Table extends Component<*, Props, State> { return true; } - static checkRenderable(cols, rows) { + static checkRenderable([{ data: { cols, rows} }]) { // scalar can always be rendered, nothing needed here } diff --git a/frontend/src/metabase/visualizations/components/ChoroplethMap.jsx b/frontend/src/metabase/visualizations/components/ChoroplethMap.jsx index cb91daa0714cddd45be4534b05f8220e4e2d4b5a..77fc0451c29f4efba18d1bd227f088c9a68ffb70 100644 --- a/frontend/src/metabase/visualizations/components/ChoroplethMap.jsx +++ b/frontend/src/metabase/visualizations/components/ChoroplethMap.jsx @@ -66,7 +66,7 @@ export default class ChoroplethMap extends Component { return cols.length > 1 && isString(cols[0]); } - static checkRenderable(cols, rows) { + static checkRenderable([{ data: { cols, rows} }]) { if (cols.length < 2) { throw new MinColumnsError(2, cols.length); } } diff --git a/frontend/src/metabase/visualizations/components/FunnelNormal.jsx b/frontend/src/metabase/visualizations/components/FunnelNormal.jsx index 498d4d0d90bd42b67abcd21e0e0f298a7c0e451c..6023ccd3e0ff9d2155d5c6c610bda9ee1ea69a26 100644 --- a/frontend/src/metabase/visualizations/components/FunnelNormal.jsx +++ b/frontend/src/metabase/visualizations/components/FunnelNormal.jsx @@ -73,7 +73,7 @@ export default class Funnel extends Component<*, VisualizationProps, *> { } }]; - var remaining: number = rows[0][1]; + var remaining: number = rows[0][metricIndex]; rows.map((row, rowIndex) => { remaining -= (infos[rowIndex].value - row[metricIndex]); @@ -92,7 +92,7 @@ export default class Funnel extends Component<*, VisualizationProps, *> { value: formatDimension(row[dimensionIndex]), }, { - key: getFriendlyName(cols[dimensionIndex]), + key: getFriendlyName(cols[metricIndex]), value: formatMetric(row[metricIndex]), }, { @@ -104,12 +104,16 @@ export default class Funnel extends Component<*, VisualizationProps, *> { }); // Remove initial setup - infos = infos.filter((e, i) => i !== 0); + infos = infos.slice(1); let initial = infos[0]; return ( - <div className={cx(className, styles.Funnel, funnelSmallSize ? styles.Small : null, 'flex', funnelSmallSize ? 'p1' : 'p2')}> + <div className={cx(className, styles.Funnel, 'flex', { + [styles.Small]: funnelSmallSize, + "p1": funnelSmallSize, + "p2": !funnelSmallSize + })}> <div className={cx(styles.FunnelStep, styles.Initial, 'flex flex-column')}> <Ellipsified className={styles.Head}>{formatDimension(rows[0][dimensionIndex])}</Ellipsified> <div className={styles.Start}> @@ -122,17 +126,17 @@ export default class Funnel extends Component<*, VisualizationProps, *> { <div className={styles.Subtitle}> </div> </div> </div> - {infos.filter((e, i) => i !== 0).map((info, index) => + {infos.slice(1).map((info, index) => <div key={index} className={cx(styles.FunnelStep, 'flex flex-column')}> <Ellipsified className={styles.Head}>{formatDimension(rows[index + 1][dimensionIndex])}</Ellipsified> <div className={styles.Graph} - onMouseMove={!funnelSmallSize ? null : (event) => onHoverChange({ + onMouseMove={(event) => onHoverChange({ index: index, event: event.nativeEvent, data: info.tooltip, })} - onMouseLeave={!funnelSmallSize ? null : () => onHoverChange(null)} + onMouseLeave={() => onHoverChange(null)} style={calculateGraphStyle(info, index, infos.length + 1, hovered)}> </div> <div className={styles.Infos}> <div className={styles.Title}>{formatPercent(info.value / initial.value)}</div> diff --git a/frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx b/frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx index 1344804ab0239afddf0a1535aa4cf8eb9b21126e..641e2af7b18b2bde535a09220d6f6a91fe4be58c 100644 --- a/frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx +++ b/frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx @@ -52,7 +52,7 @@ export default class LineAreaBarChart extends Component<*, VisualizationProps, * return getChartTypeFromData(cols, rows, false) != null; } - static checkRenderable(cols, rows, settings) { + static checkRenderable([{ data: { cols, rows} }], settings) { if (rows.length < 1) { throw new MinRowsError(1, rows.length); } const dimensions = (settings["graph.dimensions"] || []).filter(name => name); const metrics = (settings["graph.metrics"] || []).filter(name => name); diff --git a/frontend/src/metabase/visualizations/components/Visualization.jsx b/frontend/src/metabase/visualizations/components/Visualization.jsx index e1dec87f8cab501e0ff7d27504a6bbf70a8ecd13..72a6fc7e211fff3430ad451e80856af5990ddb16 100644 --- a/frontend/src/metabase/visualizations/components/Visualization.jsx +++ b/frontend/src/metabase/visualizations/components/Visualization.jsx @@ -195,8 +195,7 @@ export default class Visualization extends Component<*, Props, State> { } else { try { if (CardVisualization.checkRenderable) { - // $FlowFixMe - CardVisualization.checkRenderable(series[0].data.cols, series[0].data.rows, settings); + CardVisualization.checkRenderable(series, settings); } } catch (e) { error = e.message || "Could not display this chart with this data."; diff --git a/frontend/src/metabase/visualizations/lib/errors.js b/frontend/src/metabase/visualizations/lib/errors.js index 22e3c462a7b0393b47ac0348ba21dd38e7bea6f7..f894ec7586706b5f44bc62095e86fb4c382c7fec 100644 --- a/frontend/src/metabase/visualizations/lib/errors.js +++ b/frontend/src/metabase/visualizations/lib/errors.js @@ -2,6 +2,8 @@ import { inflect } from "metabase/lib/formatting"; +// NOTE: extending Error with Babel requires babel-plugin-transform-builtin-extend + export class MinColumnsError extends Error { constructor(minColumns: number, actualColumns: number) { super(`Doh! The data from your query doesn't fit the chosen display choice. This visualization requires at least ${actualColumns} ${inflect("column", actualColumns)} of data.`); diff --git a/package.json b/package.json index b2d7e28e3d51fffd353ca3a657a69293954f379d..1c0b8e04c8f0b9967483a7efd6fbb84280f964d8 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "babel-eslint": "^7.1.1", "babel-loader": "^6.2.4", "babel-plugin-add-react-displayname": "^0.0.4", + "babel-plugin-transform-builtin-extend": "^1.1.0", "babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-flow-strip-types": "^6.8.0", "babel-preset-es2015": "^6.6.0", diff --git a/yarn.lock b/yarn.lock index 770beb9cd0437ee708ff5e8e3099c5b017aa1fec..2828a1c1a4a5bfad2ebad89f7d69bc0438191c7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -739,6 +739,13 @@ babel-plugin-transform-async-to-generator@^6.16.0, babel-plugin-transform-async- babel-plugin-syntax-async-functions "^6.8.0" babel-runtime "^6.0.0" +babel-plugin-transform-builtin-extend@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-builtin-extend/-/babel-plugin-transform-builtin-extend-1.1.0.tgz#460c9f3801467ea373366987c9638f939867dfa4" + dependencies: + babel-runtime "^6.2.0" + babel-template "^6.3.0" + babel-plugin-transform-class-constructor-call@^6.3.13: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.18.0.tgz#80855e38a1ab47b8c6c647f8ea1bcd2c00ca3aae" @@ -6054,10 +6061,6 @@ resize-observer-polyfill@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.3.2.tgz#f467efc15a86d9ee5096fd6d7f628c8a54bb805a" -resize-observer-polyfill@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.3.2.tgz#f467efc15a86d9ee5096fd6d7f628c8a54bb805a" - resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"