diff --git a/frontend/src/metabase-lib/lib/Mode/Mode.ts b/frontend/src/metabase-lib/lib/Mode/Mode.ts index b684dc17bc3f1d9ce632b36fbe7b541afe68815f..a523bbcb1f2261d1944f4538496748e3574e09f3 100644 --- a/frontend/src/metabase-lib/lib/Mode/Mode.ts +++ b/frontend/src/metabase-lib/lib/Mode/Mode.ts @@ -1,4 +1,3 @@ -import { getMode } from "metabase/modes/lib/modes"; import { ClickAction, ClickObject, @@ -15,16 +14,6 @@ export default class Mode { this._queryMode = queryMode; } - static forQuestion(question: Question): Mode | null { - // TODO Atte Keinänen 6/22/17: Move getMode here and refactor it after writing tests - const queryMode = getMode(question); - if (queryMode) { - return new Mode(question, queryMode); - } - - return null; - } - queryMode() { return this._queryMode; } diff --git a/frontend/src/metabase-lib/lib/Mode/index.ts b/frontend/src/metabase-lib/lib/Mode/index.ts index e42afab4d3da45a9432e8595cfbdbb5e4004495c..5584a7e25ba6536567fa8278bbfdac670874bdc5 100644 --- a/frontend/src/metabase-lib/lib/Mode/index.ts +++ b/frontend/src/metabase-lib/lib/Mode/index.ts @@ -1,2 +1,2 @@ export { default } from "./Mode"; -export { getMode } from "./utils"; +export { getModeType } from "./utils"; diff --git a/frontend/src/metabase-lib/lib/Mode/utils.ts b/frontend/src/metabase-lib/lib/Mode/utils.ts index 974e2f7e47203010f57d5d72cc5d3595a64c8691..30a7336e2888d9cf6404511e2f48733eedf47ddd 100644 --- a/frontend/src/metabase-lib/lib/Mode/utils.ts +++ b/frontend/src/metabase-lib/lib/Mode/utils.ts @@ -13,7 +13,7 @@ import { } from "metabase-lib/lib/Mode/constants"; import { ModeType } from "./types"; -export function getMode(question: Question): ModeType | null { +export function getModeType(question: Question): ModeType | null { if (!question) { return null; } diff --git a/frontend/src/metabase-lib/lib/Question.ts b/frontend/src/metabase-lib/lib/Question.ts index 21df1eb0bad1700d8b90f74a9c562f06f945641e..66b14b0dc953b2bdd50764f26c3c4f34bc00433c 100644 --- a/frontend/src/metabase-lib/lib/Question.ts +++ b/frontend/src/metabase-lib/lib/Question.ts @@ -820,32 +820,6 @@ class QuestionInner { } } - mode(): Mode | null | undefined { - return Mode.forQuestion(this); - } - - /** - * Returns true if, based on filters and table columns, the expected result is a single row. - * However, it might not be true when a PK column is not unique, leading to multiple rows. - * Because of that, always check query results in addition to this property. - */ - isObjectDetail(): boolean { - const mode = this.mode(); - return mode ? mode.name() === "object" : false; - } - - objectDetailPK(): any { - const query = this.query(); - - if (this.isObjectDetail() && query instanceof StructuredQuery) { - const filters = query.filters(); - - if (filters[0] && isStandard(filters[0])) { - return filters[0][2]; - } - } - } - /** * A user-defined name for the question */ @@ -1329,10 +1303,9 @@ class QuestionInner { } } -export default class Question extends memoizeClass<QuestionInner>( - "query", - "mode", -)(QuestionInner) { +export default class Question extends memoizeClass<QuestionInner>("query")( + QuestionInner, +) { /** * TODO Atte Keinänen 6/13/17: Discussed with Tom that we could use the default Question constructor instead, * but it would require changing the constructor signature so that `card` is an optional parameter and has a default value diff --git a/frontend/src/metabase/modes/lib/modes.ts b/frontend/src/metabase/modes/lib/modes.ts index 8da65f966bc1e22fef77b06e9d9c3890c549e4fb..7e51b535682a5041d02bf71e3d0291fb2ee60bac 100644 --- a/frontend/src/metabase/modes/lib/modes.ts +++ b/frontend/src/metabase/modes/lib/modes.ts @@ -1,6 +1,6 @@ import { QueryMode } from "metabase-types/types/Visualization"; import Question from "metabase-lib/lib/Question"; -import { getMode as getModeFromLib } from "metabase-lib/lib/Mode"; +import Mode, { getModeType } from "metabase-lib/lib/Mode"; import { MODE_TYPE_ACTION, MODE_TYPE_NATIVE, @@ -19,8 +19,13 @@ import PivotMode from "../components/modes/PivotMode"; import NativeMode from "../components/modes/NativeMode"; import DefaultMode from "../components/modes/DefaultMode"; -export function getMode(question: Question): QueryMode | any | null { - const mode = getModeFromLib(question); +export function getMode(question: Question): Mode | null { + const queryMode = getQueryMode(question); + return queryMode ? new Mode(question, queryMode) : null; +} + +export function getQueryMode(question: Question): QueryMode | any | null { + const mode = getModeType(question); if (!mode) { return null; } diff --git a/frontend/src/metabase/query_builder/components/view/QuestionDataSource.jsx b/frontend/src/metabase/query_builder/components/view/QuestionDataSource.jsx index 6ae9508af4de09bc4cefd962ef5eaa01c0ea6f86..123e40ec13ebb1a962b1c94f2e914e21eeaa223a 100644 --- a/frontend/src/metabase/query_builder/components/view/QuestionDataSource.jsx +++ b/frontend/src/metabase/query_builder/components/view/QuestionDataSource.jsx @@ -202,12 +202,6 @@ function getDataSourceParts({ question, subHead, isObjectDetail }) { ); } - if (isObjectDetail) { - parts.push({ - name: question.objectDetailPK(), - }); - } - return parts.filter( part => React.isValidElement(part) || part.name || part.icon, ); diff --git a/frontend/src/metabase/query_builder/selectors.js b/frontend/src/metabase/query_builder/selectors.js index 576046db6016b62a64288f3329484d5559bb2bde..fd7191ad598b3f6cedaa14b06495254b2bc731b6 100644 --- a/frontend/src/metabase/query_builder/selectors.js +++ b/frontend/src/metabase/query_builder/selectors.js @@ -24,6 +24,7 @@ import { getMetadata } from "metabase/selectors/metadata"; import { getAlerts } from "metabase/alert/selectors"; import { getEmbedOptions, getIsEmbedded } from "metabase/selectors/embed"; import { parseTimestamp } from "metabase/lib/time"; +import { getMode as getQuestionMode } from "metabase/modes/lib/modes"; import { getSortedTimelines } from "metabase/lib/timelines"; import { getSetting } from "metabase/selectors/settings"; import { @@ -559,7 +560,9 @@ const isZoomingRow = createSelector( export const getMode = createSelector( [getLastRunQuestion, isZoomingRow], (question, isZoomingRow) => - isZoomingRow ? new Mode(question, ObjectMode) : question && question.mode(), + isZoomingRow + ? new Mode(question, ObjectMode) + : question && getQuestionMode(question), ); export const getIsObjectDetail = createSelector( diff --git a/frontend/src/metabase/visualizations/components/Visualization.jsx b/frontend/src/metabase/visualizations/components/Visualization.jsx index c7fee3f237de1b21f7a3000ac56033916f33c6a5..5e82673bb2e0123960fa211f86078f4189829001 100644 --- a/frontend/src/metabase/visualizations/components/Visualization.jsx +++ b/frontend/src/metabase/visualizations/components/Visualization.jsx @@ -25,6 +25,7 @@ import { isSameSeries } from "metabase/visualizations/lib/utils"; import { performDefaultAction } from "metabase/visualizations/lib/action"; import { getFont } from "metabase/styled-components/selectors"; +import { getMode } from "metabase/modes/lib/modes"; import Utils from "metabase/lib/utils"; import { @@ -222,7 +223,9 @@ class Visualization extends React.PureComponent { return new Mode(question, maybeModeOrQueryMode); } - return question?.mode(); + if (question) { + return getMode(question); + } } getClickActions(clicked) { diff --git a/frontend/test/metabase-lib/lib/Mode.unit.spec.js b/frontend/test/metabase-lib/lib/Mode.unit.spec.js index 6401f5ec6951979495635d4034f1857f7f00b403..47f4a40412f7d27c319a34aa281f0118f24aaad6 100644 --- a/frontend/test/metabase-lib/lib/Mode.unit.spec.js +++ b/frontend/test/metabase-lib/lib/Mode.unit.spec.js @@ -6,6 +6,7 @@ import { PEOPLE, } from "__support__/sample_database_fixture"; +import { getMode } from "metabase/modes/lib/modes"; import Question from "metabase-lib/lib/Question"; describe("Mode", () => { @@ -17,25 +18,27 @@ describe("Mode", () => { // testbed for generative testing? see http://leebyron.com/testcheck-js it("returns `segment` mode with raw data", () => { - const mode = rawDataQuery.question().mode(); + const question = rawDataQuery.question(); + const mode = getMode(question); expect(mode && mode.name()).toEqual("segment"); }); it("returns `metric` mode with >= 1 aggregations", () => { - const mode = rawDataQuery.aggregate(["count"]).question().mode(); + const question = rawDataQuery.aggregate(["count"]).question(); + const mode = getMode(question); expect(mode && mode.name()).toEqual("metric"); }); it("returns `timeseries` mode with >=1 aggregations and date breakout", () => { - const mode = rawDataQuery + const question = rawDataQuery .aggregate(["count"]) .breakout(["field", ORDERS.CREATED_AT.id, { "temporal-unit": "day" }]) - .question() - .mode(); + .question(); + const mode = getMode(question); expect(mode && mode.name()).toEqual("timeseries"); }); it("returns `timeseries` mode with >=1 aggregations and date + category breakout", () => { - const mode = rawDataQuery + const question = rawDataQuery .aggregate(["count"]) .breakout(["field", ORDERS.CREATED_AT.id, { "temporal-unit": "day" }]) .breakout([ @@ -43,26 +46,26 @@ describe("Mode", () => { PRODUCTS.CATEGORY.id, { "source-field": ORDERS.PRODUCT_ID.id }, ]) - .question() - .mode(); + .question(); + const mode = getMode(question); expect(mode && mode.name()).toEqual("timeseries"); }); it("returns `geo` mode with >=1 aggregations and an address breakout", () => { - const mode = rawDataQuery + const question = rawDataQuery .aggregate(["count"]) .breakout([ "field", PEOPLE.STATE.id, { "source-field": ORDERS.USER_ID.id }, ]) - .question() - .mode(); + .question(); + const mode = getMode(question); expect(mode && mode.name()).toEqual("geo"); }); it("returns `pivot` mode with >=1 aggregations and 1-2 category breakouts", () => { - const mode = rawDataQuery + const question = rawDataQuery .aggregate(["count"]) .breakout([ "field", @@ -74,21 +77,21 @@ describe("Mode", () => { PEOPLE.STATE.id, { "source-field": ORDERS.USER_ID.id }, ]) - .question() - .mode(); + .question(); + const mode = getMode(question); expect(mode && mode.name()).toEqual("pivot"); }); it("returns `segment` mode with pk filter", () => { - const mode = rawDataQuery + const question = rawDataQuery .filter(["=", ["field", ORDERS.ID.id, null], 42]) - .question() - .mode(); + .question(); + const mode = getMode(question); expect(mode && mode.name()).toEqual("segment"); }); it("returns `default` mode with >=0 aggregations and >=3 breakouts", () => { - const mode = rawDataQuery + const question = rawDataQuery .aggregate(["count"]) .breakout(["field", ORDERS.CREATED_AT.id, { "temporal-unit": "day" }]) .breakout([ @@ -101,8 +104,8 @@ describe("Mode", () => { PEOPLE.STATE.id, { "source-field": ORDERS.USER_ID.id }, ]) - .question() - .mode(); + .question(); + const mode = getMode(question); expect(mode && mode.name()).toEqual("default"); }); it("returns `default` mode with >=1 aggregations and >=1 breakouts when first neither date or category", () => {}); @@ -126,7 +129,7 @@ describe("Mode", () => { // this is action-specific so just rudimentary tests here showing that the actionsForClick logic works // Action-specific tests would optimally be in their respective test files describe("for a question with an aggregation and a time breakout", () => { - const timeBreakoutQuestionMode = Question.create({ + const question = Question.create({ databaseId: SAMPLE_DATABASE.id, tableId: ORDERS.id, metadata, @@ -135,16 +138,12 @@ describe("Mode", () => { .aggregate(["count"]) .breakout(["field", 1, { "temporal-unit": "day" }]) .question() - .setDisplay("table") - .mode(); + .setDisplay("table"); + const mode = getMode(question); it("has pivot as mode actions 1 and 2", () => { - expect(timeBreakoutQuestionMode.actionsForClick()[0].name).toBe( - "pivot-by-category", - ); - expect(timeBreakoutQuestionMode.actionsForClick()[1].name).toBe( - "pivot-by-location", - ); + expect(mode.actionsForClick()[0].name).toBe("pivot-by-category"); + expect(mode.actionsForClick()[1].name).toBe("pivot-by-location"); }); }); }); diff --git a/frontend/test/metabase-lib/lib/Question.unit.spec.js b/frontend/test/metabase-lib/lib/Question.unit.spec.js index 9e2647602129fbbc183f675eb8c7a22d982004f7..6f044564b6ed6b60ca10e7446f33d03583bf0437 100644 --- a/frontend/test/metabase-lib/lib/Question.unit.spec.js +++ b/frontend/test/metabase-lib/lib/Question.unit.spec.js @@ -171,9 +171,6 @@ describe("Question", () => { it("has correct display settings", () => { expect(question.display()).toBe("table"); }); - it("has correct mode", () => { - expect(question.mode().name()).toBe("segment"); - }); }); describe("Question.create(...)", () => { @@ -381,31 +378,6 @@ describe("Question", () => { // At the same time, the choice that which actions are visible depend on the question's properties // as actions are filtered using those describe("METHODS FOR DRILL-THROUGH / ACTION WIDGET", () => { - const rawDataQuestion = new Question(orders_raw_card, metadata); - const timeBreakoutQuestion = Question.create({ - databaseId: SAMPLE_DATABASE.id, - tableId: ORDERS.id, - metadata, - }) - .query() - .aggregate(["count"]) - .breakout(["field", 1, { "temporal-unit": "day" }]) - .question() - .setDisplay("table"); - - describe("mode()", () => { - describe("for a new question with Orders table and Raw data aggregation", () => { - it("returns the correct mode", () => { - expect(rawDataQuestion.mode().name()).toBe("segment"); - }); - }); - describe("for a question with an aggregation and a time breakout", () => { - it("returns the correct mode", () => { - expect(timeBreakoutQuestion.mode().name()).toBe("timeseries"); - }); - }); - }); - describe("aggregate(...)", () => { const question = new Question(orders_raw_card, metadata); it("returns the correct query for a summarization of a raw data table", () => { diff --git a/frontend/test/metabase/modes/lib/modes.unit.spec.js b/frontend/test/metabase/modes/lib/modes.unit.spec.js index 681dc2ce81306f14dc46634dd5d2672a83344885..e91ad1d52735fa32edc84a8861ffbd9b61e0a8cb 100644 --- a/frontend/test/metabase/modes/lib/modes.unit.spec.js +++ b/frontend/test/metabase/modes/lib/modes.unit.spec.js @@ -1,21 +1,21 @@ -import { getMode } from "metabase/modes/lib/modes"; +import { getQueryMode } from "metabase/modes/lib/modes"; import SegmentMode from "metabase/modes/components/modes/SegmentMode"; import { ORDERS } from "__support__/sample_database_fixture"; describe("modes", () => { - describe("getMode", () => { + describe("getQueryMode", () => { it("should be in segment mode when selecting one PK ID", () => { const filter = ["=", ["field", ORDERS.ID.id, null], 42]; const query = ORDERS.query().filter(filter); const question = ORDERS.question().setQuery(query); - expect(getMode(question)).toBe(SegmentMode); + expect(getQueryMode(question)).toBe(SegmentMode); }); it("should be in segment mode when selecting multiple PK IDs", () => { const filter = ["=", ["field", ORDERS.ID.id, null], 42, 24]; const query = ORDERS.query().filter(filter); const question = ORDERS.question().setQuery(query); - expect(getMode(question)).toBe(SegmentMode); + expect(getQueryMode(question)).toBe(SegmentMode); }); }); });