diff --git a/e2e/test/scenarios/embedding/embedding-reproductions.cy.spec.js b/e2e/test/scenarios/embedding/embedding-reproductions.cy.spec.js index 021886d417e5124b37cd74cca02c0e76e136730e..d4fd177d7c7ee48136ca099c241aaeebea39378c 100644 --- a/e2e/test/scenarios/embedding/embedding-reproductions.cy.spec.js +++ b/e2e/test/scenarios/embedding/embedding-reproductions.cy.spec.js @@ -1,6 +1,7 @@ import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database"; import { addOrUpdateDashboardCard, + assertEChartsTooltip, createDashboardWithQuestions, createNativeQuestion, createQuestionAndDashboard, @@ -10,6 +11,7 @@ import { getIframeBody, modal, openStaticEmbeddingModal, + otherSeriesChartPaths, popover, queryBuilderMain, restore, @@ -25,7 +27,7 @@ import { withDatabase, } from "e2e/support/helpers"; -const { PRODUCTS, PRODUCTS_ID } = SAMPLE_DATABASE; +const { PRODUCTS, PRODUCTS_ID, ORDERS, ORDERS_ID } = SAMPLE_DATABASE; describe.skip("issue 15860", () => { const q1IdFilter = { @@ -1050,18 +1052,88 @@ describe("issue 8490", () => { { "base-type": "type/DateTime", "temporal-unit": "month" }, ], ], + filter: [ + "time-interval", + ["field", PRODUCTS.CREATED_AT, { "base-type": "type/DateTime" }], + -12, + "month", + ], }, + limit: 100, visualization_settings: { "graph.dimensions": ["CREATED_AT", "CATEGORY"], "graph.metrics": ["count"], + "graph.max_categories_enabled": true, + "graph.max_categories": 2, }, display: "bar", enable_embedding: true, }, + { + name: "Order quantity trend", + query: { + "source-table": ORDERS_ID, + aggregation: [ + [ + "sum", + ["field", ORDERS.QUANTITY, { "base-type": "type/Integer" }], + ], + ], + breakout: [ + [ + "field", + ORDERS.CREATED_AT, + { "base-type": "type/DateTime", "temporal-unit": "month" }, + ], + ], + filter: [ + "and", + [ + "time-interval", + ["field", ORDERS.CREATED_AT, { "base-type": "type/DateTime" }], + -2, + "month", + ], + [ + "=", + [ + "field", + PRODUCTS.VENDOR, + { + "base-type": "type/Text", + "source-field": ORDERS.PRODUCT_ID, + }, + ], + "Alfreda Konopelski II Group", + ], + ], + }, + display: "smartscalar", + }, + { + name: "Pie chart", + query: { + "source-table": ORDERS_ID, + aggregation: [["count"]], + breakout: [ + [ + "field", + PRODUCTS.VENDOR, + { "base-type": "type/Text", "source-field": ORDERS.PRODUCT_ID }, + ], + ], + limit: 5, + }, + visualization_settings: { + "pie.slice_threshold": 20, + }, + display: "pie", + }, ], - }).then(({ dashboard, questions: [question] }) => { + cards: [{}, { col: 11 }], + }).then(({ dashboard, questions: [lineChartQuestion] }) => { cy.wrap(dashboard.id).as("dashboardId"); - cy.wrap(question.id).as("questionId"); + cy.wrap(lineChartQuestion.id).as("lineChartQuestionId"); }); }); @@ -1082,24 +1154,64 @@ describe("issue 8490", () => { }); cy.findByTestId("embed-frame").within(() => { - // X-axis labels: Jan 2023 - cy.findByText("1ì›” 2023").should("be.visible"); - // PDF export cy.findByText("PDFë¡œ 내보내기").should("be.visible"); - // Powered by cy.findByText("ì œê³µ:").should("be.visible"); - // Aggregation "count" - cy.findByText("카운트").should("be.visible"); + cy.log("assert the line chart"); + getDashboardCard(0).within(() => { + // X-axis labels: Jan 2024 + cy.findByText("1ì›” 2024").should("be.visible"); + // Aggregation "count" + cy.findByText("카운트").should("be.visible"); + // "Other" bar tooltip + otherSeriesChartPaths().first().realHover(); + }); + }); + + assertEChartsTooltip({ + rows: [ + { + name: "Gizmo", + value: "4", + }, + { + name: "Widget", + value: "2", + }, + { + name: "합계", + value: "6", + }, + ], + }); + + cy.findByTestId("embed-frame").within(() => { + cy.log("assert the trend chart"); + getDashboardCard(2).within(() => { + // N/A + cy.findByText("해당 ì—†ìŒ").should("be.visible"); + // (No data) + cy.findByText("(ë°ì´í„° ì—†ìŒ)").should("be.visible"); + }); + + cy.log("assert the pie chart"); + getDashboardCard(1).within(() => { + // Total + cy.findByText("합계").should("be.visible"); + // Other + cy.findByTestId("chart-legend").findByText("기타").should("be.visible"); + }); }); cy.log("test a static embedded question"); - cy.get("@questionId").then(questionId => { + + cy.log("assert the line chart"); + cy.get("@lineChartQuestionId").then(lineChartQuestionId => { visitEmbeddedPage( { - resource: { question: questionId }, + resource: { question: lineChartQuestionId }, params: {}, }, { @@ -1112,11 +1224,9 @@ describe("issue 8490", () => { cy.findByTestId("embed-frame").within(() => { // X-axis labels: Jan 2023 - cy.findByText("4ì›” 2022").should("be.visible"); - + cy.findByText("11ì›” 2023").should("be.visible"); // Powered by cy.findByText("ì œê³µ:").should("be.visible"); - // Aggregation "count" cy.findByText("카운트").should("be.visible"); }); diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/model/other-series.ts b/frontend/src/metabase/visualizations/echarts/cartesian/model/other-series.ts index 063c5068a6f4ba440abafffea046a02c7c25354d..7500c145161c7dd65652191d3f5c6a491c745901 100644 --- a/frontend/src/metabase/visualizations/echarts/cartesian/model/other-series.ts +++ b/frontend/src/metabase/visualizations/echarts/cartesian/model/other-series.ts @@ -100,55 +100,75 @@ const AGGREGATION_FN_MAP: Record< AggregationType, { fn: (values: number[]) => number; label: string } > = { - count: { - label: t`Total`, - fn: sum, + get count() { + return { + label: t`Total`, + fn: sum, + }; }, - sum: { - label: t`Total`, - fn: sum, + get sum() { + return { + label: t`Total`, + fn: sum, + }; }, - "cum-sum": { - label: t`Total`, - fn: sum, + get "cum-sum"() { + return { + label: t`Total`, + fn: sum, + }; }, - "cum-count": { - label: t`Total`, - fn: sum, + get "cum-count"() { + return { + label: t`Total`, + fn: sum, + }; }, - avg: { - label: t`Average`, - fn: values => sum(values) / values.length, + get avg() { + return { + label: t`Average`, + fn: (values: number[]) => sum(values) / values.length, + }; }, - distinct: { - label: t`Distinct values`, - fn: values => new Set(values).size, + get distinct() { + return { + label: t`Distinct values`, + fn: (values: number[]) => new Set(values).size, + }; }, - min: { - label: t`Min`, - fn: values => Math.min(...values), + get min() { + return { + label: t`Min`, + fn: (values: number[]) => Math.min(...values), + }; }, - max: { - label: t`Max`, - fn: values => Math.max(...values), + get max() { + return { + label: t`Max`, + fn: (values: number[]) => Math.max(...values), + }; }, - median: { - label: t`Median`, - fn: values => { - const sortedValues = values.sort((a, b) => a - b); - const middleIndex = Math.floor(sortedValues.length / 2); - return sortedValues.length % 2 - ? sortedValues[middleIndex] - : (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2; - }, + get median() { + return { + label: t`Median`, + fn: (values: number[]) => { + const sortedValues = values.sort((a, b) => a - b); + const middleIndex = Math.floor(sortedValues.length / 2); + return sortedValues.length % 2 + ? sortedValues[middleIndex] + : (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2; + }, + }; }, - stddev: { - label: t`Standard deviation`, - fn: values => { - const mean = sum(values) / values.length; - const squaredDifferences = values.map(v => (v - mean) ** 2); - const variance = sum(squaredDifferences) / values.length; - return Math.sqrt(variance); - }, + get stddev() { + return { + label: t`Standard deviation`, + fn: (values: number[]) => { + const mean = sum(values) / values.length; + const squaredDifferences = values.map(v => (v - mean) ** 2); + const variance = sum(squaredDifferences) / values.length; + return Math.sqrt(variance); + }, + }; }, }; diff --git a/frontend/src/metabase/visualizations/echarts/pie/constants.ts b/frontend/src/metabase/visualizations/echarts/pie/constants.ts index 63438f08244ea65e5a42360851b4c98e35a2dabe..cf5f190ad03ddc5da555a444964e3a908a6e0445 100644 --- a/frontend/src/metabase/visualizations/echarts/pie/constants.ts +++ b/frontend/src/metabase/visualizations/echarts/pie/constants.ts @@ -36,8 +36,8 @@ export const OTHER_SLICE_MIN_PERCENTAGE = 0.005; export const OTHER_SLICE_KEY = `${NULL_CHAR}___OTHER___`; -export const OTHER_SLICE_NAME = t`Other`; +export const getOtherSliceName = () => t`Other`; -export const TOTAL_TEXT = t`Total`.toUpperCase(); +export const getTotalText = () => t`Total`.toUpperCase(); export const OPTION_NAME_SEPERATOR = `–${NULL_CHAR}–`; diff --git a/frontend/src/metabase/visualizations/echarts/pie/model/index.ts b/frontend/src/metabase/visualizations/echarts/pie/model/index.ts index ef2cf2ed26b17d57b97958d8b2c1752a290b5b2e..0635427e80eb28c69e4e1ab1e78b66471f5e13ca 100644 --- a/frontend/src/metabase/visualizations/echarts/pie/model/index.ts +++ b/frontend/src/metabase/visualizations/echarts/pie/model/index.ts @@ -24,7 +24,7 @@ import type { ShowWarning } from "../../types"; import { OTHER_SLICE_KEY, OTHER_SLICE_MIN_PERCENTAGE, - OTHER_SLICE_NAME, + getOtherSliceName, } from "../constants"; import { getDimensionFormatter } from "../format"; import { getArrayFromMapValues } from "../util"; @@ -188,7 +188,7 @@ function aggregateChildrenSlices( node.children.set(OTHER_SLICE_KEY, { key: OTHER_SLICE_KEY, - name: OTHER_SLICE_NAME, + name: getOtherSliceName(), value: otherTotal, displayValue: otherTotal, normalizedPercentage: otherTotal / node.value, @@ -471,7 +471,7 @@ export function getPieChartModel( sliceTree.set(OTHER_SLICE_KEY, { key: OTHER_SLICE_KEY, - name: OTHER_SLICE_NAME, + name: getOtherSliceName(), value: otherTotal, displayValue: otherTotal, normalizedPercentage: visible ? otherTotal / total : 0, @@ -509,7 +509,7 @@ export function getPieChartModel( if (sliceTree.size === 0) { sliceTree.set(OTHER_SLICE_KEY, { key: OTHER_SLICE_KEY, - name: OTHER_SLICE_NAME, + name: getOtherSliceName(), value: 1, displayValue: 0, normalizedPercentage: 0, diff --git a/frontend/src/metabase/visualizations/echarts/pie/option.ts b/frontend/src/metabase/visualizations/echarts/pie/option.ts index f0c1c999db66929fcc01570cc6544a994dcbfa41..58b55ec31af68197db154c9a4786145b62ddaf88 100644 --- a/frontend/src/metabase/visualizations/echarts/pie/option.ts +++ b/frontend/src/metabase/visualizations/echarts/pie/option.ts @@ -9,7 +9,7 @@ import type { RenderingContext, } from "metabase/visualizations/types"; -import { DIMENSIONS, OPTION_NAME_SEPERATOR, TOTAL_TEXT } from "./constants"; +import { DIMENSIONS, OPTION_NAME_SEPERATOR, getTotalText } from "./constants"; import type { PieChartFormatters } from "./format"; import type { PieChartModel, SliceTreeNode } from "./model/types"; import { getArrayFromMapValues, getSliceTreeNodesFromPath } from "./util"; @@ -42,7 +42,7 @@ function getTotalGraphicOption( let labelText = ""; const defaultLabelWillOverflow = - renderingContext.measureText(TOTAL_TEXT, fontStyle) >= innerRadius * 2; + renderingContext.measureText(getTotalText(), fontStyle) >= innerRadius * 2; if (settings["pie.show_total"] && !defaultLabelWillOverflow) { let sliceValueOrTotal = 0; @@ -65,7 +65,7 @@ function getTotalGraphicOption( labelText = slice.name.toUpperCase(); } else { sliceValueOrTotal = chartModel.total; - labelText = TOTAL_TEXT; + labelText = getTotalText(); } const valueWillOverflow = diff --git a/frontend/src/metabase/visualizations/visualizations/SmartScalar/compute.js b/frontend/src/metabase/visualizations/visualizations/SmartScalar/compute.js index 8aed42f7394b6573ffda4be377add898285ea6c8..9fa95b5c4526d79ceb3055f2cb3b4b1635288563 100644 --- a/frontend/src/metabase/visualizations/visualizations/SmartScalar/compute.js +++ b/frontend/src/metabase/visualizations/visualizations/SmartScalar/compute.js @@ -526,18 +526,24 @@ function formatDateStr({ date, dateUnitSettings, options }) { } export const CHANGE_TYPE_OPTIONS = { - MISSING: { - CHANGE_TYPE: "PREVIOUS_VALUE_MISSING", - PERCENT_CHANGE_STR: t`N/A`, - COMPARISON_VALUE_STR: t`(No data)`, + get MISSING() { + return { + CHANGE_TYPE: "PREVIOUS_VALUE_MISSING", + PERCENT_CHANGE_STR: t`N/A`, + COMPARISON_VALUE_STR: t`(No data)`, + }; }, - SAME: { - CHANGE_TYPE: "PREVIOUS_VALUE_SAME", - PERCENT_CHANGE_STR: t`No change`, - COMPARISON_VALUE_STR: "", + get SAME() { + return { + CHANGE_TYPE: "PREVIOUS_VALUE_SAME", + PERCENT_CHANGE_STR: t`No change`, + COMPARISON_VALUE_STR: "", + }; }, - CHANGED: { - CHANGE_TYPE: "PREVIOUS_VALUE_CHANGED", + get CHANGED() { + return { + CHANGE_TYPE: "PREVIOUS_VALUE_CHANGED", + }; }, };