Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sort leaderboards by context columns #2975

Closed
wants to merge 71 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
a0a361d
add story
bcolloran Aug 11, 2023
bfca234
update story default props
bcolloran Aug 11, 2023
2a53dbc
Merge remote-tracking branch 'origin/main' into leaderboard-sort-toggle
bcolloran Aug 16, 2023
28124f1
begin migrating sort info to dashboard store
bcolloran Aug 16, 2023
f0b9553
add type
bcolloran Aug 16, 2023
3b02e5e
move business logic out of component to typed fns
bcolloran Aug 16, 2023
62a883f
Showing sort arrow in leaderboards
bcolloran Aug 16, 2023
548e511
add sorting to leaderboard
bcolloran Aug 16, 2023
0e91f60
remove comments
bcolloran Aug 16, 2023
9650b8f
update copy
bcolloran Aug 16, 2023
e1112a1
Merge branch 'main' into leaderboard-sort-toggle
bcolloran Aug 16, 2023
d1ced5b
add enum variant for DELTA_ABSOLUTE
bcolloran Aug 17, 2023
f001144
fix arrow flipping on SearchableFilterButton
bcolloran Aug 17, 2023
5d39aec
change leaderboard context column controls
bcolloran Aug 17, 2023
5c32f1f
slightly tighten spacing on SelectButton
bcolloran Aug 17, 2023
a0dc016
Merge branch 'change-context-column-toggle-btns-to-dropdown' into abs…
bcolloran Aug 17, 2023
1cecc0e
remove deprecated component
bcolloran Aug 17, 2023
dd43272
add absolute change to menu and leaderboard header
bcolloran Aug 17, 2023
88bffbf
move context column value to own component,
bcolloran Aug 17, 2023
a66d823
Merge branch 'main' into change-context-column-toggle-btns-to-dropdown
bcolloran Aug 17, 2023
042fe7d
Merge branch 'change-context-column-toggle-btns-to-dropdown' into abs…
bcolloran Aug 17, 2023
476e7f8
Merge branch 'leaderboard-sort-toggle' into sort-by-context-columns
bcolloran Aug 17, 2023
dc9cef6
merge fixes
bcolloran Aug 17, 2023
23721d4
UI for context column sorting is in place
bcolloran Aug 17, 2023
8ff2240
change name from DELTA_CHANGE to DELTA_PERCENT
bcolloran Aug 17, 2023
017af7e
update and regenerate protobuf
bcolloran Aug 17, 2023
b82256f
clean up reexport
bcolloran Aug 17, 2023
ab409a9
fix swapped export
bcolloran Aug 17, 2023
6f1a493
Merge branch 'leaderboard-sort-toggle' into sort-by-context-columns
bcolloran Aug 17, 2023
88de0a0
update generated protobuf
bcolloran Aug 17, 2023
3064413
Merge remote-tracking branch 'origin/main' into change-context-column…
bcolloran Aug 18, 2023
056b0c1
update DELTA_CHANGE to DELTA_PERCENT
bcolloran Aug 18, 2023
1c82561
Merge branch 'change-context-column-toggle-btns-to-dropdown' into abs…
bcolloran Aug 18, 2023
4513124
merge cleanups
bcolloran Aug 18, 2023
dadb370
Merge remote-tracking branch 'origin/main' into absolute-change-optio…
bcolloran Aug 18, 2023
e5370a1
remove logging
bcolloran Aug 18, 2023
b37adde
adjustment to leaderboard column widths
bcolloran Aug 18, 2023
db76abf
Adding to url state
AdityaHegde Aug 22, 2023
a420c86
change enums back to original names
bcolloran Aug 17, 2023
0c84830
fix dimension table sort arrow
bcolloran Aug 24, 2023
2fa593c
cleanup
bcolloran Aug 24, 2023
8355785
cleanups
bcolloran Aug 24, 2023
cd3b7fd
cleanups
bcolloran Aug 24, 2023
015adba
remove redeclaration
bcolloran Aug 24, 2023
d1ae608
add default prop
bcolloran Aug 24, 2023
5a1e8cc
Merge branch 'absolute-change-option-in-context-column' into sort-by-…
bcolloran Aug 24, 2023
c224f2c
final merg conflict updates
bcolloran Aug 24, 2023
b072523
fix weid dupe line
bcolloran Aug 25, 2023
75c61be
Merge branch 'main' into sort-by-context-columns
bcolloran Sep 5, 2023
78ce3a0
Merge branch 'main' into sort-by-context-columns
bcolloran Sep 5, 2023
4e273fd
cleanup
bcolloran Sep 6, 2023
b59ad62
sortedQuery returning correct results
bcolloran Sep 6, 2023
e7b64b8
seems to be working, but sort API returns multiple rows with `null` d…
bcolloran Sep 6, 2023
c988cc2
Merge branch 'main' into sort-by-context-columns
bcolloran Sep 6, 2023
844e9f4
comment out or remove old query code
bcolloran Sep 7, 2023
a923e1e
update query sort to be driven by UI
bcolloran Sep 7, 2023
e843e32
add absolute delta back
bcolloran Sep 7, 2023
553212e
cleanup
bcolloran Sep 7, 2023
f493f15
cleanups
bcolloran Sep 7, 2023
5481f0b
cleanups
bcolloran Sep 7, 2023
da7bbc9
Merge remote-tracking branch 'origin/main' into sort-by-context-columns
bcolloran Sep 8, 2023
a7cbffe
fixes to percentage formatting
bcolloran Sep 8, 2023
7546b6d
formatter updates applied
bcolloran Sep 12, 2023
b9be1cf
cleanup
bcolloran Sep 12, 2023
95d1c7f
Merge branch 'main' into sort-by-context-columns,
bcolloran Sep 12, 2023
e1638a8
cleanups
bcolloran Sep 13, 2023
0903622
fix test
bcolloran Sep 13, 2023
72477bf
update tests to look for comparison API
bcolloran Sep 13, 2023
31439f7
remove unused import
bcolloran Sep 13, 2023
28b93f7
fix sorting when changing context column
bcolloran Sep 13, 2023
516063e
fix sort mapping
bcolloran Sep 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 22 additions & 14 deletions web-common/src/components/data-types/FormattedDataType.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@

let dataType;
$: {
if (type === "RILL_PERCENTAGE_CHANGE") {
dataType = PercentageChange;
} else if (type === "RILL_CHANGE") {
dataType = MeasureChange;
} else if (NUMERICS.has(type)) {
if (NUMERICS.has(type)) {
dataType = Number;
} else if (TIMESTAMPS.has(type)) {
dataType = Timestamp;
Expand All @@ -38,12 +34,24 @@
}
</script>

<svelte:component
this={dataType}
isNull={isNull || value === null}
{inTable}
{customStyle}
{dark}
{type}
{value}
/>
<!--
NOTE:
PercentageChange and MeasureChange don't take a `type` prop,
so instantiating these directly clears a ton of warnings
about unknown props.
-->
{#if type === "RILL_PERCENTAGE_CHANGE"}
<PercentageChange {value} {isNull} {inTable} {customStyle} {dark} />
{:else if type === "RILL_CHANGE"}
<MeasureChange {value} {isNull} {inTable} {customStyle} {dark} />
{:else}
<svelte:component
this={dataType}
isNull={isNull || value === null}
{inTable}
{customStyle}
{dark}
{type}
{value}
/>
{/if}
48 changes: 40 additions & 8 deletions web-common/src/components/data-types/PercentageChange.svelte
Original file line number Diff line number Diff line change
@@ -1,15 +1,49 @@
<script>
<script lang="ts">
import Base from "./Base.svelte";
import { PERC_DIFF } from "./type-utils";
import type { NumberParts } from "@rilldata/web-common/lib/number-formatting/humanizer-types";
export let isNull = false;
export let inTable = false;
export let dark = false;
export let customStyle = "";
export let value;
export let value: undefined | NumberParts | PERC_DIFF;
export let tabularNumber = true;

$: diffIsNegative = value?.neg === "-";
$: intValue = value?.int ? value?.int : value?.int === 0 ? 0 : "";
let diffIsNegative = false;
let intValue: string;
let negSign = "";
let approxSign = "";
let suffix = "";

$: isNoData = Object.values(PERC_DIFF).includes(value) || value === undefined;

$: if (!isNoData) {
// in this case, we have a NumberParts object.
// We have a couple cases to consider:
// * If the NumberParts object has approxZero===true,
// we want to show e.g. "~0%" WITHOUT a negative sign
// * However, in this case we show the number in red to indicate a
// small negative change.
//
// Otherwise, we format the number as usual.

intValue = value?.int;
diffIsNegative = value?.neg === "-";
negSign = diffIsNegative && !value?.approxZero ? "-" : "";
approxSign = value?.approxZero ? "~" : "";
suffix = value?.suffix ?? "";

// This formatter should only ever recieve a NumberParts object
// with a value.percent === "%" field. If that invariant fails,
// we don't want to crash, but we'll emit a warning.
if (value?.percent !== "%") {
console.warn(
`PercentageChange component expects a NumberParts object with a percent sign, received ${JSON.stringify(
value
)} instead.`
);
}
}
</script>

<Base
Expand All @@ -20,13 +54,11 @@
{dark}
>
<slot name="value">
{#if value === PERC_DIFF.PREV_VALUE_NO_DATA || value === PERC_DIFF.PREV_VALUE_NULL}
{#if isNoData}
<span class="opacity-50 italic" style:font-size=".925em">no data</span>
{:else if value !== undefined}
<span class:text-red-500={diffIsNegative}>
{value?.neg || ""}{intValue}<span class="opacity-50"
>{value?.percent || ""}</span
>
{approxSign}{negSign}{intValue}{suffix}<span class="opacity-50">%</span>
</span>
{/if}
</slot>
Expand Down
62 changes: 55 additions & 7 deletions web-common/src/features/dashboards/dashboard-stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,12 +382,26 @@ const metricViewReducers = {
});
},

toggleSortDirection(name: string) {
toggleSort(name: string, sortType?: SortType) {
updateMetricsExplorerByName(name, (metricsExplorer) => {
metricsExplorer.sortDirection =
metricsExplorer.sortDirection === SortDirection.ASCENDING
? SortDirection.DESCENDING
: SortDirection.ASCENDING;
// if sortType is not provided, or if it is provided
// and is the same as the current sort type,
// then just toggle the current sort direction
if (
sortType === undefined ||
metricsExplorer.dashboardSortType === sortType
) {
metricsExplorer.sortDirection =
metricsExplorer.sortDirection === SortDirection.ASCENDING
? SortDirection.DESCENDING
: SortDirection.ASCENDING;
} else {
// if the sortType is different from the current sort type,
// then update the sort type and set the sort direction
// to descending
metricsExplorer.dashboardSortType = sortType;
metricsExplorer.sortDirection = SortDirection.DESCENDING;
}
});
},

Expand Down Expand Up @@ -492,16 +506,30 @@ const metricViewReducers = {

setContextColumn(name: string, contextColumn: LeaderboardContextColumn) {
updateMetricsExplorerByName(name, (metricsExplorer) => {
const initialSort = sortTypeForContextColumnType(
metricsExplorer.leaderboardContextColumn
);
switch (contextColumn) {
case LeaderboardContextColumn.DELTA_ABSOLUTE:
case LeaderboardContextColumn.DELTA_PERCENT: {
// if there is no time comparison, then we can't show
// these context columns, so return with no change
if (metricsExplorer.showComparison === false) return;

metricsExplorer.leaderboardContextColumn = contextColumn;
return;
break;
}
default:
metricsExplorer.leaderboardContextColumn = contextColumn;
return;
}

// if we have changed the context column, and the leaderboard is
// sorted by the context column from before we made the change,
// then we also need to change
// the sort type to match the new context column
if (metricsExplorer.dashboardSortType === initialSort) {
metricsExplorer.dashboardSortType =
sortTypeForContextColumnType(contextColumn);
}
});
},
Expand Down Expand Up @@ -655,6 +683,26 @@ function setDisplayComparison(
}
}

function sortTypeForContextColumnType(
contextCol: LeaderboardContextColumn
): SortType {
const sortType = {
[LeaderboardContextColumn.DELTA_PERCENT]: SortType.DELTA_PERCENT,
[LeaderboardContextColumn.DELTA_ABSOLUTE]: SortType.DELTA_ABSOLUTE,
[LeaderboardContextColumn.PERCENT]: SortType.PERCENT,
[LeaderboardContextColumn.HIDDEN]: SortType.VALUE,
}[contextCol];

// Note: the above map needs to be EXHAUSTIVE over
// LeaderboardContextColumn variants. If we ever add a new
// context column type, we need to add it to the map above.
// Otherwise, we will throw an error here.
if (!sortType) {
throw new Error(`Invalid context column type: ${contextCol}`);
}
return sortType;
}

function setSelectedScrubRange(
metricsExplorer: MetricsExplorerEntity,
scrubRange: ScrubRange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@
if (!measureNames.includes(columnName)) return;

if (columnName === leaderboardMeasureName) {
metricsExplorerStore.toggleSortDirection(metricViewName);
metricsExplorerStore.toggleSort(metricViewName);
} else {
metricsExplorerStore.setLeaderboardMeasureName(
metricViewName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,11 @@ const expectedData = [
measure_0: 25,
measure_0_delta: 0,
measure_0_delta_perc: {
int: 0,
neg: "",
dot: "",
frac: "",
int: "0",
percent: "%",
suffix: "",
},
},
{
Expand Down
88 changes: 77 additions & 11 deletions web-common/src/features/dashboards/humanize-numbers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { humanizedFormatterFactory } from "@rilldata/web-common/lib/number-forma
import {
FormatterFactoryOptions,
NumberKind,
NumberParts,
} from "@rilldata/web-common/lib/number-formatting/humanizer-types";
import {
formatMsInterval,
Expand Down Expand Up @@ -167,17 +168,82 @@ function humanizeGroupValuesUtil2(values: number[], type: FormatPreset) {
});
}

/** formatter for the comparison percentage differences */
export function formatMeasurePercentageDifference(
value,
method = "partsFormat"
) {
if (Math.abs(value * 100) < 1 && value !== 0) {
return method === "partsFormat"
? { percent: "%", neg: "", int: "<1" }
: "<1%";
/**
* Formatter for the comparison percentage differences.
* Input values are given as proportions, not percentages
* (not yet multiplied by 100). However, inputs may be
* any real number, not just a proper fraction, so negative
* values and values of arbitrarily large magnitudes must be
* supported.
*/
export function formatMeasurePercentageDifference(value): NumberParts {
if (value === 0) {
return { percent: "%", int: "0", dot: "", frac: "", suffix: "" };
} else if (value < 0.005 && value > 0) {
return {
percent: "%",
int: "0",
dot: "",
frac: "",
suffix: "",
approxZero: true,
};
} else if (value > -0.005 && value < 0) {
return {
percent: "%",
neg: "-",
int: "0",
dot: "",
frac: "",
suffix: "",
approxZero: true,
};
}

const factory = new PerRangeFormatter([], {
strategy: "perRange",
rangeSpecs: [
{
minMag: -2,
supMag: 3,
maxDigitsRight: 1,
baseMagnitude: 0,
padWithInsignificantZeros: false,
},
],
defaultMaxDigitsRight: 0,
numberKind: NumberKind.PERCENT,
});

return factory["partsFormat"](value);
}

/**
* This function is used to format proper fractions, which
* must be between 0 and 1, as percentages. It is used in
* formatting the percentage of total column, as well as
* other contexts where the input number is guaranteed to
* be a proper fraction.
*
* If the input number is not a proper fraction, this function
* will `console.warn` (since this is not worth crashing over)
* and use formatMeasurePercentageDifference
* instead, though that will likely result in a badly formatted
* output, since formatting of proper fractions may make
* assumptions that are violated by improper fractions.
*/
export function formatProperFractionAsPercent(value): NumberParts {
if (value < 0 || value > 1) {
console.warn(
`formatProperFractionAsPercent received invalid input: ${value}. Value must be between 0 and 1.`
);
return formatMeasurePercentageDifference(value);
}

if (value < 0.01 && value !== 0) {
return { percent: "%", int: "<1", dot: "", frac: "", suffix: "" };
} else if (value === 0) {
return method === "partsFormat" ? { percent: "%", neg: "", int: 0 } : "0%";
return { percent: "%", int: "0", dot: "", frac: "", suffix: "" };
}
const factory = new PerRangeFormatter([], {
strategy: "perRange",
Expand All @@ -194,5 +260,5 @@ export function formatMeasurePercentageDifference(
numberKind: NumberKind.PERCENT,
});

return factory[method](value);
return factory["partsFormat"](value);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,43 @@
import { LeaderboardContextColumn } from "@rilldata/web-common/features/dashboards/leaderboard-context-column";
import PercentageChange from "../../../components/data-types/PercentageChange.svelte";
import { FormattedDataType } from "@rilldata/web-common/components/data-types";
import { contextColumnWidth } from "./leaderboard-utils";
import type { NumberParts } from "@rilldata/web-common/lib/number-formatting/humanizer-types";
import type { PERC_DIFF } from "@rilldata/web-common/components/data-types/type-utils";

export let formattedValue: string;
export let showContext: LeaderboardContextColumn;
$: neg = formattedValue[0] === "-";
$: noData = formattedValue === "" || !formattedValue;
$: customStyle = neg ? "text-red-500" : noData ? "opacity-50 italic" : "";
export let formattedValue:
| string
| NumberParts
| PERC_DIFF.PREV_VALUE_NO_DATA;
export let contextColumn: LeaderboardContextColumn;

let neg: boolean;
let noData: boolean;
let customStyle: string;
$: if (typeof formattedValue === "string") {
neg = formattedValue[0] === "-";
noData = formattedValue === "" || !formattedValue;
customStyle = neg ? "text-red-500" : noData ? "opacity-50 italic" : "";
}
$: width = contextColumnWidth(contextColumn);

$: if (
(contextColumn === LeaderboardContextColumn.DELTA_PERCENT ||
contextColumn === LeaderboardContextColumn.PERCENT) &&
typeof formattedValue === "string"
) {
console.warn(
"PercentageChange component expects a NumberParts object, not a string."
);
}
</script>

{#if showContext === LeaderboardContextColumn.DELTA_PERCENT || showContext === LeaderboardContextColumn.PERCENT}
<div style:width="44px">
{#if contextColumn === LeaderboardContextColumn.DELTA_PERCENT || contextColumn === LeaderboardContextColumn.PERCENT}
<div style:width>
<PercentageChange value={formattedValue} />
</div>
{:else if showContext === LeaderboardContextColumn.DELTA_ABSOLUTE}
<div style:width="54px">
{:else if contextColumn === LeaderboardContextColumn.DELTA_ABSOLUTE}
<div style:width>
{#if noData}
<span class="opacity-50 italic" style:font-size=".925em">no data</span>
{:else}
Expand Down
Loading