diff --git a/web-common/src/components/virtualized-table/core/ColumnHeader.svelte b/web-common/src/components/virtualized-table/core/ColumnHeader.svelte index b4c0943d8cc..b54b3194ac7 100644 --- a/web-common/src/components/virtualized-table/core/ColumnHeader.svelte +++ b/web-common/src/components/virtualized-table/core/ColumnHeader.svelte @@ -27,6 +27,9 @@ export let enableResize = true; export let isSelected = false; export let bgClass = ""; + // set this prop to control sorting arrow externally. + // if undefined, sorting arrow is toggled within the component. + export let sortAscending: boolean | undefined = undefined; const config: VirtualizedTableConfig = getContext("config"); const dispatch = createEventDispatcher(); @@ -34,7 +37,10 @@ const { shiftClickAction } = createShiftClickAction(); let showMore = false; - $: isSortingDesc = true; + + // if sorting is controlled externally, use that prop value + // otherwise, default to true + $: isSortingDesc = sortAscending !== undefined ? !sortAscending : true; $: isDimensionTable = config.table === "DimensionTable"; $: isDimensionColumn = isDimensionTable && type === "VARCHAR"; @@ -68,8 +74,12 @@ showMore = false; }} on:click={() => { - if (isSelected) isSortingDesc = !isSortingDesc; - else isSortingDesc = true; + // only toggle `isSortingDesc` within the component if + // sorting is not controlled externally + if (sortAscending === undefined) { + if (isSelected) isSortingDesc = !isSortingDesc; + else isSortingDesc = true; + } dispatch("click-column"); }} > diff --git a/web-common/src/components/virtualized-table/sections/ColumnHeaders.svelte b/web-common/src/components/virtualized-table/sections/ColumnHeaders.svelte index 8cabf1876f1..18ea03688a8 100644 --- a/web-common/src/components/virtualized-table/sections/ColumnHeaders.svelte +++ b/web-common/src/components/virtualized-table/sections/ColumnHeaders.svelte @@ -13,6 +13,11 @@ export let selectedColumn: string = null; export let fallbackBGClass = ""; + // set this prop to control sorting arrow externally. + // if undefined, sorting arrow is toggled within the + // cell header component. + export let sortAscending: boolean = undefined; + const getColumnHeaderProps = (header) => { const name = columns[header.index]?.label || columns[header.index]?.name; const isEnableResizeDefined = "enableResize" in columns[header.index]; @@ -61,6 +66,7 @@ {header} {noPin} {showDataIcon} + {sortAscending} on:pin={() => { dispatch("pin", columns[header.index]); }} diff --git a/web-common/src/features/dashboards/dashboard-stores.ts b/web-common/src/features/dashboards/dashboard-stores.ts index b11ba9fb092..6ef1f00d732 100644 --- a/web-common/src/features/dashboards/dashboard-stores.ts +++ b/web-common/src/features/dashboards/dashboard-stores.ts @@ -490,27 +490,19 @@ const metricViewReducers = { }); }, - displayDeltaChange(name: string) { + setContextColumn(name: string, contextColumn: LeaderboardContextColumn) { updateMetricsExplorerByName(name, (metricsExplorer) => { - // NOTE: only show delta change if comparison is enabled - if (metricsExplorer.showComparison === false) return; - - metricsExplorer.leaderboardContextColumn = - LeaderboardContextColumn.DELTA_PERCENT; - }); - }, - - displayPercentOfTotal(name: string) { - updateMetricsExplorerByName(name, (metricsExplorer) => { - metricsExplorer.leaderboardContextColumn = - LeaderboardContextColumn.PERCENT; - }); - }, - - hideContextColumn(name: string) { - updateMetricsExplorerByName(name, (metricsExplorer) => { - metricsExplorer.leaderboardContextColumn = - LeaderboardContextColumn.HIDDEN; + switch (contextColumn) { + case LeaderboardContextColumn.DELTA_ABSOLUTE: + case LeaderboardContextColumn.DELTA_PERCENT: { + if (metricsExplorer.showComparison === false) return; + metricsExplorer.leaderboardContextColumn = contextColumn; + return; + } + default: + metricsExplorer.leaderboardContextColumn = contextColumn; + return; + } }); }, diff --git a/web-common/src/features/dashboards/dimension-table/DimensionDisplay.svelte b/web-common/src/features/dashboards/dimension-table/DimensionDisplay.svelte index c2a5afc7804..8dfbd3dd5c7 100644 --- a/web-common/src/features/dashboards/dimension-table/DimensionDisplay.svelte +++ b/web-common/src/features/dashboards/dimension-table/DimensionDisplay.svelte @@ -358,6 +358,7 @@ onSelectItem(event)} on:sort={(event) => onSortByColumn(event)} + {sortAscending} dimensionName={dimensionColumn} {columns} {selectedValues} diff --git a/web-common/src/features/dashboards/dimension-table/DimensionTable.svelte b/web-common/src/features/dashboards/dimension-table/DimensionTable.svelte index 9f25e3969be..08b97483693 100644 --- a/web-common/src/features/dashboards/dimension-table/DimensionTable.svelte +++ b/web-common/src/features/dashboards/dimension-table/DimensionTable.svelte @@ -23,6 +23,7 @@ TableCells – the cell contents. export let columns: VirtualizedTableColumns[]; export let selectedValues: Array = []; export let sortByColumn: string; + export let sortAscending: boolean; export let dimensionName: string; export let excludeMode = false; @@ -203,6 +204,7 @@ TableCells – the cell contents. selectedColumn={sortByColumn} columns={measureColumns} fallbackBGClass="bg-white" + {sortAscending} on:click-column={handleColumnHeaderClick} /> diff --git a/web-common/src/features/dashboards/leaderboard-context-column.ts b/web-common/src/features/dashboards/leaderboard-context-column.ts index d689208c450..18b2885f8cb 100644 --- a/web-common/src/features/dashboards/leaderboard-context-column.ts +++ b/web-common/src/features/dashboards/leaderboard-context-column.ts @@ -6,6 +6,8 @@ export enum LeaderboardContextColumn { PERCENT = "percent", // show percent change of the value compared to the previous time range DELTA_PERCENT = "delta_change", + // show absolute change of the value compared to the previous time range + DELTA_ABSOLUTE = "delta_absolute", // Do not show the context column HIDDEN = "hidden", } diff --git a/web-common/src/features/dashboards/leaderboard/ContextColumnValue.svelte b/web-common/src/features/dashboards/leaderboard/ContextColumnValue.svelte new file mode 100644 index 00000000000..5407188707d --- /dev/null +++ b/web-common/src/features/dashboards/leaderboard/ContextColumnValue.svelte @@ -0,0 +1,25 @@ + + +{#if showContext === LeaderboardContextColumn.DELTA_PERCENT || showContext === LeaderboardContextColumn.PERCENT} +
+ +
+{:else if showContext === LeaderboardContextColumn.DELTA_ABSOLUTE} +
+ {#if noData} + no data + {:else} + + {/if} +
+{/if} diff --git a/web-common/src/features/dashboards/leaderboard/Leaderboard.svelte b/web-common/src/features/dashboards/leaderboard/Leaderboard.svelte index cc9e86cdade..dae68fd22dd 100644 --- a/web-common/src/features/dashboards/leaderboard/Leaderboard.svelte +++ b/web-common/src/features/dashboards/leaderboard/Leaderboard.svelte @@ -158,11 +158,13 @@ return b.value - a.value; }); + $: contextColumn = $dashboardStore?.leaderboardContextColumn; // Compose the comparison /toplist query $: showTimeComparison = - $dashboardStore?.leaderboardContextColumn === - LeaderboardContextColumn.DELTA_PERCENT && + (contextColumn === LeaderboardContextColumn.DELTA_PERCENT || + contextColumn === LeaderboardContextColumn.DELTA_ABSOLUTE) && $timeControlsStore?.showComparison; + $: showPercentOfTotal = $dashboardStore?.leaderboardContextColumn === LeaderboardContextColumn.PERCENT; @@ -237,8 +239,7 @@ on:mouseleave={() => (hovered = false)} > { const value: SelectMenuItem = evt.detail; const key = value.key; + metricsExplorerStore.setContextColumn(metricViewName, key); - if (key === LeaderboardContextColumn.HIDDEN) { - metricsExplorerStore.hideContextColumn(metricViewName); - } else if (key === LeaderboardContextColumn.DELTA_PERCENT) { - metricsExplorerStore.displayDeltaChange(metricViewName); - } else if (key === LeaderboardContextColumn.PERCENT) { - metricsExplorerStore.displayPercentOfTotal(metricViewName); - } + // if (key === LeaderboardContextColumn.HIDDEN) { + // metricsExplorerStore.hideContextColumn(metricViewName); + // } else if (key === LeaderboardContextColumn.DELTA_PERCENT) { + // metricsExplorerStore.displayDeltaChange(metricViewName); + // } else if (key === LeaderboardContextColumn.PERCENT) { + // metricsExplorerStore.displayPercentOfTotal(metricViewName); + // } else if (key === LeaderboardContextColumn.DELTA_ABSOLUTE) { + // metricsExplorerStore.displayDeltaAbsolute(metricViewName); + // } }; let options: SelectMenuItem[]; @@ -41,6 +44,11 @@ key: LeaderboardContextColumn.DELTA_PERCENT, disabled: !$timeControlsStore.showComparison, }, + { + main: "Absolute change", + key: LeaderboardContextColumn.DELTA_ABSOLUTE, + disabled: !$timeControlsStore.showComparison, + }, { main: "No context column", key: LeaderboardContextColumn.HIDDEN, diff --git a/web-common/src/features/dashboards/leaderboard/LeaderboardControls.svelte b/web-common/src/features/dashboards/leaderboard/LeaderboardControls.svelte index e01b763274e..d1f4cafdc5e 100644 --- a/web-common/src/features/dashboards/leaderboard/LeaderboardControls.svelte +++ b/web-common/src/features/dashboards/leaderboard/LeaderboardControls.svelte @@ -80,7 +80,10 @@ metricsExplorer?.leaderboardContextColumn === LeaderboardContextColumn.PERCENT ) { - metricsExplorerStore.hideContextColumn(metricViewName); + metricsExplorerStore.setContextColumn( + metricViewName, + LeaderboardContextColumn.HIDDEN + ); } $: showHideDimensions = createShowHideDimensionsStore( diff --git a/web-common/src/features/dashboards/leaderboard/LeaderboardHeader.svelte b/web-common/src/features/dashboards/leaderboard/LeaderboardHeader.svelte index cf6beac85a8..63ce15d0723 100644 --- a/web-common/src/features/dashboards/leaderboard/LeaderboardHeader.svelte +++ b/web-common/src/features/dashboards/leaderboard/LeaderboardHeader.svelte @@ -10,15 +10,14 @@ import Delta from "@rilldata/web-common/components/icons/Delta.svelte"; import PieChart from "@rilldata/web-common/components/icons/PieChart.svelte"; import ArrowDown from "@rilldata/web-common/components/icons/ArrowDown.svelte"; - import { CONTEXT_COLUMN_WIDTH } from "./leaderboard-utils"; import { createEventDispatcher } from "svelte"; + import { LeaderboardContextColumn } from "../leaderboard-context-column"; export let displayName: string; export let isFetching: boolean; export let dimensionDescription: string; export let hovered: boolean; - export let showTimeComparison: boolean; - export let showPercentOfTotal: boolean; + export let contextColumn: LeaderboardContextColumn; export let sortAscending: boolean; export let filterExcludeMode: boolean; @@ -26,12 +25,21 @@ let optionsMenuActive = false; const dispatch = createEventDispatcher(); + $: contextColumnWidth = (contextColumn: LeaderboardContextColumn) => { + switch (contextColumn) { + case LeaderboardContextColumn.DELTA_ABSOLUTE: + return "54px"; + case LeaderboardContextColumn.DELTA_PERCENT: + case LeaderboardContextColumn.PERCENT: + return "44px"; + case LeaderboardContextColumn.HIDDEN: + return "0px"; + default: + throw new Error("Invalid context column, all cases must be handled"); + } + }; + $: arrowTransform = sortAscending ? "scale(1 -1)" : "scale(1 1)"; - $: iconShown = showTimeComparison - ? "delta" - : showPercentOfTotal - ? "pie" - : null;
@@ -110,14 +118,16 @@ # - {#if iconShown} + {#if contextColumn !== LeaderboardContextColumn.HIDDEN}
- {#if iconShown === "delta"} + {#if contextColumn === LeaderboardContextColumn.DELTA_PERCENT} % - {:else if iconShown === "pie"} + {:else if contextColumn === LeaderboardContextColumn.DELTA_ABSOLUTE} + + {:else if contextColumn === LeaderboardContextColumn.PERCENT} % {/if}
diff --git a/web-common/src/features/dashboards/leaderboard/LeaderboardListItem.svelte b/web-common/src/features/dashboards/leaderboard/LeaderboardListItem.svelte index dba01de472d..055aae9a40b 100644 --- a/web-common/src/features/dashboards/leaderboard/LeaderboardListItem.svelte +++ b/web-common/src/features/dashboards/leaderboard/LeaderboardListItem.svelte @@ -17,15 +17,14 @@ import LeaderboardTooltipContent from "./LeaderboardTooltipContent.svelte"; - import PercentageChange from "../../../components/data-types/PercentageChange.svelte"; import LeaderboardItemFilterIcon from "./LeaderboardItemFilterIcon.svelte"; import { humanizeDataType } from "../humanize-numbers"; import LongBarZigZag from "./LongBarZigZag.svelte"; import { - CONTEXT_COLUMN_WIDTH, LeaderboardItemData, - getFormatterValueForPercDiff, + formatContextColumnValue, } from "./leaderboard-utils"; + import ContextColumnValue from "./ContextColumnValue.svelte"; export let itemData: LeaderboardItemData; $: label = itemData.label; @@ -52,17 +51,12 @@ $: formattedValue = humanizeDataType(measureValue, formatPreset); - $: percentChangeFormatted = - showContext === LeaderboardContextColumn.DELTA_PERCENT - ? getFormatterValueForPercDiff( - measureValue && comparisonValue - ? measureValue - comparisonValue - : null, - comparisonValue - ) - : showContext === LeaderboardContextColumn.PERCENT - ? getFormatterValueForPercDiff(measureValue, unfilteredTotal) - : undefined; + $: contextColumnFormattedValue = formatContextColumnValue( + itemData, + unfilteredTotal, + showContext, + formatPreset + ); $: previousValueString = comparisonValue !== undefined && comparisonValue !== null @@ -178,14 +172,10 @@ value={formattedValue || measureValue} />
- {#if percentChangeFormatted !== undefined} -
- -
- {/if} + diff --git a/web-common/src/features/dashboards/leaderboard/leaderboard-utils.ts b/web-common/src/features/dashboards/leaderboard/leaderboard-utils.ts index f7c89d5e012..0f847cf408c 100644 --- a/web-common/src/features/dashboards/leaderboard/leaderboard-utils.ts +++ b/web-common/src/features/dashboards/leaderboard/leaderboard-utils.ts @@ -1,5 +1,10 @@ import { PERC_DIFF } from "../../../components/data-types/type-utils"; -import { formatMeasurePercentageDifference } from "../humanize-numbers"; +import { + FormatPreset, + formatMeasurePercentageDifference, + humanizeDataType, +} from "../humanize-numbers"; +import { LeaderboardContextColumn } from "../leaderboard-context-column"; export function getFormatterValueForPercDiff(numerator, denominator) { if (denominator === 0) return PERC_DIFF.PREV_VALUE_ZERO; @@ -13,7 +18,11 @@ export function getFormatterValueForPercDiff(numerator, denominator) { export type LeaderboardItemData = { label: string | number; + // main value to be shown in the leaderboard value: number; + // the comparison value, which may be either the previous value + // (used to calculate the absolute or percentage change) or + // the measure total (used to calculate the percentage of total) comparisonValue: number; // selection is not enough to determine if the item is included // or excluded; for that we need to know the leaderboard's @@ -39,4 +48,38 @@ export function prepareLeaderboardItemData( }); } -export const CONTEXT_COLUMN_WIDTH = 44; +/** + * Returns the formatted value for the context column + * given the + * accounting for the context column type. + */ +export function formatContextColumnValue( + itemData: LeaderboardItemData, + unfilteredTotal: number, + contextType: LeaderboardContextColumn, + formatPreset: FormatPreset +): string { + const { value, comparisonValue } = itemData; + + switch (contextType) { + case LeaderboardContextColumn.DELTA_ABSOLUTE: { + const delta = value && comparisonValue ? value - comparisonValue : null; + let formattedValue = humanizeDataType(delta, formatPreset); + if (delta && delta > 0) { + formattedValue = "+" + formattedValue; + } + return formattedValue; + } + case LeaderboardContextColumn.DELTA_PERCENT: + return getFormatterValueForPercDiff( + value && comparisonValue ? value - comparisonValue : null, + comparisonValue + ); + case LeaderboardContextColumn.PERCENT: + return getFormatterValueForPercDiff(value, unfilteredTotal); + case LeaderboardContextColumn.HIDDEN: + return ""; + default: + throw new Error("Invalid context column, all cases must be handled"); + } +}