diff --git a/web-common/src/components/BarAndLabel.svelte b/web-common/src/components/BarAndLabel.svelte index cc4a1a82f81..708ec3a33c1 100644 --- a/web-common/src/components/BarAndLabel.svelte +++ b/web-common/src/components/BarAndLabel.svelte @@ -44,7 +44,7 @@ class:pr-2={!compact} class:pr-1={compact} class:pl-1={compact} - class="text-right overflow-x-hidden" + class="text-right overflow-hidden" style="position: relative;" > diff --git a/web-common/src/components/data-types/Base.svelte b/web-common/src/components/data-types/Base.svelte index 32aeb2e1754..2549f2ccecd 100644 --- a/web-common/src/components/data-types/Base.svelte +++ b/web-common/src/components/data-types/Base.svelte @@ -5,7 +5,7 @@ $: color = dark ? "" : "text-gray-900"; - + {#if isNull} no data {:else} diff --git a/web-common/src/features/dashboards/leaderboard/ContextColumnValue.svelte b/web-common/src/features/dashboards/leaderboard/ContextColumnValue.svelte index 7e6ee866344..0b3f6ad9474 100644 --- a/web-common/src/features/dashboards/leaderboard/ContextColumnValue.svelte +++ b/web-common/src/features/dashboards/leaderboard/ContextColumnValue.svelte @@ -11,6 +11,7 @@ const { selectors: { contextColumn: { + contextColumn, widthPx, isDeltaAbsolute, isDeltaPercent, @@ -19,36 +20,59 @@ }, numberFormat: { activeMeasureFormatter }, }, + actions: { + contextCol: { observeContextColumnWidth }, + }, } = getStateManagers(); $: negativeChange = itemData.deltaAbs !== null && itemData.deltaAbs < 0; $: noChangeData = itemData.deltaRel === null; + + let element: HTMLElement; + + $: { + // Re-observe the width when the context column changes, + // but after a short delay to allow the DOM to update. + if (element && $contextColumn) { + setTimeout(() => { + // the element may be gone by the time we get here, + // if so, don't try to observe it + if (!element) return; + observeContextColumnWidth( + $contextColumn, + element.getBoundingClientRect().width + ); + }, 17); + } + } {#if !$isHidden} -
- {#if $isPercentOfTotal} - - {:else if noChangeData} - no data - {:else if $isDeltaPercent} - - {:else if $isDeltaAbsolute} - - {/if} +
+
+ {#if $isPercentOfTotal} + + {:else if noChangeData} + no data + {:else if $isDeltaPercent} + + {:else if $isDeltaAbsolute} + + {/if} +
{/if} diff --git a/web-common/src/features/dashboards/leaderboard/LeaderboardContextColumnMenu.svelte b/web-common/src/features/dashboards/leaderboard/LeaderboardContextColumnMenu.svelte index 823cbf5da67..b9bcf5a657f 100644 --- a/web-common/src/features/dashboards/leaderboard/LeaderboardContextColumnMenu.svelte +++ b/web-common/src/features/dashboards/leaderboard/LeaderboardContextColumnMenu.svelte @@ -1,17 +1,21 @@ diff --git a/web-common/src/features/dashboards/leaderboard/LeaderboardControls.svelte b/web-common/src/features/dashboards/leaderboard/LeaderboardControls.svelte index 748b63d5561..70d1c308fef 100644 --- a/web-common/src/features/dashboards/leaderboard/LeaderboardControls.svelte +++ b/web-common/src/features/dashboards/leaderboard/LeaderboardControls.svelte @@ -13,9 +13,17 @@ import { metricsExplorerStore } from "web-common/src/features/dashboards/stores/dashboard-stores"; import { useMetaQuery } from "../selectors"; import LeaderboardContextColumnMenu from "./LeaderboardContextColumnMenu.svelte"; + import { getStateManagers } from "../state-managers/state-managers"; export let metricViewName; + const { + actions: { + contextCol: { setContextColumn }, + setLeaderboardMeasureName, + }, + } = getStateManagers(); + $: metaQuery = useMetaQuery($runtime.instanceId, metricViewName); $: measures = $metaQuery.data?.measures; @@ -24,10 +32,7 @@ $: metricsExplorer = $metricsExplorerStore.entities[metricViewName]; function handleMeasureUpdate(event: CustomEvent) { - metricsExplorerStore.setLeaderboardMeasureName( - metricViewName, - event.detail.key - ); + setLeaderboardMeasureName(event.detail.key); } function measureKeyAndMain(measure: MetricsViewSpecMeasureV2) { @@ -93,10 +98,7 @@ metricsExplorer?.leaderboardContextColumn === LeaderboardContextColumn.PERCENT ) { - metricsExplorerStore.setContextColumn( - metricViewName, - LeaderboardContextColumn.HIDDEN - ); + setContextColumn(LeaderboardContextColumn.HIDDEN); } $: showHideDimensions = createShowHideDimensionsStore( @@ -145,7 +147,7 @@ on:select={handleMeasureUpdate} /> - +
{:else}
{ + for (const contextColumn in contextColumnWidths) { + contextColumnWidths[contextColumn as LeaderboardContextColumn] = + contextColWidthDefaults[contextColumn as LeaderboardContextColumn]; + } +}; + +/** + * Observe this width value, updating the overall width of + * the context column if the given width is larger than the + * current width. + */ +export const observeContextColumnWidth = ( + { dashboard }: DashboardMutables, + contextColumn: LeaderboardContextColumn, + width: number +) => { + dashboard.contextColumnWidths[contextColumn] = Math.min( + Math.max(width, dashboard.contextColumnWidths[contextColumn]), + CONTEXT_COL_MAX_WIDTH + ); +}; + export const contextColActions = { /** * Updates the dashboard to use the context column of the given type, * as well as updating to sort by that context column. */ setContextColumn, + + /** + * Observe this width value, updating the overall width of + * the context column if the given width is larger than the + * current width. + */ + observeContextColumnWidth, }; diff --git a/web-common/src/features/dashboards/state-managers/actions/core-actions.ts b/web-common/src/features/dashboards/state-managers/actions/core-actions.ts index cc57838ddac..acf421281b5 100644 --- a/web-common/src/features/dashboards/state-managers/actions/core-actions.ts +++ b/web-common/src/features/dashboards/state-managers/actions/core-actions.ts @@ -1,3 +1,4 @@ +import { resetAllContextColumnWidths } from "./context-columns"; import type { DashboardMutables } from "./types"; export const setLeaderboardMeasureName = ( @@ -5,4 +6,7 @@ export const setLeaderboardMeasureName = ( name: string ) => { dashboard.leaderboardMeasureName = name; + + // reset column widths when changing the leaderboard measure + resetAllContextColumnWidths(dashboard.contextColumnWidths); }; diff --git a/web-common/src/features/dashboards/state-managers/selectors/context-column.ts b/web-common/src/features/dashboards/state-managers/selectors/context-column.ts index 5326760dc9e..fe492b28430 100644 --- a/web-common/src/features/dashboards/state-managers/selectors/context-column.ts +++ b/web-common/src/features/dashboards/state-managers/selectors/context-column.ts @@ -1,18 +1,13 @@ import { LeaderboardContextColumn } from "../../leaderboard-context-column"; import type { DashboardDataSources } from "./types"; -const contextColumnWidth = (contextType: LeaderboardContextColumn): string => { - switch (contextType) { - case LeaderboardContextColumn.DELTA_ABSOLUTE: - case LeaderboardContextColumn.DELTA_PERCENT: - return "56px"; - case LeaderboardContextColumn.PERCENT: - return "44px"; - case LeaderboardContextColumn.HIDDEN: - return "0px"; - default: - throw new Error("Invalid context column, all cases must be handled"); +const contextColumnWidth = ({ dashboard }: DashboardDataSources): string => { + const contextType = dashboard.leaderboardContextColumn; + const width = dashboard.contextColumnWidths[contextType]; + if (typeof width === "number") { + return width + "px"; } + return "0px"; }; export const contextColSelectors = { @@ -61,6 +56,5 @@ export const contextColSelectors = { * returns a css style string specifying the width of the context * column in the leaderboards. */ - widthPx: ({ dashboard }: DashboardDataSources) => - contextColumnWidth(dashboard.leaderboardContextColumn), + widthPx: contextColumnWidth, }; diff --git a/web-common/src/features/dashboards/stores/dashboard-store-defaults.ts b/web-common/src/features/dashboards/stores/dashboard-store-defaults.ts index aa185b1edfe..e2aa2a24a13 100644 --- a/web-common/src/features/dashboards/stores/dashboard-store-defaults.ts +++ b/web-common/src/features/dashboards/stores/dashboard-store-defaults.ts @@ -3,7 +3,10 @@ import { SortDirection, SortType, } from "@rilldata/web-common/features/dashboards/proto-state/derived-types"; -import type { MetricsExplorerEntity } from "@rilldata/web-common/features/dashboards/stores/metrics-explorer-entity"; +import { + contextColWidthDefaults, + type MetricsExplorerEntity, +} from "@rilldata/web-common/features/dashboards/stores/metrics-explorer-entity"; import { getLocalUserPreferences } from "@rilldata/web-common/features/dashboards/user-preferences"; import { getTimeComparisonParametersForComponent } from "@rilldata/web-common/lib/time/comparisons"; import { DEFAULT_TIME_RANGES } from "@rilldata/web-common/lib/time/config"; @@ -106,19 +109,27 @@ export function getDefaultMetricsExplorerEntity( name: string, metricsView: V1MetricsViewSpec, fullTimeRange: V1ColumnTimeRangeResponse | undefined -) { +): MetricsExplorerEntity { + // CAST SAFETY: safe b/c (1) measure.name is a string if defined, + // and (2) we filter out undefined values + const defaultMeasureNames = (metricsView?.measures + ?.map((measure) => measure?.name) + .filter((name) => name !== undefined) ?? []) as string[]; + + // CAST SAFETY: safe b/c (1) measure.name is a string if defined, + // and (2) we filter out undefined values + const defaultDimNames = (metricsView?.dimensions + ?.map((dim) => dim.name) + .filter((name) => name !== undefined) ?? []) as string[]; + const metricsExplorer: MetricsExplorerEntity = { name, - - visibleMeasureKeys: new Set( - metricsView.measures.map((measure) => measure.name) - ), + selectedMeasureNames: [...defaultMeasureNames], + visibleMeasureKeys: new Set(defaultMeasureNames), allMeasuresVisible: true, - visibleDimensionKeys: new Set( - metricsView.dimensions.map((dim) => dim.name) - ), + visibleDimensionKeys: new Set(defaultDimNames), allDimensionsVisible: true, - leaderboardMeasureName: metricsView.measures[0]?.name, + leaderboardMeasureName: defaultMeasureNames[0], filters: { include: [], exclude: [], @@ -131,6 +142,7 @@ export function getDefaultMetricsExplorerEntity( showTimeComparison: false, dimensionSearchText: "", pinIndex: -1, + contextColumnWidths: { ...contextColWidthDefaults }, }; // set time range related stuff setDefaultTimeRange(metricsView, metricsExplorer, fullTimeRange); diff --git a/web-common/src/features/dashboards/stores/dashboard-stores.ts b/web-common/src/features/dashboards/stores/dashboard-stores.ts index e9bd0a5a5d0..62427221cb2 100644 --- a/web-common/src/features/dashboards/stores/dashboard-stores.ts +++ b/web-common/src/features/dashboards/stores/dashboard-stores.ts @@ -206,7 +206,18 @@ const metricViewReducers = { }); }, + /** + * DEPRECATED!!! + * use setLeaderboardMeasureName via: + * getStateManagers().actions.setLeaderboardMeasureName + * + * Still used in tests, so we can't remove it yet, but don't use + * it in production code. + */ setLeaderboardMeasureName(name: string, measureName: string) { + console.warn( + "setLeaderboardMeasureName is deprecated. Use setLeaderboardMeasureName via `getStateManagers().actions.setLeaderboardMeasureName`. Still used in tests, so we can't remove it yet, but don't use it in production code." + ); updateMetricsExplorerByName(name, (metricsExplorer) => { metricsExplorer.leaderboardMeasureName = measureName; }); diff --git a/web-common/src/features/dashboards/stores/metrics-explorer-entity.ts b/web-common/src/features/dashboards/stores/metrics-explorer-entity.ts index c1923d2b4ca..df56eaa1395 100644 --- a/web-common/src/features/dashboards/stores/metrics-explorer-entity.ts +++ b/web-common/src/features/dashboards/stores/metrics-explorer-entity.ts @@ -1,4 +1,4 @@ -import type { LeaderboardContextColumn } from "@rilldata/web-common/features/dashboards/leaderboard-context-column"; +import { LeaderboardContextColumn } from "@rilldata/web-common/features/dashboards/leaderboard-context-column"; import type { SortDirection, SortType, @@ -125,6 +125,13 @@ export interface MetricsExplorerEntity { */ leaderboardContextColumn: LeaderboardContextColumn; + /** + * Width of each context column. Needs to be reset to default + * when changing context column or switching between leaderboard + * and dimension detail table + */ + contextColumnWidths: ContextColWidths; + /** * The name of the dimension that is currently shown in the dimension * detail table. If this is undefined, then the dimension detail table @@ -134,3 +141,15 @@ export interface MetricsExplorerEntity { proto?: string; } + +export type ContextColWidths = { + [LeaderboardContextColumn.DELTA_ABSOLUTE]: number; + [LeaderboardContextColumn.DELTA_PERCENT]: number; + [LeaderboardContextColumn.PERCENT]: number; +}; + +export const contextColWidthDefaults: ContextColWidths = { + [LeaderboardContextColumn.DELTA_ABSOLUTE]: 56, + [LeaderboardContextColumn.DELTA_PERCENT]: 44, + [LeaderboardContextColumn.PERCENT]: 44, +};