From 1fe63c52bfed1aacb70923bac9f60850709871c2 Mon Sep 17 00:00:00 2001 From: Uladzimir Havenchyk <125459446+uladzimirdev@users.noreply.github.com> Date: Mon, 13 May 2024 17:35:14 +0300 Subject: [PATCH] Partially fix columns and viz settings mismatch (#42344) From now we allow mismatch between `base-type` if we need to match columns and columnSettings --- .../scenarios/question/settings.cy.spec.js | 17 +- .../42049-wrong-columns-order.cy.spec.js | 86 ++++++++++ .../metabase-lib/v1/queries/utils/dataset.ts | 21 ++- .../v1/queries/utils/dataset.unit.spec.ts | 160 ++++++++++++++++++ .../v1/queries/utils/get-column-key.ts | 3 +- frontend/src/metabase-lib/v1/references.ts | 9 +- .../src/metabase-lib/viz/column_settings.ts | 1 + 7 files changed, 281 insertions(+), 16 deletions(-) create mode 100644 e2e/test/scenarios/visualizations-tabular/reproductions/42049-wrong-columns-order.cy.spec.js create mode 100644 frontend/src/metabase-lib/v1/queries/utils/dataset.unit.spec.ts diff --git a/e2e/test/scenarios/question/settings.cy.spec.js b/e2e/test/scenarios/question/settings.cy.spec.js index 3874128342f..004d2b0665b 100644 --- a/e2e/test/scenarios/question/settings.cy.spec.js +++ b/e2e/test/scenarios/question/settings.cy.spec.js @@ -138,8 +138,21 @@ describe("scenarios > question > settings", () => { "source-table": PRODUCTS_ID, condition: [ "=", - ["field-id", ORDERS.PRODUCT_ID], - ["joined-field", "Products", ["field-id", PRODUCTS.ID]], + [ + "field", + ORDERS.PRODUCT_ID, + { + "base-type": "type/Integer", + }, + ], + [ + "field", + PRODUCTS.ID, + { + "base-type": "type/BigInteger", + "join-alias": "Products", + }, + ], ], alias: "Products", }, diff --git a/e2e/test/scenarios/visualizations-tabular/reproductions/42049-wrong-columns-order.cy.spec.js b/e2e/test/scenarios/visualizations-tabular/reproductions/42049-wrong-columns-order.cy.spec.js new file mode 100644 index 00000000000..d79e2bfeded --- /dev/null +++ b/e2e/test/scenarios/visualizations-tabular/reproductions/42049-wrong-columns-order.cy.spec.js @@ -0,0 +1,86 @@ +import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database"; +import { restore, createQuestion } from "e2e/support/helpers"; +const { ORDERS, ORDERS_ID } = SAMPLE_DATABASE; + +// unskip once metabase#42049 is addressed +describe.skip("issue 42049", () => { + beforeEach(() => { + restore(); + cy.signInAsAdmin(); + }); + + it("should not mess up columns order (metabase#42049)", () => { + cy.intercept("POST", "/api/card/*/query", req => { + req.on("response", res => { + const createdAt = res.body.data.cols[1]; + + createdAt.field_ref[1] = "created_at"; // simulate named field ref + + res.send(); + }); + }).as("cardQuery"); + + createQuestion( + { + query: { + "source-table": ORDERS_ID, + fields: [ + ["field", ORDERS.ID, { "base-type": "type/BigInteger" }], + ["field", ORDERS.CREATED_AT, { "base-type": "type/DateTime" }], + ["field", ORDERS.QUANTITY, { "base-type": "type/Integer" }], + ], + }, + visualization_settings: { + "table.columns": [ + { + name: "ID", + fieldRef: ["field", ORDERS.ID, null], + enabled: true, + }, + { + name: "CREATED_AT", + fieldRef: [ + "field", + ORDERS.CREATED_AT, + { + "temporal-unit": "default", + }, + ], + enabled: true, + }, + { + name: "QUANTITY", + fieldRef: ["field", ORDERS.QUANTITY, null], + enabled: true, + }, + ], + }, + }, + { visitQuestion: true }, + ); + + cy.log("verify initial columns order"); + + cy.findAllByTestId("header-cell").as("headerCells"); + cy.get("@headerCells").eq(0).should("have.text", "ID"); + cy.get("@headerCells").eq(1).should("have.text", "Created At"); + cy.get("@headerCells").eq(2).should("have.text", "Quantity"); + + cy.findByRole("button", { name: "Filter" }).click(); + + cy.findByRole("dialog").within(() => { + cy.findByRole("button", { name: "Last month" }).click(); + cy.findByRole("button", { name: "Apply filters" }).click(); + }); + + cy.wait("@cardQuery"); + cy.get("@cardQuery.all").should("have.length", 2); + + cy.log("verify columns order after applying the filter"); + + cy.findAllByTestId("header-cell").as("headerCells"); + cy.get("@headerCells").eq(0).should("have.text", "ID"); + cy.get("@headerCells").eq(1).should("have.text", "Created At"); + cy.get("@headerCells").eq(2).should("have.text", "Quantity"); + }); +}); diff --git a/frontend/src/metabase-lib/v1/queries/utils/dataset.ts b/frontend/src/metabase-lib/v1/queries/utils/dataset.ts index 09b2378f911..41356713df9 100644 --- a/frontend/src/metabase-lib/v1/queries/utils/dataset.ts +++ b/frontend/src/metabase-lib/v1/queries/utils/dataset.ts @@ -9,11 +9,14 @@ import type { export const datasetContainsNoResults = (data: DatasetData) => data.rows == null || data.rows.length === 0; -export function getColumnSettingKey({ - key, - name, - fieldRef, -}: TableColumnOrderSetting) { +export function getColumnSettingKey( + { key, name, fieldRef }: TableColumnOrderSetting, + ignoreBaseType = false, +) { + if (ignoreBaseType) { + return getColumnKey({ name, field_ref: normalize(fieldRef) }, true); + } + return key ?? getColumnKey({ name, field_ref: normalize(fieldRef) }); } @@ -22,11 +25,11 @@ export function findColumnIndexesForColumnSettings( columnSettings: TableColumnOrderSetting[], ) { const columnIndexByKey = new Map( - columns.map((column, index) => [getColumnKey(column), index]), + columns.map((column, index) => [getColumnKey(column, true), index]), ); return columnSettings.map( columnSetting => - columnIndexByKey.get(getColumnSettingKey(columnSetting)) ?? -1, + columnIndexByKey.get(getColumnSettingKey(columnSetting, true)) ?? -1, ); } @@ -36,11 +39,11 @@ export function findColumnSettingIndexesForColumns( ) { const columnSettingIndexByKey = new Map( columnSettings.map((columnSetting, index) => [ - getColumnSettingKey(columnSetting), + getColumnSettingKey(columnSetting, true), index, ]), ); return columns.map( - column => columnSettingIndexByKey.get(getColumnKey(column)) ?? -1, + column => columnSettingIndexByKey.get(getColumnKey(column, true)) ?? -1, ); } diff --git a/frontend/src/metabase-lib/v1/queries/utils/dataset.unit.spec.ts b/frontend/src/metabase-lib/v1/queries/utils/dataset.unit.spec.ts new file mode 100644 index 00000000000..4a10f540ef0 --- /dev/null +++ b/frontend/src/metabase-lib/v1/queries/utils/dataset.unit.spec.ts @@ -0,0 +1,160 @@ +import { + createMockColumn, + createMockTableColumnOrderSetting, +} from "metabase-types/api/mocks"; +import { ORDERS } from "metabase-types/api/mocks/presets"; + +import { + findColumnIndexesForColumnSettings, + findColumnSettingIndexesForColumns, +} from "./dataset"; + +describe("dataset utils", () => { + describe("findColumnIndexesForColumnSettings", () => { + it("finds columnIndex for ColumnSettings without base-type", () => { + const column = createMockColumn({ + id: ORDERS.TOTAL, + name: "TOTAL", + display_name: "Total", + field_ref: [ + "field", + ORDERS.TOTAL, + { + "base-type": "type/Number", + }, + ], + }); + const columnSetting = createMockTableColumnOrderSetting({ + name: "TOTAL", + key: `["ref",["field",${ORDERS.TOTAL},null]]`, + fieldRef: ["field", ORDERS.TOTAL, null], + enabled: true, + }); + + expect( + findColumnIndexesForColumnSettings([column], [columnSetting]), + ).toEqual([0]); + }); + + it("finds columnIndex for ColumnSettings with base-type", () => { + const column = createMockColumn({ + id: ORDERS.TOTAL, + name: "TOTAL", + display_name: "Total", + field_ref: [ + "field", + ORDERS.TOTAL, + { + "base-type": "type/Number", + }, + ], + }); + const columnSetting = createMockTableColumnOrderSetting({ + name: "TOTAL", + key: `["ref",["field",${ORDERS.TOTAL},{"base-type":"type/Number"}]]`, + fieldRef: ["field", ORDERS.TOTAL, { "base-type": "type/Number" }], + enabled: true, + }); + + expect( + findColumnIndexesForColumnSettings([column], [columnSetting]), + ).toEqual([0]); + }); + + it("finds findColumnIndexesForColumnSettings for Columns with different base-type", () => { + const column = createMockColumn({ + id: ORDERS.TOTAL, + name: "TOTAL", + display_name: "Total", + field_ref: [ + "field", + ORDERS.TOTAL, + { + "base-type": "type/Text", + }, + ], + }); + const columnSetting = createMockTableColumnOrderSetting({ + name: "TOTAL", + key: `["ref",["field",${ORDERS.TOTAL},{"base-type":"type/Number"}]]`, + fieldRef: ["field", ORDERS.TOTAL, { "base-type": "type/Number" }], + enabled: true, + }); + + expect( + findColumnIndexesForColumnSettings([column], [columnSetting]), + ).toEqual([0]); + }); + }); + + describe("findColumnSettingIndexesForColumns", () => { + it("finds columnSettingsIndex for Column without base-type", () => { + const column = createMockColumn({ + id: ORDERS.TOTAL, + name: "TOTAL", + display_name: "Total", + field_ref: ["field", ORDERS.TOTAL, null], + }); + const columnSetting = createMockTableColumnOrderSetting({ + name: "TOTAL", + key: `["ref",["field",${ORDERS.TOTAL},{"base-type":"type/Number"}]]`, + fieldRef: ["field", ORDERS.TOTAL, { "base-type": "type/Number" }], + enabled: true, + }); + + expect( + findColumnSettingIndexesForColumns([column], [columnSetting]), + ).toEqual([0]); + }); + + it("finds columnSettingsIndex for Columns with base-type", () => { + const column = createMockColumn({ + id: ORDERS.TOTAL, + name: "TOTAL", + display_name: "Total", + field_ref: [ + "field", + ORDERS.TOTAL, + { + "base-type": "type/Number", + }, + ], + }); + const columnSetting = createMockTableColumnOrderSetting({ + name: "TOTAL", + key: `["ref",["field",${ORDERS.TOTAL},{"base-type":"type/Number"}]]`, + fieldRef: ["field", ORDERS.TOTAL, { "base-type": "type/Number" }], + enabled: true, + }); + + expect( + findColumnSettingIndexesForColumns([column], [columnSetting]), + ).toEqual([0]); + }); + + it("finds columnSettingsIndex for Columns with different base-type", () => { + const column = createMockColumn({ + id: ORDERS.TOTAL, + name: "TOTAL", + display_name: "Total", + field_ref: [ + "field", + ORDERS.TOTAL, + { + "base-type": "type/Text", + }, + ], + }); + const columnSetting = createMockTableColumnOrderSetting({ + name: "TOTAL", + key: `["ref",["field",${ORDERS.TOTAL},{"base-type":"type/Number"}]]`, + fieldRef: ["field", ORDERS.TOTAL, { "base-type": "type/Number" }], + enabled: true, + }); + + expect( + findColumnSettingIndexesForColumns([column], [columnSetting]), + ).toEqual([0]); + }); + }); +}); diff --git a/frontend/src/metabase-lib/v1/queries/utils/get-column-key.ts b/frontend/src/metabase-lib/v1/queries/utils/get-column-key.ts index 34c34e53d75..d875143e3b3 100644 --- a/frontend/src/metabase-lib/v1/queries/utils/get-column-key.ts +++ b/frontend/src/metabase-lib/v1/queries/utils/get-column-key.ts @@ -12,6 +12,7 @@ import type { DatasetColumn } from "metabase-types/api"; export const getColumnKey = ( column: Pick<DatasetColumn, "name" | "field_ref">, + ignoreBaseType = false, ) => { let fieldRef = column.field_ref; @@ -31,7 +32,7 @@ export const getColumnKey = ( isExpressionReference(fieldRef) || isAggregationReference(fieldRef) ) { - fieldRef = getBaseDimensionReference(fieldRef); + fieldRef = getBaseDimensionReference(fieldRef, ignoreBaseType); } const isLegacyRef = diff --git a/frontend/src/metabase-lib/v1/references.ts b/frontend/src/metabase-lib/v1/references.ts index 2c392b7f487..87cb56f176b 100644 --- a/frontend/src/metabase-lib/v1/references.ts +++ b/frontend/src/metabase-lib/v1/references.ts @@ -111,11 +111,12 @@ export const BASE_DIMENSION_REFERENCE_OMIT_OPTIONS = [ export const getBaseDimensionReference = ( mbql: DimensionReferenceWithOptions, + ignoreBaseType: boolean, ) => - getDimensionReferenceWithoutOptions( - mbql, - BASE_DIMENSION_REFERENCE_OMIT_OPTIONS, - ); + getDimensionReferenceWithoutOptions(mbql, [ + ...BASE_DIMENSION_REFERENCE_OMIT_OPTIONS, + ...(ignoreBaseType ? ["base-type"] : []), + ]); /** * Whether this Field clause has a string Field name (as opposed to an integer Field ID). This generally means the diff --git a/frontend/src/metabase-lib/viz/column_settings.ts b/frontend/src/metabase-lib/viz/column_settings.ts index ed7ff0b8908..66ae5da594f 100644 --- a/frontend/src/metabase-lib/viz/column_settings.ts +++ b/frontend/src/metabase-lib/viz/column_settings.ts @@ -87,6 +87,7 @@ function syncTableColumnSettings( fieldRef: col.field_ref, enabled: true, })); + return { ...settings, "table.columns": [...existingColumnSettings, ...addedColumnSettings], -- GitLab