Skip to content
Snippets Groups Projects
Unverified Commit 2e9e95c0 authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

Extract action column placement (#39958)

parent 1174c113
No related branches found
No related tags found
No related merge requests found
Showing
with 377 additions and 147 deletions
import _ from "underscore"; import _ from "underscore";
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database"; import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
import { ORDERS_QUESTION_ID } from "e2e/support/cypress_sample_instance_data";
import { import {
enterCustomColumnDetails, enterCustomColumnDetails,
getNotebookStep, getNotebookStep,
...@@ -8,6 +9,7 @@ import { ...@@ -8,6 +9,7 @@ import {
openOrdersTable, openOrdersTable,
popover, popover,
restore, restore,
visitQuestion,
visualize, visualize,
} from "e2e/support/helpers"; } from "e2e/support/helpers";
...@@ -77,6 +79,96 @@ describe("extract action", () => { ...@@ -77,6 +79,96 @@ describe("extract action", () => {
}); });
}); });
describe("should add a new column after the selected column", () => {
it("ad-hoc question", () => {
openOrdersTable();
extractColumnAndCheck({
column: "Created At",
option: "Year",
});
const columnIndex = 7;
checkColumnIndex({
column: "Created At",
columnIndex,
});
checkColumnIndex({
column: "Year",
columnIndex: columnIndex + 1,
});
});
it("saved question without viz settings", () => {
visitQuestion(ORDERS_QUESTION_ID);
extractColumnAndCheck({
column: "Created At",
option: "Year",
});
const columnIndex = 7;
checkColumnIndex({
column: "Created At",
columnIndex,
});
checkColumnIndex({
column: "Year",
columnIndex: columnIndex + 1,
});
});
it("saved question with viz settings", () => {
cy.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 },
);
extractColumnAndCheck({
column: "Created At",
option: "Year",
});
const columnIndex = 1;
checkColumnIndex({
column: "Created At",
columnIndex,
});
checkColumnIndex({
column: "Year",
columnIndex: columnIndex + 1,
});
});
});
it("should add an expression based on a breakout column", () => { it("should add an expression based on a breakout column", () => {
cy.createQuestion(DATE_QUESTION, { visitQuestion: true }); cy.createQuestion(DATE_QUESTION, { visitQuestion: true });
extractColumnAndCheck({ extractColumnAndCheck({
...@@ -151,3 +243,7 @@ function extractColumnAndCheck({ column, option, newColumn = option, value }) { ...@@ -151,3 +243,7 @@ function extractColumnAndCheck({ column, option, newColumn = option, value }) {
cy.findByRole("gridcell", { name: value }).should("be.visible"); cy.findByRole("gridcell", { name: value }).should("be.visible");
} }
} }
function checkColumnIndex({ column, columnIndex }) {
cy.findAllByRole("columnheader").eq(columnIndex).should("have.text", column);
}
...@@ -45,10 +45,6 @@ import { getTemplateTagParametersFromCard } from "metabase-lib/v1/parameters/uti ...@@ -45,10 +45,6 @@ import { getTemplateTagParametersFromCard } from "metabase-lib/v1/parameters/uti
import { fieldFilterParameterToFilter } from "metabase-lib/v1/parameters/utils/mbql"; import { fieldFilterParameterToFilter } from "metabase-lib/v1/parameters/utils/mbql";
import { getQuestionVirtualTableId } from "metabase-lib/v1/metadata/utils/saved-questions"; import { getQuestionVirtualTableId } from "metabase-lib/v1/metadata/utils/saved-questions";
import { isTransientId } from "metabase-lib/v1/queries/utils/card"; import { isTransientId } from "metabase-lib/v1/queries/utils/card";
import {
findColumnIndexesForColumnSettings,
findColumnSettingIndexesForColumns,
} from "metabase-lib/v1/queries/utils/dataset";
import { import {
ALERT_TYPE_PROGRESS_BAR_GOAL, ALERT_TYPE_PROGRESS_BAR_GOAL,
ALERT_TYPE_ROWS, ALERT_TYPE_ROWS,
...@@ -56,7 +52,6 @@ import { ...@@ -56,7 +52,6 @@ import {
} from "metabase-lib/v1/Alert"; } from "metabase-lib/v1/Alert";
import type { Query } from "../types"; import type { Query } from "../types";
import { getColumnKey } from "metabase-lib/v1/queries/utils/get-column-key";
export type QuestionCreatorOpts = { export type QuestionCreatorOpts = {
databaseId?: DatabaseId; databaseId?: DatabaseId;
...@@ -470,110 +465,24 @@ class Question { ...@@ -470,110 +465,24 @@ class Question {
}); });
} }
private _syncGraphMetricSettings( syncColumnsAndSettings(
{ data: { cols = [] } = {} }: Dataset, queryResults?: Dataset,
{ data: { cols: prevCols = [] } = {} }: Dataset, prevQueryResults?: Dataset,
options?: Lib.SettingsSyncOptions,
) { ) {
const graphMetrics = this.setting("graph.metrics"); const settings = this.settings();
if (!graphMetrics) { const newSettings = Lib.syncColumnSettings(
return this; settings,
} queryResults,
prevQueryResults,
const hasNativeColumns = options,
cols.some(column => column.source === "native") ||
prevCols.some(column => column.source === "native");
if (hasNativeColumns) {
return this;
}
const metricColumnNames = new Set(
cols
.filter(column => column.source === "aggregation")
.map(column => column.name),
);
const prevMetricColumnNames = new Set(
prevCols
.filter(column => column.source === "aggregation")
.map(column => column.name),
);
const addedMetricColumnNames = new Set(
[...metricColumnNames].filter(name => !prevMetricColumnNames.has(name)),
);
const removedMetricColumnNames = new Set(
[...prevMetricColumnNames].filter(name => !metricColumnNames.has(name)),
); );
if (addedMetricColumnNames.size > 0 || removedMetricColumnNames.size > 0) { if (newSettings !== settings) {
return this.updateSettings({ return this.setSettings(newSettings);
"graph.metrics": [ } else {
...graphMetrics.filter(name => !removedMetricColumnNames.has(name)),
...addedMetricColumnNames,
],
});
}
return this;
}
_syncTableColumnSettings({ data = {} }: Dataset) {
const columnSettings = this.setting("table.columns") || [];
// "table.columns" receive a value only if there are custom settings
// e.g. some columns are hidden. If it's empty, it means everything is visible
const isUsingDefaultSettings = columnSettings.length === 0;
if (isUsingDefaultSettings) {
return this;
}
// remove columns used for remapping only
const cols = data.cols.filter(col => col.remapped_from == null);
const columnIndexes = findColumnIndexesForColumnSettings(
cols,
columnSettings,
);
const columnSettingIndexes = findColumnSettingIndexesForColumns(
cols,
columnSettings,
);
const addedColumns = cols.filter((col, colIndex) => {
const hasVizSettings = columnSettingIndexes[colIndex] >= 0;
return !hasVizSettings;
});
const existingColumnSettings = columnSettings.filter(
(setting, settingIndex) => columnIndexes[settingIndex] >= 0,
);
const noColumnsRemoved =
existingColumnSettings.length === columnSettings.length;
if (noColumnsRemoved && addedColumns.length === 0) {
return this; return this;
} }
const addedColumnSettings = addedColumns.map(col => ({
name: col.name,
key: getColumnKey(col),
fieldRef: col.field_ref,
enabled: true,
}));
return this.updateSettings({
"table.columns": [...existingColumnSettings, ...addedColumnSettings],
});
}
syncColumnsAndSettings(queryResults?: Dataset, prevQueryResults?: Dataset) {
let question = this;
if (queryResults && !queryResults.error) {
question = question._syncTableColumnSettings(queryResults);
if (prevQueryResults && !prevQueryResults.error) {
question = question._syncGraphMetricSettings(
queryResults,
prevQueryResults,
);
}
}
return question;
} }
/** /**
......
import {
findColumnIndexesForColumnSettings,
findColumnSettingIndexesForColumns,
} from "metabase-lib/v1/queries/utils/dataset";
import { getColumnKey } from "metabase-lib/v1/queries/utils/get-column-key";
import type {
Dataset,
DatasetColumn,
VisualizationSettings,
} from "metabase-types/api";
export type SettingsSyncOptions = {
column: DatasetColumn;
};
export function syncColumnSettings(
settings: VisualizationSettings,
queryResults?: Dataset,
prevQueryResults?: Dataset,
options?: SettingsSyncOptions,
): VisualizationSettings {
let newSettings = settings;
if (queryResults && !queryResults.error) {
newSettings = syncTableColumnSettings(newSettings, queryResults, options);
if (prevQueryResults && !prevQueryResults.error) {
newSettings = syncGraphMetricSettings(
newSettings,
queryResults,
prevQueryResults,
);
if (options) {
newSettings = moveNewTableColumnsAfterColumn(
newSettings,
queryResults,
prevQueryResults,
options,
);
}
}
}
return newSettings;
}
function syncTableColumnSettings(
settings: VisualizationSettings,
{ data }: Dataset,
options?: SettingsSyncOptions,
): VisualizationSettings {
// "table.columns" receive a value only if there are custom settings
// e.g. some columns are hidden. If it's empty, it means everything is visible
const columnSettings = settings["table.columns"] ?? [];
if (columnSettings.length === 0 && !options?.column) {
return settings;
}
// remove columns used for remapping only
const cols = data.cols.filter(col => col.remapped_from == null);
const columnIndexes = findColumnIndexesForColumnSettings(
cols,
columnSettings,
);
const columnSettingIndexes = findColumnSettingIndexesForColumns(
cols,
columnSettings,
);
const addedColumns = cols.filter((col, colIndex) => {
const hasVizSettings = columnSettingIndexes[colIndex] >= 0;
return !hasVizSettings;
});
const existingColumnSettings = columnSettings.filter(
(setting, settingIndex) => columnIndexes[settingIndex] >= 0,
);
const noColumnsRemoved =
existingColumnSettings.length === columnSettings.length;
if (noColumnsRemoved && addedColumns.length === 0) {
return settings;
}
const addedColumnSettings = addedColumns.map(col => ({
name: col.name,
key: getColumnKey(col),
fieldRef: col.field_ref,
enabled: true,
}));
return {
...settings,
"table.columns": [...existingColumnSettings, ...addedColumnSettings],
};
}
function moveNewTableColumnsAfterColumn(
settings: VisualizationSettings,
{ data: { cols } }: Dataset,
{ data: { cols: prevCols } }: Dataset,
{ column }: SettingsSyncOptions,
): VisualizationSettings {
const columnSettings = settings["table.columns"];
if (!column || !columnSettings) {
return settings;
}
const prevColumnNames = new Set(prevCols.map(col => col.name));
const addedColumns = cols.filter(col => !prevColumnNames.has(col.name));
const addedColumnSettingIndexes = findColumnSettingIndexesForColumns(
addedColumns,
columnSettings,
);
const addedColumnSettings = addedColumnSettingIndexes.map(
index => columnSettings[index],
);
const existingColumnSettings = columnSettings.filter(
(_, index) => !addedColumnSettingIndexes.includes(index),
);
const [columnSettingIndex] = findColumnSettingIndexesForColumns(
[column],
existingColumnSettings,
);
if (columnSettingIndex < 0) {
return settings;
}
return {
...settings,
"table.columns": [
...existingColumnSettings.slice(0, columnSettingIndex + 1), // before and including the selected column
...addedColumnSettings,
...existingColumnSettings.slice(columnSettingIndex + 1), // after the selected column
],
};
}
function syncGraphMetricSettings(
settings: VisualizationSettings,
{ data: { cols } }: Dataset,
{ data: { cols: prevCols } }: Dataset,
): VisualizationSettings {
const graphMetrics = settings["graph.metrics"];
if (!graphMetrics) {
return settings;
}
const hasNativeColumns =
cols.some(column => column.source === "native") ||
prevCols.some(column => column.source === "native");
if (hasNativeColumns) {
return settings;
}
const metricColumnNames = new Set(
cols
.filter(column => column.source === "aggregation")
.map(column => column.name),
);
const prevMetricColumnNames = new Set(
prevCols
.filter(column => column.source === "aggregation")
.map(column => column.name),
);
const addedMetricColumnNames = new Set(
[...metricColumnNames].filter(name => !prevMetricColumnNames.has(name)),
);
const removedMetricColumnNames = new Set(
[...prevMetricColumnNames].filter(name => !metricColumnNames.has(name)),
);
if (
addedMetricColumnNames.size === 0 &&
removedMetricColumnNames.size === 0
) {
return settings;
}
return {
...settings,
"graph.metrics": [
...graphMetrics.filter(name => !removedMetricColumnNames.has(name)),
...addedMetricColumnNames,
],
};
}
export * from "./display"; export * from "./display";
export * from "./column_settings";
...@@ -34,6 +34,7 @@ import { ...@@ -34,6 +34,7 @@ import {
isBasedOnExistingQuestion, isBasedOnExistingQuestion,
getParameters, getParameters,
getSubmittableQuestion, getSubmittableQuestion,
getQueryResults,
} from "../../selectors"; } from "../../selectors";
import { updateUrl } from "../navigation"; import { updateUrl } from "../navigation";
import { zoomInRow } from "../object-detail"; import { zoomInRow } from "../object-detail";
...@@ -111,7 +112,10 @@ export const reloadCard = createThunkAction(RELOAD_CARD, () => { ...@@ -111,7 +112,10 @@ export const reloadCard = createThunkAction(RELOAD_CARD, () => {
* - `navigateToNewCardInsideQB` is being called (see below) * - `navigateToNewCardInsideQB` is being called (see below)
*/ */
export const SET_CARD_AND_RUN = "metabase/qb/SET_CARD_AND_RUN"; export const SET_CARD_AND_RUN = "metabase/qb/SET_CARD_AND_RUN";
export const setCardAndRun = (nextCard, shouldUpdateUrl = true) => { export const setCardAndRun = (
nextCard,
{ shouldUpdateUrl = true, prevQueryResults, settingsSyncOptions } = {},
) => {
return async (dispatch, getState) => { return async (dispatch, getState) => {
// clone // clone
const card = copy(nextCard); const card = copy(nextCard);
...@@ -127,7 +131,13 @@ export const setCardAndRun = (nextCard, shouldUpdateUrl = true) => { ...@@ -127,7 +131,13 @@ export const setCardAndRun = (nextCard, shouldUpdateUrl = true) => {
// Update the card and originalCard before running the actual query // Update the card and originalCard before running the actual query
dispatch({ type: SET_CARD_AND_RUN, payload: { card, originalCard } }); dispatch({ type: SET_CARD_AND_RUN, payload: { card, originalCard } });
dispatch(runQuestionQuery({ shouldUpdateUrl })); dispatch(
runQuestionQuery({
shouldUpdateUrl,
prevQueryResults,
settingsSyncOptions,
}),
);
// Load table & database metadata for the current question // Load table & database metadata for the current question
dispatch(loadMetadataForCard(card)); dispatch(loadMetadataForCard(card));
...@@ -147,14 +157,16 @@ export const setCardAndRun = (nextCard, shouldUpdateUrl = true) => { ...@@ -147,14 +157,16 @@ export const setCardAndRun = (nextCard, shouldUpdateUrl = true) => {
export const NAVIGATE_TO_NEW_CARD = "metabase/qb/NAVIGATE_TO_NEW_CARD"; export const NAVIGATE_TO_NEW_CARD = "metabase/qb/NAVIGATE_TO_NEW_CARD";
export const navigateToNewCardInsideQB = createThunkAction( export const navigateToNewCardInsideQB = createThunkAction(
NAVIGATE_TO_NEW_CARD, NAVIGATE_TO_NEW_CARD,
({ nextCard, previousCard, objectId }) => { ({ nextCard, previousCard, objectId, settingsSyncOptions }) => {
return async (dispatch, getState) => { return async (dispatch, getState) => {
if (previousCard === nextCard) { if (previousCard === nextCard) {
// Do not reload questions with breakouts when clicked on a legend item // Do not reload questions with breakouts when clicked on a legend item
} else if (cardIsEquivalent(previousCard, nextCard)) { } else if (cardIsEquivalent(previousCard, nextCard)) {
// This is mainly a fallback for scenarios where a visualization legend is clicked inside QB // This is mainly a fallback for scenarios where a visualization legend is clicked inside QB
dispatch( dispatch(
setCardAndRun(await loadCard(nextCard.id, { dispatch, getState })), setCardAndRun(await loadCard(nextCard.id, { dispatch, getState }), {
settingsSyncOptions,
}),
); );
} else { } else {
const card = getCardAfterVisualizationClick(nextCard, previousCard); const card = getCardAfterVisualizationClick(nextCard, previousCard);
...@@ -163,13 +175,19 @@ export const navigateToNewCardInsideQB = createThunkAction( ...@@ -163,13 +175,19 @@ export const navigateToNewCardInsideQB = createThunkAction(
dispatch(openUrl(url)); dispatch(openUrl(url));
} else { } else {
dispatch(onCloseSidebars()); dispatch(onCloseSidebars());
const prevQueryResults = getQueryResults(getState());
if (!cardQueryIsEquivalent(previousCard, nextCard)) { if (!cardQueryIsEquivalent(previousCard, nextCard)) {
// clear the query result so we don't try to display the new visualization before running the new query // clear the query result so we don't try to display the new visualization before running the new query
dispatch(clearQueryResult()); dispatch(clearQueryResult());
} }
// When the dataset query changes, we should change the type, // When the dataset query changes, we should change the type,
// to start building a new ad-hoc question based on a dataset // to start building a new ad-hoc question based on a dataset
dispatch(setCardAndRun({ ...card, type: "question" })); dispatch(
setCardAndRun(
{ ...card, type: "question" },
{ prevQueryResults, settingsSyncOptions },
),
);
} }
if (objectId !== undefined) { if (objectId !== undefined) {
dispatch(zoomInRow({ objectId })); dispatch(zoomInRow({ objectId }));
......
...@@ -57,8 +57,8 @@ export const popState = createThunkAction( ...@@ -57,8 +57,8 @@ export const popState = createThunkAction(
const card = getCard(getState()); const card = getCard(getState());
if (location.state && location.state.card) { if (location.state && location.state.card) {
if (!equals(card, location.state.card)) { if (!equals(card, location.state.card)) {
const shouldRefreshUrl = location.state.card.type === "model"; const shouldUpdateUrl = location.state.card.type === "model";
await dispatch(setCardAndRun(location.state.card, shouldRefreshUrl)); await dispatch(setCardAndRun(location.state.card, { shouldUpdateUrl }));
await dispatch(setCurrentState(location.state)); await dispatch(setCurrentState(location.state));
await dispatch(resetUIControls()); await dispatch(resetUIControls());
} }
......
...@@ -94,6 +94,8 @@ export const runQuestionQuery = ({ ...@@ -94,6 +94,8 @@ export const runQuestionQuery = ({
shouldUpdateUrl = true, shouldUpdateUrl = true,
ignoreCache = false, ignoreCache = false,
overrideWithQuestion = null, overrideWithQuestion = null,
prevQueryResults = undefined,
settingsSyncOptions = undefined,
} = {}) => { } = {}) => {
return async (dispatch, getState) => { return async (dispatch, getState) => {
dispatch(loadStartUIControls()); dispatch(loadStartUIControls());
...@@ -135,7 +137,12 @@ export const runQuestionQuery = ({ ...@@ -135,7 +137,12 @@ export const runQuestionQuery = ({
duration, duration,
), ),
); );
return dispatch(queryCompleted(question, queryResults)); return dispatch(
queryCompleted(question, queryResults, {
prevQueryResults: prevQueryResults ?? getQueryResults(getState()),
settingsSyncOptions,
}),
);
}) })
.catch(error => dispatch(queryErrored(startTime, error))); .catch(error => dispatch(queryErrored(startTime, error)));
...@@ -168,10 +175,13 @@ export const CLEAR_QUERY_RESULT = "metabase/query_builder/CLEAR_QUERY_RESULT"; ...@@ -168,10 +175,13 @@ export const CLEAR_QUERY_RESULT = "metabase/query_builder/CLEAR_QUERY_RESULT";
export const clearQueryResult = createAction(CLEAR_QUERY_RESULT); export const clearQueryResult = createAction(CLEAR_QUERY_RESULT);
export const QUERY_COMPLETED = "metabase/qb/QUERY_COMPLETED"; export const QUERY_COMPLETED = "metabase/qb/QUERY_COMPLETED";
export const queryCompleted = (question, queryResults) => { export const queryCompleted = (
question,
queryResults,
{ prevQueryResults, settingsSyncOptions } = {},
) => {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const [{ data }] = queryResults; const [{ data }] = queryResults;
const prevQueryResults = getQueryResults(getState());
const [{ data: prevData }] = prevQueryResults ?? [{}]; const [{ data: prevData }] = prevQueryResults ?? [{}];
const originalQuestion = getOriginalQuestionWithParameterValues(getState()); const originalQuestion = getOriginalQuestionWithParameterValues(getState());
const { isEditable } = Lib.queryDisplayInfo(question.query()); const { isEditable } = Lib.queryDisplayInfo(question.query());
...@@ -181,6 +191,7 @@ export const queryCompleted = (question, queryResults) => { ...@@ -181,6 +191,7 @@ export const queryCompleted = (question, queryResults) => {
question = question.syncColumnsAndSettings( question = question.syncColumnsAndSettings(
queryResults[0], queryResults[0],
prevQueryResults?.[0], prevQueryResults?.[0],
settingsSyncOptions,
); );
question = question.maybeResetDisplay( question = question.maybeResetDisplay(
......
...@@ -9,6 +9,7 @@ import type * as Lib from "metabase-lib"; ...@@ -9,6 +9,7 @@ import type * as Lib from "metabase-lib";
export const columnExtractDrill: Drill<Lib.ColumnExtractDrillThruInfo> = ({ export const columnExtractDrill: Drill<Lib.ColumnExtractDrillThruInfo> = ({
drill, drill,
drillInfo, drillInfo,
clicked,
applyDrill, applyDrill,
}) => { }) => {
const DrillPopover = ({ onClick }: ClickActionPopoverProps) => { const DrillPopover = ({ onClick }: ClickActionPopoverProps) => {
...@@ -19,6 +20,7 @@ export const columnExtractDrill: Drill<Lib.ColumnExtractDrillThruInfo> = ({ ...@@ -19,6 +20,7 @@ export const columnExtractDrill: Drill<Lib.ColumnExtractDrillThruInfo> = ({
section: "extract-popover", section: "extract-popover",
buttonType: "horizontal", buttonType: "horizontal",
question: () => applyDrill(drill, extraction.key), question: () => applyDrill(drill, extraction.key),
extra: () => ({ settingsSyncOptions: { column: clicked.column } }),
}), }),
); );
......
...@@ -6,17 +6,17 @@ import { DRILLS } from "./constants"; ...@@ -6,17 +6,17 @@ import { DRILLS } from "./constants";
export function queryDrill( export function queryDrill(
question: Question, question: Question,
clicked?: Lib.ClickObject, clicked: Lib.ClickObject,
): ClickAction[] { ): ClickAction[] {
const query = question.query(); const query = question.query();
const stageIndex = -1; const stageIndex = -1;
const drills = Lib.availableDrillThrus( const drills = Lib.availableDrillThrus(
query, query,
stageIndex, stageIndex,
clicked?.column, clicked.column,
clicked?.value, clicked.value,
clicked?.data, clicked.data,
clicked?.dimensions, clicked.dimensions,
); );
const applyDrill = (drill: Lib.DrillThru, ...args: unknown[]) => { const applyDrill = (drill: Lib.DrillThru, ...args: unknown[]) => {
...@@ -33,7 +33,7 @@ export function queryDrill( ...@@ -33,7 +33,7 @@ export function queryDrill(
stageIndex, stageIndex,
drill, drill,
drillInfo, drillInfo,
isDashboard: clicked?.extraData?.dashboard != null, clicked,
applyDrill, applyDrill,
}); });
}); });
......
...@@ -7,10 +7,11 @@ import type * as Lib from "metabase-lib"; ...@@ -7,10 +7,11 @@ import type * as Lib from "metabase-lib";
export const zoomDrill: Drill<Lib.ZoomDrillThruInfo> = ({ export const zoomDrill: Drill<Lib.ZoomDrillThruInfo> = ({
drill, drill,
drillInfo, drillInfo,
isDashboard, clicked,
applyDrill, applyDrill,
}) => { }) => {
const { objectId, isManyPks } = drillInfo; const { objectId, isManyPks } = drillInfo;
const isDashboard = clicked.extraData?.dashboard != null;
return [ return [
{ {
......
...@@ -25,7 +25,7 @@ export class Mode { ...@@ -25,7 +25,7 @@ export class Mode {
} }
actionsForClick( actionsForClick(
clicked: ClickObject | undefined, clicked: ClickObject,
settings: Record<string, any>, settings: Record<string, any>,
extraData?: Record<string, any>, extraData?: Record<string, any>,
): ClickAction[] { ): ClickAction[] {
......
...@@ -302,13 +302,23 @@ class Visualization extends PureComponent { ...@@ -302,13 +302,23 @@ class Visualization extends PureComponent {
}; };
// Add the underlying card of current series to onChangeCardAndRun if available // Add the underlying card of current series to onChangeCardAndRun if available
handleOnChangeCardAndRun = ({ nextCard, seriesIndex, objectId }) => { handleOnChangeCardAndRun = ({
nextCard,
seriesIndex,
objectId,
settingsSyncOptions,
}) => {
const { series, clicked } = this.state; const { series, clicked } = this.state;
const index = seriesIndex || (clicked && clicked.seriesIndex) || 0; const index = seriesIndex || (clicked && clicked.seriesIndex) || 0;
const previousCard = series && series[index] && series[index].card; const previousCard = series && series[index] && series[index].card;
this.props.onChangeCardAndRun({ nextCard, previousCard, objectId }); this.props.onChangeCardAndRun({
nextCard,
previousCard,
objectId,
settingsSyncOptions,
});
}; };
onRender = ({ yAxisSplit, warnings = [] } = {}) => { onRender = ({ yAxisSplit, warnings = [] } = {}) => {
......
...@@ -149,7 +149,7 @@ export type Drill< ...@@ -149,7 +149,7 @@ export type Drill<
stageIndex: number; stageIndex: number;
drill: Lib.DrillThru; drill: Lib.DrillThru;
drillInfo: T; drillInfo: T;
isDashboard: boolean; clicked: Lib.ClickObject;
applyDrill: (drill: Lib.DrillThru, ...args: any[]) => Question; applyDrill: (drill: Lib.DrillThru, ...args: any[]) => Question;
}) => ClickAction[]; }) => ClickAction[];
......
...@@ -610,7 +610,7 @@ describe("Question", () => { ...@@ -610,7 +610,7 @@ describe("Question", () => {
}); });
}); });
describe("Question.prototype._syncTableColumnSettings", () => { describe("Question.prototype.syncColumnsAndSettings", () => {
let question; let question;
const cols = [ const cols = [
{ {
...@@ -656,39 +656,37 @@ describe("Question", () => { ...@@ -656,39 +656,37 @@ describe("Question", () => {
beforeEach(() => { beforeEach(() => {
question = native_orders_count_question.clone(); question = native_orders_count_question.clone();
question.setting = jest.fn(); question.settings = jest.fn(() => ({}));
question.updateSettings = jest.fn(); question.setSettings = jest.fn();
}); });
describe("when columns have not been defined", () => { describe("when columns have not been defined", () => {
it("should do nothing when given no cols", () => { it("should do nothing when given no cols", () => {
question._syncTableColumnSettings({}); question.syncColumnsAndSettings({});
question._syncTableColumnSettings({ data: { cols: [] } }); question.syncColumnsAndSettings({ data: { cols: [] } });
question._syncTableColumnSettings({ data: { cols } }); question.syncColumnsAndSettings({ data: { cols } });
expect(question.updateSettings).not.toHaveBeenCalled(); expect(question.setSettings).not.toHaveBeenCalled();
}); });
it("should do nothing when given cols", () => { it("should do nothing when given cols", () => {
question._syncTableColumnSettings({ data: { cols } }); question.syncColumnsAndSettings({ data: { cols } });
expect(question.updateSettings).not.toHaveBeenCalled(); expect(question.setSettings).not.toHaveBeenCalled();
}); });
}); });
describe("after vizSetting columns have been defined", () => { describe("after vizSetting columns have been defined", () => {
beforeEach(() => { beforeEach(() => {
question.setting.mockImplementation(property => { question.settings.mockImplementation(() => ({
if (property === "table.columns") { "table.columns": vizSettingCols,
return vizSettingCols; }));
}
});
}); });
// Adding a column with same name is covered as well, as name is generated at FE and it will // Adding a column with same name is covered as well, as name is generated at FE and it will
// be unique (e.g. foo -> foo_2) // be unique (e.g. foo -> foo_2)
it("should handle the addition and removal of columns", () => { it("should handle the addition and removal of columns", () => {
question._syncTableColumnSettings({ question.syncColumnsAndSettings({
data: { data: {
cols: [ cols: [
...cols.slice(1), ...cols.slice(1),
...@@ -709,7 +707,7 @@ describe("Question", () => { ...@@ -709,7 +707,7 @@ describe("Question", () => {
}, },
}); });
expect(question.updateSettings).toHaveBeenCalledWith({ expect(question.setSettings).toHaveBeenCalledWith({
"table.columns": [ "table.columns": [
...vizSettingCols.slice(1), ...vizSettingCols.slice(1),
{ {
...@@ -742,13 +740,13 @@ describe("Question", () => { ...@@ -742,13 +740,13 @@ describe("Question", () => {
name: "foo", name: "foo",
base_type: "type/Float", base_type: "type/Float",
}; };
question._syncTableColumnSettings({ question.syncColumnsAndSettings({
data: { data: {
cols: [updatedColumn, ...cols.slice(1)], cols: [updatedColumn, ...cols.slice(1)],
}, },
}); });
expect(question.updateSettings).toHaveBeenCalledWith({ expect(question.setSettings).toHaveBeenCalledWith({
"table.columns": [ "table.columns": [
...vizSettingCols.slice(1), ...vizSettingCols.slice(1),
{ {
...@@ -768,7 +766,7 @@ describe("Question", () => { ...@@ -768,7 +766,7 @@ describe("Question", () => {
}); });
it("should handle the mutation of a field_ref on an existing column", () => { it("should handle the mutation of a field_ref on an existing column", () => {
question._syncTableColumnSettings({ question.syncColumnsAndSettings({
data: { data: {
cols: [ cols: [
{ {
...@@ -789,7 +787,7 @@ describe("Question", () => { ...@@ -789,7 +787,7 @@ describe("Question", () => {
}, },
}); });
expect(question.updateSettings).toHaveBeenCalledWith({ expect(question.setSettings).toHaveBeenCalledWith({
"table.columns": [ "table.columns": [
...vizSettingCols.slice(1), ...vizSettingCols.slice(1),
{ {
...@@ -803,13 +801,13 @@ describe("Question", () => { ...@@ -803,13 +801,13 @@ describe("Question", () => {
}); });
it("shouldn't update settings if order of columns has changed", () => { it("shouldn't update settings if order of columns has changed", () => {
question._syncTableColumnSettings({ question.syncColumnsAndSettings({
data: { data: {
cols: [cols[1], cols[0]], cols: [cols[1], cols[0]],
}, },
}); });
expect(question.updateSettings).not.toHaveBeenCalled(); expect(question.setSettings).not.toHaveBeenCalled();
}); });
}); });
}); });
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment