Skip to content
Snippets Groups Projects
Unverified Commit d327c451 authored by Aleksandr Lesnenko's avatar Aleksandr Lesnenko Committed by GitHub
Browse files

untie `getColumnKey` function from code not usable in static viz rendering environment (#25925)

* untie getColumnKey function from unimportable in static viz environment code

* review

* spec
parent 0bfcae26
No related branches found
No related tags found
No related merge requests found
Showing
with 529 additions and 129 deletions
......@@ -34,6 +34,15 @@ import Aggregation from "metabase-lib/lib/queries/structured/Aggregation";
import Filter from "metabase-lib/lib/queries/structured/Filter";
import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
import NativeQuery from "metabase-lib/lib/queries/NativeQuery";
import {
isFieldReference,
isExpressionReference,
isAggregationReference,
isTemplateTagReference,
normalizeReferenceOptions,
getBaseDimensionReference,
BASE_DIMENSION_REFERENCE_OMIT_OPTIONS,
} from "metabase-lib/lib/references";
/**
* A dimension option returned by the query_metadata API
......@@ -87,23 +96,6 @@ export default class Dimension {
this._options = options;
}
/**
* Canonically the field clause should use `null` instead of empty options. Keys with null values should get removed.
*/
static normalizeOptions(options: any): any {
if (!options) {
return null;
}
// recursively normalize maps inside options.
options = _.mapObject(options, val =>
typeof val === "object" ? this.normalizeOptions(val) : val,
);
// remove null/undefined options from map.
options = _.omit(options, value => value == null);
return _.isEmpty(options) ? null : options;
}
/**
* Parses an MBQL expression into an appropriate Dimension subclass, if possible.
* Metadata should be provided if you intend to use the display name or render methods.
......@@ -219,7 +211,7 @@ export default class Dimension {
*/
_dimensionForOption(option: DimensionOption) {
// fill in the parent field ref
const fieldRef = this.baseDimension().mbql();
const fieldRef = getBaseDimensionReference(this.mbql());
let mbql = option.mbql;
if (mbql) {
......@@ -565,13 +557,6 @@ export default class Dimension {
return this.withoutOptions("temporal-unit");
}
/**
* Return a copy of this Dimension with any binning options removed.
*/
withoutBinning(): Dimension {
return this.withoutOptions("binning");
}
withoutJoinAlias(): Dimension {
return this.withoutOptions("join-alias");
}
......@@ -580,7 +565,7 @@ export default class Dimension {
* Return a copy of this Dimension with any temporal bucketing or binning options removed.
*/
baseDimension(): Dimension {
return this.withoutTemporalBucketing().withoutBinning();
return this.withoutOptions(...BASE_DIMENSION_REFERENCE_OMIT_OPTIONS);
}
isValidFKRemappingTarget() {
......@@ -675,21 +660,12 @@ export default class Dimension {
*/
export class FieldDimension extends Dimension {
/**
* Whether `clause` is an array, and a valid `:field` clause
*/
static isFieldClause(clause): boolean {
return (
Array.isArray(clause) && clause.length === 3 && clause[0] === "field"
);
}
static parseMBQL(
mbql,
metadata = null,
query = null,
): FieldDimension | null | undefined {
if (FieldDimension.isFieldClause(mbql)) {
if (isFieldReference(mbql)) {
return Object.freeze(
new FieldDimension(mbql[1], mbql[2], metadata, query),
);
......@@ -742,7 +718,7 @@ export class FieldDimension extends Dimension {
[fieldIdOrName, options],
metadata,
query,
Object.freeze(Dimension.normalizeOptions(options)),
Object.freeze(normalizeReferenceOptions(options)),
);
this._fieldIdOrName = fieldIdOrName;
......@@ -764,7 +740,7 @@ export class FieldDimension extends Dimension {
}
// this should be considered equivalent to an equivalent MBQL clause
if (FieldDimension.isFieldClause(somethingElse)) {
if (isFieldReference(somethingElse)) {
const dimension = FieldDimension.parseMBQL(
somethingElse,
this._metadata,
......@@ -1142,21 +1118,12 @@ const isFieldDimension = dimension => dimension instanceof FieldDimension;
export class ExpressionDimension extends Dimension {
_expressionName: ExpressionName;
/**
* Whether `clause` is an array, and a valid `:expression` clause
*/
static isExpressionClause(clause): boolean {
return (
Array.isArray(clause) && clause.length >= 2 && clause[0] === "expression"
);
}
static parseMBQL(
mbql: any,
metadata?: Metadata | null | undefined,
query?: StructuredQuery | null | undefined,
): Dimension | null | undefined {
if (ExpressionDimension.isExpressionClause(mbql)) {
if (isExpressionReference(mbql)) {
const [expressionName, options] = mbql.slice(1);
return new ExpressionDimension(expressionName, options, metadata, query);
}
......@@ -1174,7 +1141,7 @@ export class ExpressionDimension extends Dimension {
[expressionName, options],
metadata,
query,
Object.freeze(Dimension.normalizeOptions(options)),
Object.freeze(normalizeReferenceOptions(options)),
);
this._expressionName = expressionName;
......@@ -1195,7 +1162,7 @@ export class ExpressionDimension extends Dimension {
);
}
if (ExpressionDimension.isExpressionClause(somethingElse)) {
if (isExpressionReference(somethingElse)) {
const dimension = ExpressionDimension.parseMBQL(
somethingElse,
this._metadata,
......@@ -1411,7 +1378,7 @@ export class AggregationDimension extends Dimension {
metadata?: Metadata | null | undefined,
query?: StructuredQuery | null | undefined,
): Dimension | null | undefined {
if (Array.isArray(mbql) && mbql[0] === "aggregation") {
if (isAggregationReference(mbql)) {
const [aggregationIndex, options] = mbql.slice(1);
return new AggregationDimension(
aggregationIndex,
......@@ -1434,7 +1401,7 @@ export class AggregationDimension extends Dimension {
[aggregationIndex, options],
metadata,
query,
Object.freeze(Dimension.normalizeOptions(options)),
Object.freeze(normalizeReferenceOptions(options)),
);
this._aggregationIndex = aggregationIndex;
......@@ -1542,15 +1509,11 @@ export class TemplateTagDimension extends FieldDimension {
metadata: Metadata = null,
query: NativeQuery = null,
): FieldDimension | null | undefined {
return TemplateTagDimension.isTemplateTagClause(mbql)
return isTemplateTagReference(mbql)
? Object.freeze(new TemplateTagDimension(mbql[1], metadata, query))
: null;
}
static isTemplateTagClause(clause) {
return Array.isArray(clause) && clause[0] === "template-tag";
}
validateTemplateTag(): ValidationError | null {
const tag = this.tag();
if (!tag) {
......
......@@ -22,8 +22,6 @@ import {
AggregationDimension,
FieldDimension,
} from "metabase-lib/lib/Dimension";
import Mode from "metabase-lib/lib/Mode";
import { isStandard } from "metabase-lib/lib/queries/utils/filter";
import { isFK } from "metabase-lib/lib/types/utils/isa";
import { memoizeClass, sortObject } from "metabase-lib/lib/utils";
......@@ -81,6 +79,7 @@ import {
ALERT_TYPE_ROWS,
ALERT_TYPE_TIMESERIES_GOAL,
} from "metabase-lib/lib/Alert";
import { getBaseDimensionReference } from "metabase-lib/lib/references";
export type QuestionCreatorOpts = {
databaseId?: DatabaseId;
......@@ -713,7 +712,7 @@ class QuestionInner {
const dimension = query.columnDimensionWithName(name);
return {
name: name,
field_ref: dimension.baseDimension().mbql(),
field_ref: getBaseDimensionReference(dimension.mbql()),
enabled: true,
};
}),
......
......@@ -6,15 +6,13 @@ import {
EXCLUDE_OPTIONS,
EXCLUDE_UNITS,
} from "metabase-lib/lib/queries/utils/query-time";
import Dimension, {
FieldDimension,
TemplateTagDimension,
} from "metabase-lib/lib/Dimension";
import Dimension, { FieldDimension } from "metabase-lib/lib/Dimension";
import { isDimensionTarget } from "metabase-lib/lib/parameters/utils/targets";
import {
getParameterSubType,
isDateParameter,
} from "metabase-lib/lib/parameters/utils/parameter-type";
import { isTemplateTagReference } from "metabase-lib/lib/references";
import { getParameterOperatorName } from "metabase-lib/lib/parameters/utils/operators";
import { hasParameterValue } from "metabase-lib/lib/parameters/utils/parameter-values";
......@@ -168,8 +166,7 @@ export function isFieldFilterParameterConveratableToMBQL(parameter) {
const hasValue = hasParameterValue(value);
const hasWellFormedTarget = Array.isArray(target?.[1]);
const hasFieldDimensionTarget =
isDimensionTarget(target) &&
!TemplateTagDimension.isTemplateTagClause(target[1]);
isDimensionTarget(target) && !isTemplateTagReference(target[1]);
return hasValue && hasWellFormedTarget && hasFieldDimensionTarget;
}
......
......@@ -25,6 +25,7 @@ import Variable from "metabase-lib/lib/variables/Variable";
import TemplateTagVariable from "metabase-lib/lib/variables/TemplateTagVariable";
import { createTemplateTag } from "metabase-lib/lib/queries/TemplateTag";
import ValidationError from "metabase-lib/lib/ValidationError";
import { isFieldReference } from "metabase-lib/lib/references";
import Dimension, { FieldDimension, TemplateTagDimension } from "../Dimension";
import DimensionOptions from "../DimensionOptions";
......@@ -561,9 +562,7 @@ export default class NativeQuery extends AtomicQuery {
const templateTags = this.templateTags();
return templateTags
.filter(
tag =>
tag.type === "dimension" &&
FieldDimension.isFieldClause(tag.dimension),
tag => tag.type === "dimension" && isFieldReference(tag.dimension),
)
.map(tag => {
const dimension = FieldDimension.parseMBQL(
......
import _ from "underscore";
import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
import Dimension, {
AggregationDimension,
FieldDimension,
} from "metabase-lib/lib/Dimension";
import Dimension, { FieldDimension } from "metabase-lib/lib/Dimension";
export const datasetContainsNoResults = data =>
data.rows == null || data.rows.length === 0;
......@@ -37,36 +34,6 @@ export function fieldRefWithOption(fieldRef, key, value) {
return dimension && dimension.withOption(key, value).mbql();
}
export const keyForColumn = column => {
let fieldRef = column.field_ref;
if (!fieldRef) {
console.error("column is missing field_ref", column);
fieldRef = new FieldDimension(column.name).mbql();
}
let dimension = Dimension.parseMBQL(fieldRef);
if (!dimension) {
console.warn("Unknown field_ref", fieldRef);
return JSON.stringify(fieldRef);
}
dimension = dimension.baseDimension();
// match bug where field w/ join alias returned field w/o join alias instead
if (dimension instanceof FieldDimension) {
dimension = dimension.withoutOptions("join-alias");
}
// match legacy behavior which didn't have "field-literal" or "aggregation" field refs
const isLegacyRef =
(dimension instanceof FieldDimension && dimension.isStringFieldName()) ||
dimension instanceof AggregationDimension;
return JSON.stringify(
isLegacyRef ? ["name", column.name] : ["ref", dimension.mbql()],
);
};
/**
* finds the column object from the dataset results for the given `table.columns` column setting
* @param {Column[]} columns Dataset results columns
......
import { DatasetColumn } from "metabase-types/api";
import {
createFieldReference,
getBaseDimensionReference,
getDimensionReferenceWithoutOptions,
getNormalizedDimensionReference,
hasStringFieldName,
isAggregationReference,
isExpressionReference,
isFieldReference,
isValidDimensionReference,
} from "metabase-lib/lib/references";
export const getColumnKey = (
column: Pick<DatasetColumn, "name" | "field_ref">,
) => {
let fieldRef = column.field_ref;
if (!fieldRef) {
console.error("column is missing field_ref", column);
fieldRef = createFieldReference(column.name);
}
if (!isValidDimensionReference(fieldRef)) {
console.warn("Unknown field_ref", fieldRef);
return JSON.stringify(fieldRef);
}
fieldRef = getNormalizedDimensionReference(fieldRef);
if (
isFieldReference(fieldRef) ||
isExpressionReference(fieldRef) ||
isAggregationReference(fieldRef)
) {
fieldRef = getBaseDimensionReference(fieldRef);
}
if (isFieldReference(fieldRef)) {
fieldRef = getDimensionReferenceWithoutOptions(fieldRef, ["join-alias"]);
}
const isLegacyRef =
(isFieldReference(fieldRef) && hasStringFieldName(fieldRef)) ||
isAggregationReference(fieldRef);
return JSON.stringify(
isLegacyRef ? ["name", column.name] : ["ref", fieldRef],
);
};
import { getColumnKey } from "metabase-lib/lib/queries/utils/get-column-key";
describe("getColumnKey", () => {
// NOTE: run legacy tests with and without a field_ref. without is disabled in latest since it now always uses
// field_ref, leaving test code in place to compare against older versions
for (const fieldRefEnabled of [/*false,*/ true]) {
describe(fieldRefEnabled ? "with field_ref" : "without field_ref", () => {
it("should return [ref [field ...]] for field", () => {
expect(
getColumnKey({
name: "foo",
id: 1,
field_ref: fieldRefEnabled ? ["field", 1, null] : undefined,
}),
).toEqual(JSON.stringify(["ref", ["field", 1, null]]));
});
it("should return [ref [field ...]] for foreign field", () => {
expect(
getColumnKey({
name: "foo",
id: 1,
fk_field_id: 2,
field_ref: fieldRefEnabled
? ["field", 1, { "source-field": 2 }]
: undefined,
}),
).toEqual(JSON.stringify(["ref", ["field", 1, { "source-field": 2 }]]));
});
it("should return [ref [expression ...]] for expression", () => {
expect(
getColumnKey({
name: "foo",
expression_name: "foo",
field_ref: fieldRefEnabled ? ["expression", "foo"] : undefined,
}),
).toEqual(JSON.stringify(["ref", ["expression", "foo", null]]));
});
it("should return [name ...] for aggregation", () => {
const col = {
name: "foo",
source: "aggregation",
field_ref: fieldRefEnabled ? ["aggregation", 0] : undefined,
};
expect(getColumnKey(col, [col])).toEqual(
// NOTE: not ideal, matches existing behavior, but should be ["aggregation", 0]
JSON.stringify(["name", "foo"]),
);
});
it("should return [name ...] for aggregation", () => {
const col = {
name: "foo",
id: ["field", "foo", { "base-type": "type/Integer" }],
field_ref: fieldRefEnabled
? ["field", "foo", { "base-type": "type/Integer" }]
: undefined,
};
expect(getColumnKey(col, [col])).toEqual(
// NOTE: not ideal, matches existing behavior, but should be ["field", "foo", {"base-type": "type/Integer"}]
JSON.stringify(["name", "foo"]),
);
});
it("should return [field ...] for native query column", () => {
expect(
getColumnKey({
name: "foo",
field_ref: fieldRefEnabled
? ["field", "foo", { "base-type": "type/Integer" }]
: undefined,
}),
).toEqual(
// NOTE: not ideal, matches existing behavior, but should be ["field", "foo", {"base-type": "type/Integer"}]
JSON.stringify(["name", "foo"]),
);
});
});
}
describe("with field_ref", () => {
it("should return [ref [field ...]] for joined field", () => {
const col = {
name: "foo",
id: 1,
field_ref: ["field", 1, { "join-alias": "x" }],
};
expect(getColumnKey(col)).toEqual(
// NOTE: not ideal, matches existing behavior, but should be ["field", 1, {"join-alias": "x"}]
JSON.stringify(["ref", ["field", 1, null]]),
);
});
});
});
import _ from "underscore";
import {
AggregationReference,
ColumnName,
DimensionReference,
DimensionReferenceWithOptions,
ExpressionReference,
FieldId,
FieldReference,
ReferenceOptions,
TemplateTagReference,
} from "metabase-types/api/query";
export const isFieldReference = (mbql: any): mbql is FieldReference => {
return Array.isArray(mbql) && mbql.length === 3 && mbql[0] === "field";
};
export const isExpressionReference = (
mbql: any,
): mbql is ExpressionReference => {
return Array.isArray(mbql) && mbql.length >= 2 && mbql[0] === "expression";
};
export const isAggregationReference = (
mbql: any,
): mbql is AggregationReference => {
return Array.isArray(mbql) && mbql[0] === "aggregation";
};
export const isTemplateTagReference = (
mbql: any,
): mbql is TemplateTagReference => {
return Array.isArray(mbql) && mbql[0] === "template-tag";
};
export const createFieldReference = (
columnNameOrFieldId: ColumnName | FieldId,
): FieldReference => ["field", columnNameOrFieldId, null];
export const isValidDimensionReference = (
mbql: any,
): mbql is DimensionReference => {
return [
isFieldReference,
isExpressionReference,
isAggregationReference,
isTemplateTagReference,
].some(predicate => predicate(mbql));
};
export const normalizeReferenceOptions = (
options?: ReferenceOptions | null,
): ReferenceOptions | null => {
if (!options) {
return null;
}
// recursively normalize maps inside options.
options = _.mapObject(options, val =>
typeof val === "object" ? normalizeReferenceOptions(val) : val,
);
// remove null/undefined options from map.
options = _.omit(options, value => value == null);
return _.isEmpty(options) ? null : options;
};
export const getNormalizedDimensionReference = (
mbql: DimensionReference,
): DimensionReference => {
if (
isFieldReference(mbql) ||
isExpressionReference(mbql) ||
isAggregationReference(mbql)
) {
const normalizedReference = [...mbql] as DimensionReference;
const normalizedOptions = normalizeReferenceOptions(mbql[2]);
normalizedReference[2] = normalizedOptions;
return normalizedReference;
}
return mbql;
};
export const getDimensionReferenceWithoutOptions = (
mbql: DimensionReferenceWithOptions,
optionsKeysToOmit: string[],
): DimensionReferenceWithOptions => {
const newReference = mbql.slice() as DimensionReferenceWithOptions;
const options = newReference[2];
if (!options) {
return newReference;
}
newReference[2] =
options == null ? null : _.omit(options, ...optionsKeysToOmit);
if (_.isEmpty(newReference[2])) {
newReference[2] = null;
}
return newReference;
};
export const BASE_DIMENSION_REFERENCE_OMIT_OPTIONS = [
"temporal-unit",
"binning",
];
export const getBaseDimensionReference = (
mbql: DimensionReferenceWithOptions,
) =>
getDimensionReferenceWithoutOptions(
mbql,
BASE_DIMENSION_REFERENCE_OMIT_OPTIONS,
);
/**
* Whether this Field clause has a string Field name (as opposed to an integer Field ID). This generally means the
* Field comes from a native query.
*/
export const hasStringFieldName = (mbql: FieldReference) =>
typeof mbql[1] === "string";
import {
isFieldReference,
isExpressionReference,
isAggregationReference,
isTemplateTagReference,
normalizeReferenceOptions,
} from "metabase-lib/lib/references";
describe("reference predicates", () => {
describe("isFieldReference", () => {
it("returns true for valid field references", () => {
const validReferences = [
["field", 123, null],
["field", "column_name", null],
["field", "column_name", {}],
];
validReferences.forEach(ref => expect(isFieldReference(ref)).toBe(true));
});
it("returns false for invalid field references", () => {
const invalidReferences = [
null,
"field",
["field", 123],
["aggregation", 123, null],
["field", "column_name", null, { "source-field": true }],
];
invalidReferences.forEach(ref =>
expect(isFieldReference(ref)).toBe(false),
);
});
});
describe("isAggregationReference", () => {
it("returns true for valid aggregation references", () => {
const validReferences = [
["aggregation", 123, null],
["aggregation", "string", null],
["aggregation", "column_name", { "base-type": "number" }],
];
validReferences.forEach(ref =>
expect(isAggregationReference(ref)).toBe(true),
);
});
it("returns false for invalid aggregation references", () => {
const invalidReferences = [null, 123, "aggregation", ["field", 123]];
invalidReferences.forEach(ref =>
expect(isAggregationReference(ref)).toBe(false),
);
});
});
describe("isExpressionReference", () => {
it("returns true for valid expression references", () => {
const validReferences = [
["expression", "string", null],
["expression", "column_name", { "base-type": "number" }],
];
validReferences.forEach(ref =>
expect(isExpressionReference(ref)).toBe(true),
);
});
it("returns false for invalid expression references", () => {
const invalidReferences = [
null,
123,
"expression",
["expression"],
["field", 123],
];
invalidReferences.forEach(ref =>
expect(isExpressionReference(ref)).toBe(false),
);
});
});
describe("isTemplateTagReference", () => {
it("returns true for valid template tag references", () => {
const validReferences = [["template-tag", "tag name"]];
validReferences.forEach(ref =>
expect(isTemplateTagReference(ref)).toBe(true),
);
});
it("returns false for invalid template tag references", () => {
const invalidReferences = [null, "tag", ["templateTag", 123]];
invalidReferences.forEach(ref =>
expect(isTemplateTagReference(ref)).toBe(false),
);
});
});
});
describe("normalizeReferenceOptions", () => {
it("should remove empty options map", () => {
expect(normalizeReferenceOptions(null)).toEqual(null);
expect(normalizeReferenceOptions({})).toEqual(null);
});
it("should remove null/undefined keys", () => {
expect(
normalizeReferenceOptions({
"temporal-unit": undefined,
binning: { strategy: "default" },
"base-type": undefined,
}),
).toEqual({
binning: { strategy: "default" },
});
});
it("should recursively normalize maps options", () => {
expect(
normalizeReferenceOptions({
binning: { strategy: "default", other: null } as any,
}),
).toStrictEqual({
binning: { strategy: "default" },
});
});
// TODO -- it should also remove empty arrays, but we currently don't have any situations where there might be
// one.
});
import type { DatetimeUnit } from "metabase-types/api/query";
import type {
DatetimeUnit,
DimensionReference,
} from "metabase-types/api/query";
import { DatabaseId } from "./database";
import { DownloadPermission } from "./permissions";
......@@ -6,11 +9,14 @@ export type RowValue = string | number | null | boolean;
export type RowValues = RowValue[];
export interface DatasetColumn {
id?: number;
display_name: string;
source: string;
name: string;
remapped_to_column?: DatasetColumn;
unit?: DatetimeUnit;
field_ref?: DimensionReference;
expression_name?: any;
}
export interface DatasetData {
......
......@@ -40,3 +40,70 @@ export type DatetimeUnit =
| "quarter"
| "quarter-of-year"
| "year";
export interface ReferenceOptions {
binning?: BinningOptions;
"temporal-unit"?: DatetimeUnit;
"join-alias"?: string;
"base-type"?: string;
}
type BinningOptions =
| DefaultBinningOptions
| NumBinsBinningOptions
| BinWidthBinningOptions;
interface DefaultBinningOptions {
strategy: "default";
}
interface NumBinsBinningOptions {
strategy: "num-bins";
"num-bins": number;
}
interface BinWidthBinningOptions {
strategy: "bin-width";
"bin-width": number;
}
export type ReferenceOptionsKeys =
| "source-field"
| "base-type"
| "join-alias"
| "temporal-unit"
| "binning";
export type FieldId = number;
export type ColumnName = string;
export type FieldReference = [
"field",
FieldId | ColumnName,
ReferenceOptions | null,
];
export type ExpressionName = string;
export type ExpressionReference = [
"expression",
ExpressionName,
ReferenceOptions | null,
];
export type AggregationIndex = number;
export type AggregationReference = [
"aggregation",
AggregationIndex,
ReferenceOptions | null,
];
export type TagName = string;
export type TemplateTagReference = ["template-tag", TagName];
export type DimensionReferenceWithOptions =
| FieldReference
| ExpressionReference
| AggregationReference;
export type DimensionReference =
| DimensionReferenceWithOptions
| TemplateTagReference;
......@@ -16,12 +16,12 @@ import type {
CardId,
ClickBehavior,
DatasetData,
DatasetColumn,
} from "metabase-types/api";
import type { Column } from "metabase-types/types/Dataset";
import { isTableDisplay } from "metabase/lib/click-behavior";
import { clickBehaviorIsValid } from "metabase-lib/lib/parameters/utils/click-behavior";
import { keyForColumn } from "metabase-lib/lib/queries/utils/dataset";
import { getColumnKey } from "metabase-lib/lib/queries/utils/get-column-key";
import { getClickBehaviorForColumn } from "./utils";
import ClickBehaviorSidebarContent from "./ClickBehaviorSidebarContent";
import ClickBehaviorSidebarHeader from "./ClickBehaviorSidebarHeader";
......@@ -67,7 +67,9 @@ function ClickBehaviorSidebar({
boolean | null
>(null);
const [selectedColumn, setSelectedColumn] = useState<Column | null>(null);
const [selectedColumn, setSelectedColumn] = useState<DatasetColumn | null>(
null,
);
const [originalVizSettings, setOriginalVizSettings] = useState<
VizSettings | undefined | null
......@@ -105,7 +107,7 @@ function ClickBehaviorSidebar({
click_behavior: nextClickBehavior,
});
} else {
onUpdateDashCardColumnSettings(id, keyForColumn(selectedColumn), {
onUpdateDashCardColumnSettings(id, getColumnKey(selectedColumn), {
click_behavior: nextClickBehavior,
});
}
......
......@@ -11,8 +11,8 @@ import type {
CardId,
ClickBehavior,
DatasetData,
DatasetColumn,
} from "metabase-types/api";
import type { Column } from "metabase-types/types/Dataset";
import { isTableDisplay } from "metabase/lib/click-behavior";
import { getClickBehaviorForColumn } from "./utils";
......@@ -29,7 +29,7 @@ interface Props {
clickBehavior?: ClickBehavior;
isTypeSelectorVisible: boolean | null;
hasSelectedColumn: boolean;
onColumnSelected: (column: Column) => void;
onColumnSelected: (column: DatasetColumn) => void;
onSettingsChange: (clickBehavior?: Partial<ClickBehavior>) => void;
onTypeSelectorVisibilityChange: (isVisible: boolean) => void;
}
......@@ -62,7 +62,7 @@ function ClickBehaviorSidebar({
<TableClickBehaviorView
columns={columns}
dashcard={dashcard}
getClickBehaviorForColumn={(column: Column) =>
getClickBehaviorForColumn={(column: DatasetColumn) =>
getClickBehaviorForColumn(dashcard, column)
}
onColumnClick={onColumnSelected}
......
......@@ -8,8 +8,7 @@ import {
getActionButtonLabel,
} from "metabase/writeback/utils";
import type { DashboardOrderedCard } from "metabase-types/api";
import type { Column } from "metabase-types/types/Dataset";
import type { DashboardOrderedCard, DatasetColumn } from "metabase-types/api";
import { isTableDisplay } from "metabase/lib/click-behavior";
import { Heading, SidebarHeader } from "../ClickBehaviorSidebar.styled";
......@@ -29,7 +28,7 @@ function DefaultHeader({ children }: { children: React.ReactNode }) {
interface Props {
dashcard: DashboardOrderedCard;
selectedColumn?: Column | null;
selectedColumn?: DatasetColumn | null;
onUnsetColumn: () => void;
}
......
......@@ -6,8 +6,8 @@ import type {
DashboardOrderedCard,
ClickBehavior,
ClickBehaviorType,
DatasetColumn,
} from "metabase-types/api";
import type { Column as IColumn } from "metabase-types/types/Dataset";
import { hasActionsMenu } from "metabase/lib/click-behavior";
import Column from "./Column";
......@@ -33,10 +33,12 @@ function explainClickBehaviorType(
}
interface Props {
columns: IColumn[];
columns: DatasetColumn[];
dashcard: DashboardOrderedCard;
getClickBehaviorForColumn: (column: IColumn) => ClickBehavior | undefined;
onColumnClick: (column: IColumn) => void;
getClickBehaviorForColumn: (
column: DatasetColumn,
) => ClickBehavior | undefined;
onColumnClick: (column: DatasetColumn) => void;
}
function TableClickBehaviorView({
......
......@@ -4,10 +4,10 @@ import { getIn } from "icepick";
import type {
ClickBehaviorType,
DashboardOrderedCard,
DatasetColumn,
} from "metabase-types/api";
import type { Column } from "metabase-types/types/Dataset";
import { hasActionsMenu } from "metabase/lib/click-behavior";
import { keyForColumn } from "metabase-lib/lib/queries/utils/dataset";
import { getColumnKey } from "metabase-lib/lib/queries/utils/get-column-key";
type ClickBehaviorOption = {
value: ClickBehaviorType | "menu";
......@@ -42,12 +42,12 @@ export function getClickBehaviorOptionName(
}
export function getClickBehaviorForColumn(
dashcard: DashboardOrderedCard,
column: Column,
column: DatasetColumn,
) {
return getIn(dashcard, [
"visualization_settings",
"column_settings",
keyForColumn(column),
getColumnKey(column),
"click_behavior",
]);
}
......@@ -7,7 +7,7 @@ import { t } from "ttag";
import { getSettingsWidgetsForSeries } from "metabase/visualizations/lib/settings/visualization";
import ChartSettingsWidget from "metabase/visualizations/components/ChartSettingsWidget";
import { updateSettings } from "metabase/visualizations/lib/settings";
import { keyForColumn } from "metabase-lib/lib/queries/utils/dataset";
import { getColumnKey } from "metabase-lib/lib/queries/utils/get-column-key";
export default ({ question, clicked }) => {
if (
......@@ -56,7 +56,7 @@ export default ({ question, clicked }) => {
...columnSettingsWidget,
props: {
...columnSettingsWidget.props,
initialKey: keyForColumn(column),
initialKey: getColumnKey(column),
},
}}
hidden={false}
......
......@@ -13,7 +13,7 @@ import {
isFuzzyOperator,
} from "metabase-lib/lib/operators/utils";
import { isCurrency } from "metabase-lib/lib/types/utils/isa";
import { keyForColumn } from "metabase-lib/lib/queries/utils/dataset";
import { getColumnKey } from "metabase-lib/lib/queries/utils/get-column-key";
import TextPicker from "./TextPicker";
import SelectPicker from "./SelectPicker";
import NumberPicker from "./NumberPicker";
......@@ -66,7 +66,7 @@ export default function DefaultPicker({
const visualizationSettings = filter?.query()?.question()?.settings();
const key = keyForColumn(dimension.column());
const key = getColumnKey(dimension.column());
const columnSettings = visualizationSettings?.column_settings?.[key];
const fieldMetadata = field?.metadata?.fields[field?.id];
......
......@@ -3,6 +3,7 @@ import { ChartColumns } from "metabase/visualizations/lib/graph/columns";
import { getStackOffset } from "metabase/visualizations/lib/settings/stacking";
import { formatNumber, formatPercent } from "metabase/static-viz/lib/numbers";
import { ChartTicksFormatters } from "metabase/visualizations/shared/types/format";
import { getColumnKey } from "metabase-lib/lib/queries/utils/get-column-key";
export const getXValueMetricColumn = (chartColumns: ChartColumns) => {
// For multi-metrics charts we use the first metic column settings for formatting
......@@ -21,7 +22,9 @@ export const getStaticFormatters = (
};
const metricColumnSettings =
settings.column_settings?.[getXValueMetricColumn(chartColumns).column.name];
settings.column_settings?.[
getColumnKey(getXValueMetricColumn(chartColumns).column)
];
const xTickFormatter = (value: any) =>
formatNumber(value, metricColumnSettings);
......
......@@ -2,7 +2,7 @@
import React from "react";
import { t } from "ttag";
import _ from "underscore";
import { keyForColumn } from "metabase-lib/lib/queries/utils/dataset";
import { getColumnKey } from "metabase-lib/lib/queries/utils/get-column-key";
import ChartSettingSelect from "./ChartSettingSelect";
import {
SettingsIcon,
......@@ -25,7 +25,7 @@ const ChartSettingFieldPicker = ({
if (value && showColumnSetting && columns) {
const column = _.findWhere(columns, { name: value });
if (column && columnHasSettings(column)) {
columnKey = keyForColumn(column);
columnKey = getColumnKey(column);
}
}
return (
......
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