From 282d56b4ded3c2e0619f7813f32e2940f52e14eb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 3 Sep 2025 15:41:50 +0000 Subject: [PATCH] Add dimension value formatting for complex data types Co-authored-by: eric.okuma --- .../dimension-table/dimension-table-utils.ts | 4 +- .../dashboards/leaderboard/Leaderboard.svelte | 5 ++- .../leaderboard/leaderboard-utils.ts | 38 ++++++++++++++++++- .../selectors/dimension-table.ts | 1 + 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/web-common/src/features/dashboards/dimension-table/dimension-table-utils.ts b/web-common/src/features/dashboards/dimension-table/dimension-table-utils.ts index 68dcf68adac7..30f48c01ce80 100644 --- a/web-common/src/features/dashboards/dimension-table/dimension-table-utils.ts +++ b/web-common/src/features/dashboards/dimension-table/dimension-table-utils.ts @@ -38,6 +38,7 @@ import { getFiltersForOtherDimensions } from "../selectors"; import type { ExploreState } from "web-common/src/features/dashboards/stores/explore-state"; import type { DimensionTableRow } from "./dimension-table-types"; import type { DimensionTableConfig } from "./DimensionTableConfig"; +import { formatDimensionValue } from "../leaderboard/leaderboard-utils"; /** Returns an updated filter set for a given dimension on search */ export function updateFilterOnSearch( @@ -450,6 +451,7 @@ export function prepareDimensionTableRows( addDeltas: boolean, addPercentOfTotal: boolean, unfilteredTotal: number | { [key: string]: number }, + dimensionDataType?: { code?: string }, ): DimensionTableRow[] { if (!queryRows || !queryRows.length) return []; @@ -475,7 +477,7 @@ export function prepareDimensionTableRows( ]); const rowOut: DimensionTableRow = Object.fromEntries([ - [dimensionColumn, row[dimensionColumn] as string], + [dimensionColumn, formatDimensionValue(row[dimensionColumn], dimensionDataType)], ...rawVals, ...formattedVals, ]); diff --git a/web-common/src/features/dashboards/leaderboard/Leaderboard.svelte b/web-common/src/features/dashboards/leaderboard/Leaderboard.svelte index e61ed0eeac1d..268dd871e56f 100644 --- a/web-common/src/features/dashboards/leaderboard/Leaderboard.svelte +++ b/web-common/src/features/dashboards/leaderboard/Leaderboard.svelte @@ -33,6 +33,7 @@ import { cleanUpComparisonValue, compareLeaderboardValues, + formatDimensionValue, getSort, prepareLeaderboardItemData, } from "./leaderboard-utils"; @@ -209,6 +210,7 @@ slice, $selectedValues?.data ?? [], leaderboardTotals, + dimension?.dataType, )); $: belowTheFoldDataLimit = maxValuesToShow - aboveTheFold.length; @@ -267,8 +269,9 @@ leaderboardMeasureNames, leaderboardTotals, $selectedValues?.data?.findIndex((value) => - compareLeaderboardValues(value, item[dimensionName]), + compareLeaderboardValues(value, formatDimensionValue(item[dimensionName], dimension?.dataType)), ) ?? -1, + dimension?.dataType, ), ); diff --git a/web-common/src/features/dashboards/leaderboard/leaderboard-utils.ts b/web-common/src/features/dashboards/leaderboard/leaderboard-utils.ts index c123df8fbb7f..9b29de2d2d5f 100644 --- a/web-common/src/features/dashboards/leaderboard/leaderboard-utils.ts +++ b/web-common/src/features/dashboards/leaderboard/leaderboard-utils.ts @@ -7,6 +7,7 @@ import { V1MetricsViewComparisonMeasureType as ApiSortType, type V1MetricsViewAggregationResponseDataItem, type V1MetricsViewComparisonValue, + TypeCode, } from "@rilldata/web-common/runtime-client"; import { SortType } from "../proto-state/derived-types"; import { DashboardState_LeaderboardSortType } from "@rilldata/web-common/proto/gen/rill/ui/v1/dashboard_pb"; @@ -66,15 +67,47 @@ export const URI_DIMENSION_SUFFIX = "__rill_uri"; const finiteOrNull = (v: unknown): number | null => Number.isFinite(v) ? (v as number) : null; +/** + * Converts a dimension value to a string, properly handling JSON objects and other complex types + */ +export function formatDimensionValue(value: unknown, dataType?: { code?: string }): string { + if (value === null || value === undefined) { + return ""; + } + + // If it's already a string, return as-is + if (typeof value === "string") { + return value; + } + + // Handle JSON and struct types by stringifying them + if (dataType?.code === TypeCode.CODE_JSON || + dataType?.code === TypeCode.CODE_STRUCT || + dataType?.code === TypeCode.CODE_MAP || + dataType?.code === TypeCode.CODE_ARRAY || + typeof value === "object") { + try { + return JSON.stringify(value); + } catch { + // Fallback to string conversion if JSON.stringify fails + return String(value); + } + } + + // For all other types, convert to string + return String(value); +} + export function cleanUpComparisonValue( v: V1MetricsViewAggregationResponseDataItem, dimensionName: string, measureNames: string[], totals: Record, selectedIndex: number, + dimensionDataType?: { code?: string }, ): LeaderboardItemData { const cleanValue: LeaderboardItemData = { - dimensionValue: v[dimensionName] as string, + dimensionValue: formatDimensionValue(v[dimensionName], dimensionDataType), uri: (v[dimensionName + URI_DIMENSION_SUFFIX] as string | undefined | null) || null, @@ -165,6 +198,7 @@ export function prepareLeaderboardItemData( // The totals of the measures for the current period, // or null if the measure is not valid_percent_of_total totals: Record, + dimensionDataType?: { code?: string }, ) { if (values?.length === 0 || !values) { return { @@ -181,7 +215,7 @@ export function prepareLeaderboardItemData( for (const value of values) { if (aboveTheFold.length === numberAboveTheFold) break; - const dimensionValue = value[dimensionName] as string; + const dimensionValue = formatDimensionValue(value[dimensionName], dimensionDataType); belowTheFoldValues.delete(dimensionValue); diff --git a/web-common/src/features/dashboards/state-managers/selectors/dimension-table.ts b/web-common/src/features/dashboards/state-managers/selectors/dimension-table.ts index 41ec7cc132d8..65a62924259d 100644 --- a/web-common/src/features/dashboards/state-managers/selectors/dimension-table.ts +++ b/web-common/src/features/dashboards/state-managers/selectors/dimension-table.ts @@ -96,6 +96,7 @@ export const prepareDimTableRows = isTimeComparisonActive(dashData), isValidPercentOfTotal(dashData), unfilteredTotal, + dimension?.dataType, ); };