-
Aleksandr Lesnenko authored
* fix row chart for remapped columns * static viz remapped values
Aleksandr Lesnenko authored* fix row chart for remapped columns * static viz remapped values
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
format.ts 5.95 KiB
import moment from "moment";
import { Moment } from "moment-timezone";
import { NumberLike, StringLike } from "@visx/scale";
import {
DatasetColumn,
RowValue,
VisualizationSettings,
} from "metabase-types/api";
import { formatTime } from "metabase/lib/formatting/time";
import {
formatDateTimeWithUnit,
formatRange,
} from "metabase/lib/formatting/date";
import { formatNumber } from "metabase/lib/formatting/numbers";
import { formatCoordinate } from "metabase/lib/formatting/geography";
import {
ChartTicksFormatters,
ValueFormatter,
} from "metabase/visualizations/shared/types/format";
import { ChartColumns } from "metabase/visualizations/lib/graph/columns";
import { getStackOffset } from "metabase/visualizations/lib/settings/stacking";
import { getLabelsMetricColumn } from "metabase/visualizations/shared/utils/series";
import { RemappingHydratedDatasetColumn } from "metabase/visualizations/shared/types/data";
import {
isCoordinate,
isDate,
isNumber,
isTime,
isBoolean,
} from "metabase-lib/types/utils/isa";
import { rangeForValue } from "metabase-lib/queries/utils/range-for-value";
import { getColumnKey } from "metabase-lib/queries/utils/get-column-key";
const getRemappedValue = (
value: unknown,
column: RemappingHydratedDatasetColumn,
) => {
if (column.remapping instanceof Map && column.remapping.has(value)) {
return column.remapping.get(value);
}
return value;
};
type StaticFormattingOptions = {
column: DatasetColumn;
number_separators?: string;
jsx?: boolean;
number_style?: string;
decimals?: number;
prefix?: string;
suffix?: string;
noRange?: boolean;
};
// Literally simplified copy of frontend/src/metabase/lib/formatting/value.tsx that excludes
// click behavior, any html formatting, any code uses globals or that imports packages which use globals.
// The reason for that is inability to use highly-coupled formatting code from the main app for static viz
// because it crashes while using it in the GraalVM SSR
export const formatStaticValue = (
value: unknown,
options: StaticFormattingOptions,
) => {
const { prefix, suffix, column } = options;
let formattedValue = null;
if (value == null) {
formattedValue = JSON.stringify(null);
} else if (isTime(column)) {
formattedValue = formatTime(value as Moment);
} else if (column?.unit != null) {
formattedValue = formatDateTimeWithUnit(
value as string | number,
column.unit,
options,
);
} else if (
isDate(column) ||
moment.isDate(value) ||
moment.isMoment(value) ||
moment(value as string, ["YYYY-MM-DD'T'HH:mm:ss.SSSZ"], true).isValid()
) {
formattedValue = formatDateTimeWithUnit(
value as string | number,
"minute",
options,
);
} else if (column?.semantic_type && typeof value === "string") {
formattedValue = value;
} else if (typeof value === "number" && isCoordinate(column)) {
const range = rangeForValue(value, column);
formattedValue = formatRange(range ?? [], formatCoordinate, options);
} else if (typeof value === "number" && isNumber(column)) {
const range = rangeForValue(value, column);
if (range && !options.noRange) {
formattedValue = formatRange(range, formatNumber, options);
} else {
formattedValue = formatNumber(value, options);
}
} else if (typeof value === "boolean" && isBoolean(column)) {
formattedValue = JSON.stringify(value);
} else if (typeof value === "object") {
// no extra whitespace for table cells
formattedValue = JSON.stringify(value);
} else {
formattedValue = String(value);
}
if (prefix || suffix) {
return `${prefix || ""}${formattedValue}${suffix || ""}`;
}
return formattedValue;
};
export const getStaticFormatters = (
chartColumns: ChartColumns,
settings: VisualizationSettings,
): ChartTicksFormatters => {
const yTickFormatter = (value: StringLike) => {
const column = chartColumns.dimension.column;
const columnSettings = settings.column_settings?.[getColumnKey(column)];
const valueToFormat = getRemappedValue(value, column);
return String(
formatStaticValue(valueToFormat, {
column,
...columnSettings,
jsx: false,
}),
);
};
const metricColumn = getLabelsMetricColumn(chartColumns);
const percentXTicksFormatter = (percent: NumberLike) => {
const column = metricColumn.column;
const number_separators =
settings.column_settings?.[getColumnKey(column)]?.number_separators;
return String(
formatStaticValue(percent, {
column,
number_separators,
jsx: false,
number_style: "percent",
decimals: 2,
}),
);
};
const xTickFormatter = (value: NumberLike) => {
const column = metricColumn.column;
const columnSettings = settings.column_settings?.[getColumnKey(column)];
const valueToFormat = getRemappedValue(value, column);
return String(
formatStaticValue(valueToFormat, {
column,
...columnSettings,
jsx: false,
}),
);
};
const shouldFormatXTicksAsPercent = getStackOffset(settings) === "expand";
return {
yTickFormatter,
xTickFormatter: shouldFormatXTicksAsPercent
? percentXTicksFormatter
: xTickFormatter,
};
};
export const getLabelsStaticFormatter = (
chartColumns: ChartColumns,
settings: VisualizationSettings,
): ValueFormatter => {
const column = getLabelsMetricColumn(chartColumns).column;
const columnSettings = settings.column_settings?.[getColumnKey(column)];
const labelsFormatter = (value: any) =>
String(
formatStaticValue(value, {
column,
...columnSettings,
jsx: false,
compact: settings["graph.label_value_formatting"] === "compact",
}),
);
return labelsFormatter;
};
export const getColumnValueStaticFormatter = () => {
return (value: RowValue, column: DatasetColumn) => {
const valueToFormat = getRemappedValue(value, column);
return String(formatStaticValue(valueToFormat, { column }));
};
};