diff --git a/frontend/src/metabase-lib/types.ts b/frontend/src/metabase-lib/types.ts index 473224c3fe95544c7d373381e7f9ffc36dd5f0e1..1186395a319a6ead768547fe88bc24acfb75362a 100644 --- a/frontend/src/metabase-lib/types.ts +++ b/frontend/src/metabase-lib/types.ts @@ -190,13 +190,16 @@ export type TextFingerprintDisplayInfo = { percentUrl: number; }; +// We're setting the values here as unknown even though +// the API will return numbers most of the time, because +// sometimes it doesn't! export type NumberFingerprintDisplayInfo = { - avg: number; - max: number; - min: number; - q1: number; - q3: number; - sd: number; + avg: unknown; + max: unknown; + min: unknown; + q1: unknown; + q3: unknown; + sd: unknown; }; export type DateTimeFingerprintDisplayInfo = { diff --git a/frontend/src/metabase/components/MetadataInfo/ColumnFingerprintInfo/NumberFingerprint.tsx b/frontend/src/metabase/components/MetadataInfo/ColumnFingerprintInfo/NumberFingerprint.tsx index dc10f5f08f10576ab4f2f9ecbc3ae839b05d55a7..b4430d696f420387f4819f8c63fc7ce34feae0ca 100644 --- a/frontend/src/metabase/components/MetadataInfo/ColumnFingerprintInfo/NumberFingerprint.tsx +++ b/frontend/src/metabase/components/MetadataInfo/ColumnFingerprintInfo/NumberFingerprint.tsx @@ -48,10 +48,18 @@ export function NumberFingerprint({ * @param num - a number value from the type/Number fingerprint; might not be a number * @returns - a tuple, [isFormattedNumber, formattedNumber] */ -function roundNumber(num: number | null | undefined): [boolean, string] { - if (num == null) { +function roundNumber(num: unknown): [boolean, string] { + if (!isNumber(num)) { return [false, ""]; } - return [true, Number.isInteger(num) ? num.toString() : num.toFixed(2)]; + if (Number.isInteger(num)) { + return [true, num.toString()]; + } + + return [true, num.toFixed(2)]; +} + +function isNumber(num: unknown): num is number { + return typeof num === "number" && Number.isFinite(num) && !Number.isNaN(num); } diff --git a/frontend/src/metabase/components/MetadataInfo/ColumnFingerprintInfo/NumberFingerprint.unit.spec.tsx b/frontend/src/metabase/components/MetadataInfo/ColumnFingerprintInfo/NumberFingerprint.unit.spec.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1ccfc4a95df3e908ad4efe93dd2ab3829a32e2da --- /dev/null +++ b/frontend/src/metabase/components/MetadataInfo/ColumnFingerprintInfo/NumberFingerprint.unit.spec.tsx @@ -0,0 +1,65 @@ +import { render, screen } from "__support__/ui"; +import type * as Lib from "metabase-lib"; + +import { NumberFingerprint } from "./NumberFingerprint"; + +type SetupOpts = { + fingerprintTypeInfo?: { + avg: unknown; + min: unknown; + max: unknown; + }; +}; + +function setup({ fingerprintTypeInfo }: SetupOpts) { + render( + <NumberFingerprint + fingerprintTypeInfo={ + fingerprintTypeInfo as Lib.NumberFingerprintDisplayInfo + } + />, + ); +} + +describe("NumberFingerprint", () => { + it("should render valid numbers and round to two decimal places", () => { + setup({ + fingerprintTypeInfo: { + avg: 123, + min: 456.789, + max: 2e4, + }, + }); + expect(screen.getByText("Average")).toBeInTheDocument(); + expect(screen.getByText("Min")).toBeInTheDocument(); + expect(screen.getByText("Max")).toBeInTheDocument(); + + expect(screen.getByText("123")).toBeInTheDocument(); + expect(screen.getByText("456.79")).toBeInTheDocument(); + expect(screen.getByText("20000")).toBeInTheDocument(); + }); + it("should ignore invalid number values in the info", () => { + setup({ + fingerprintTypeInfo: { + avg: 123, + min: Infinity, + max: NaN, + }, + }); + expect(screen.getByText("Average")).toBeInTheDocument(); + expect(screen.queryByText("Min")).not.toBeInTheDocument(); + expect(screen.queryByText("Max")).not.toBeInTheDocument(); + }); + it("should ignore invalid non-number values in the info", () => { + setup({ + fingerprintTypeInfo: { + avg: 123, + min: "456", + max: null, + }, + }); + expect(screen.getByText("Average")).toBeInTheDocument(); + expect(screen.queryByText("Min")).not.toBeInTheDocument(); + expect(screen.queryByText("Max")).not.toBeInTheDocument(); + }); +});