diff --git a/frontend/src/metabase/visualizations/components/Visualization.jsx b/frontend/src/metabase/visualizations/components/Visualization.jsx index f336f993a9ce5ee9f22143bf55bbe44ae423d67a..fb9ed971871fa88dda959886480aa224485cfeb7 100644 --- a/frontend/src/metabase/visualizations/components/Visualization.jsx +++ b/frontend/src/metabase/visualizations/components/Visualization.jsx @@ -258,7 +258,7 @@ export default class Visualization extends Component { } render() { - const { actionButtons, className, showTitle, isDashboard, width, height, errorIcon, isSlow, expectedDuration, replacementContent } = this.props; + const { actionButtons, className, showTitle, isDashboard, width, height, isSlow, expectedDuration, replacementContent } = this.props; const { series, CardVisualization } = this.state; const small = width < 330; @@ -270,6 +270,7 @@ export default class Visualization extends Component { } let error = this.props.error || this.state.error; + let errorIcon = this.props.errorIcon || this.state.errorIcon; let loading = !(series && series.length > 0 && _.every(series, (s) => s.data)); let noResults = false; @@ -287,17 +288,22 @@ export default class Visualization extends Component { } } catch (e) { error = e.message || "Could not display this chart with this data."; - if (e instanceof ChartSettingsError && this.props.onOpenChartSettings) { - error = ( - <div> - <div>{error}</div> - <div className="mt2"> - <button className="Button Button--primary Button--medium" onClick={this.props.onOpenChartSettings}> - {e.buttonText} - </button> + if (e instanceof ChartSettingsError) { + if (this.props.onOpenChartSettings) { + error = ( + <div> + <div>{error}</div> + <div className="mt2"> + <button className="Button Button--primary Button--medium" onClick={this.props.onOpenChartSettings}> + {e.buttonText} + </button> + </div> </div> - </div> - ); + ); + } + if (e.section === "Columns") { + errorIcon = 'empty'; + } } else if (e instanceof MinRowsError) { noResults = true; } diff --git a/frontend/src/metabase/visualizations/visualizations/Table.jsx b/frontend/src/metabase/visualizations/visualizations/Table.jsx index 7082cc4d2ace129afa74965cc743e9b240f808d5..1a3e4f112ebd4cc6e56a26d4b8c74cc2e61bcf0f 100644 --- a/frontend/src/metabase/visualizations/visualizations/Table.jsx +++ b/frontend/src/metabase/visualizations/visualizations/Table.jsx @@ -11,9 +11,9 @@ import Query from "metabase/lib/query"; import { isMetric, isDimension } from "metabase/lib/schema_metadata"; import { columnsAreValid, getFriendlyName } from "metabase/visualizations/lib/utils"; import ChartSettingOrderedFields from "metabase/visualizations/components/settings/ChartSettingOrderedFields.jsx"; -import { MinColumnsError } from "metabase/visualizations/lib/errors"; import _ from "underscore"; +import RetinaImage from "react-retina-image"; import { getIn } from "icepick"; import type { DatasetData } from "metabase/meta/types/Dataset"; @@ -45,10 +45,6 @@ export default class Table extends Component { static checkRenderable([{ data: { cols, rows} }], settings) { // scalar can always be rendered, nothing needed here - const enabledColumns = (settings["table.columns"] || []).filter(f => f.enabled); - if (enabledColumns.length < 1) { - throw new MinColumnsError(1, enabledColumns.length); - } } static settings = { @@ -130,21 +126,38 @@ export default class Table extends Component { const { data } = this.state; const sort = getIn(card, ["dataset_query", "query", "order_by"]) || null; const isPivoted = settings["table.pivot"]; + const isColumnsDisabled = (settings["table.columns"] || []).filter(f => f.enabled).length < 1; const TableComponent = isDashboard ? TableSimple : TableInteractive; if (!data) { return null; } - return ( - // $FlowFixMe - <TableComponent - {...this.props} - data={data} - isPivoted={isPivoted} - sort={sort} - /> - ); + if (isColumnsDisabled) { + return ( + <div className={"flex-full px1 pb1 text-centered flex flex-column layout-centered " + (isDashboard ? "text-slate-light" : "text-slate")}> + <RetinaImage + width={99} + src="app/assets/img/hidden-field.png" + forceOriginalDimensions={false} + className="mb2" + /> + <span className="h4 text-bold"> + Every field is hidden right now + </span> + </div> + ) + } else { + return ( + // $FlowFixMe + <TableComponent + {...this.props} + data={data} + isPivoted={isPivoted} + sort={sort} + /> + ); + } } } diff --git a/frontend/test/visualizations/components/Visualization.integ.spec.js b/frontend/test/visualizations/components/Visualization.integ.spec.js index 275a365fa2ffa92c287d8d6993cf17f0762dfbf3..7b26d15eb74e10153323408676bf593053424d6b 100644 --- a/frontend/test/visualizations/components/Visualization.integ.spec.js +++ b/frontend/test/visualizations/components/Visualization.integ.spec.js @@ -2,7 +2,7 @@ import "__support__/integrated_tests"; import React from "react"; -import Visualization from "metabase/visualizations/components/Visualization"; +import ChartSettingOrderedFields from "metabase/visualizations/components/Visualization"; import LegendHeader from "metabase/visualizations/components/LegendHeader"; import LegendItem from "metabase/visualizations/components/LegendItem"; diff --git a/frontend/test/visualizations/components/settings/ChartSettingOrderedFields.unit.spec.js b/frontend/test/visualizations/components/settings/ChartSettingOrderedFields.unit.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..4df304f2f02fda5af4b3437401056de4b85d5ef9 --- /dev/null +++ b/frontend/test/visualizations/components/settings/ChartSettingOrderedFields.unit.spec.js @@ -0,0 +1,121 @@ +import "__support__/integrated_tests"; + +import React from "react"; + +import ChartSettingOrderedFields from "metabase/visualizations/components/settings/ChartSettingOrderedFields"; + +import { ScalarCard } from "../__support__/visualizations"; + +import { mount } from "enzyme"; + +function renderVisualization(props) { + return mount(<ChartSettingOrderedFields className="spread" {...props} />); +} + +function getScalarTitles (scalarComponent) { + return scalarComponent.find('.Scalar-title').map((title) => title.text()) +} + +function getTitles(viz) { + return viz.find(LegendHeader).map(header => + header.find(LegendItem).map((item) => item.props().title) + ) +} + +describe("Visualization", () => { + describe("not in dashboard", () => { + describe("scalar card", () => { + it("should not render title", () => { + let viz = renderVisualization({ series: [ScalarCard("Foo")] }); + expect(getScalarTitles(viz)).toEqual([]); + }); + }); + + describe("line card", () => { + it("should not render card title", () => { + let viz = renderVisualization({ series: [LineCard("Foo")] }); + expect(getTitles(viz)).toEqual([]); + }); + it("should not render setting title", () => { + let viz = renderVisualization({ series: [LineCard("Foo", { card: { visualization_settings: { "card.title": "Foo_title" }}})] }); + expect(getTitles(viz)).toEqual([]); + }); + it("should render breakout multiseries titles", () => { + let viz = renderVisualization({ series: [MultiseriesLineCard("Foo")] }); + expect(getTitles(viz)).toEqual([ + ["Foo_cat1", "Foo_cat2"] + ]); + }); + }); + }); + + describe("in dashboard", () => { + describe("scalar card", () => { + it("should render a scalar title, not a legend title", () => { + let viz = renderVisualization({ series: [ScalarCard("Foo")], showTitle: true, isDashboard: true }); + expect(getTitles(viz)).toEqual([]); + expect(getScalarTitles(viz).length).toEqual(1); + }); + it("should render title when loading", () => { + let viz = renderVisualization({ series: [ScalarCard("Foo", { data: null })], showTitle: true }); + expect(getTitles(viz)).toEqual([ + ["Foo_name"] + ]); + }); + it("should render title when there's an error", () => { + let viz = renderVisualization({ series: [ScalarCard("Foo")], showTitle: true, error: "oops" }); + expect(getTitles(viz)).toEqual([ + ["Foo_name"] + ]); + }); + it("should not render scalar title", () => { + let viz = renderVisualization({ series: [ScalarCard("Foo")], showTitle: true }); + expect(getTitles(viz)).toEqual([]); + }); + it("should render multi scalar titles", () => { + let viz = renderVisualization({ series: [ScalarCard("Foo"), ScalarCard("Bar")], showTitle: true }); + expect(getTitles(viz)).toEqual([ + ["Foo_name", "Bar_name"] + ]); + }); + }); + + describe("line card", () => { + it("should render normal title", () => { + let viz = renderVisualization({ series: [LineCard("Foo")], showTitle: true }); + expect(getTitles(viz)).toEqual([ + ["Foo_name"] + ]); + }); + it("should render normal title and breakout multiseries titles", () => { + let viz = renderVisualization({ series: [MultiseriesLineCard("Foo")], showTitle: true }); + expect(getTitles(viz)).toEqual([ + ["Foo_name"], + ["Foo_cat1", "Foo_cat2"] + ]); + }); + it("should render dashboard multiseries titles", () => { + let viz = renderVisualization({ series: [LineCard("Foo"), LineCard("Bar")], showTitle: true }); + expect(getTitles(viz)).toEqual([ + ["Foo_name", "Bar_name"] + ]); + }); + it("should render dashboard multiseries titles and chart setting title", () => { + let viz = renderVisualization({ series: [ + LineCard("Foo", { card: { visualization_settings: { "card.title": "Foo_title" }}}), + LineCard("Bar") + ], showTitle: true }); + expect(getTitles(viz)).toEqual([ + ["Foo_title"], + ["Foo_name", "Bar_name"] + ]); + }); + it("should render multiple breakout multiseries titles (with both card titles and breakout values)", () => { + let viz = renderVisualization({ series: [MultiseriesLineCard("Foo"), MultiseriesLineCard("Bar")], showTitle: true }); + expect(getTitles(viz)).toEqual([ + ["Foo_name: Foo_cat1", "Foo_name: Foo_cat2", "Bar_name: Bar_cat1", "Bar_name: Bar_cat2"] + ]); + }); + }); + }); +}); diff --git a/resources/frontend_client/app/assets/img/hidden-field.png b/resources/frontend_client/app/assets/img/hidden-field.png new file mode 100644 index 0000000000000000000000000000000000000000..534b47c4de9a55a5ff6563e43a17e2fac7023339 Binary files /dev/null and b/resources/frontend_client/app/assets/img/hidden-field.png differ diff --git a/resources/frontend_client/app/assets/img/hidden-field@2x.png b/resources/frontend_client/app/assets/img/hidden-field@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5fb615c47eb72f0bf27da619014aaa343d90f289 Binary files /dev/null and b/resources/frontend_client/app/assets/img/hidden-field@2x.png differ