Skip to content

Commit

Permalink
Add categorisation for rill times
Browse files Browse the repository at this point in the history
  • Loading branch information
AdityaHegde committed Nov 20, 2024
1 parent eb774cf commit bbbe3d5
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 66 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// WIP as of 04/19/2024

import { parseRillTime } from "@rilldata/web-common/features/dashboards/url-state/time-ranges/parser";
import {
RillTimeType,
type RillTime,
} from "@rilldata/web-common/features/dashboards/url-state/time-ranges/RillTime";
import { humaniseISODuration } from "@rilldata/web-common/lib/time/ranges/iso-ranges";
import { writable, type Writable, get } from "svelte/store";
import {
Expand All @@ -10,7 +15,7 @@ import {
Duration,
IANAZone,
} from "luxon";
import type { MetricsViewSpecAvailableTimeRange } from "@rilldata/web-common/runtime-client";
import type { V1TimeRange } from "@rilldata/web-common/runtime-client";

// CONSTANTS -> time-control-constants.ts

Expand Down Expand Up @@ -84,8 +89,8 @@ export type RillPreviousPeriod = RillPreviousPeriodTuple[number];
type RillLatestTuple = typeof RILL_LATEST;
export type RillLatest = RillLatestTuple[number];

export const CUSTOM_TIME_RANGE_ALIAS = "CUSTOM" as const;
export const ALL_TIME_RANGE_ALIAS = "inf" as const;
export const CUSTOM_TIME_RANGE_ALIAS = "CUSTOM";
export const ALL_TIME_RANGE_ALIAS = "inf";
export type AllTime = typeof ALL_TIME_RANGE_ALIAS;
export type CustomRange = typeof CUSTOM_TIME_RANGE_ALIAS;
export type ISODurationString = string;
Expand Down Expand Up @@ -380,53 +385,83 @@ export function getRangeLabel(range: NamedRange | ISODurationString): string {

// BUCKETS FOR DISPLAYING IN DROPDOWN (yaml spec may make this unnecessary)

export type RangeBucket = {
range: string;
label: string;
shortLabel: string;
};
export type RangeBuckets = {
latest: { label: string; range: ISODurationString }[];
previous: { range: RillPreviousPeriod; label: string }[];
periodToDate: { range: RillPeriodToDate; label: string }[];
latest: RangeBucket[];
latestIncomplete: RangeBucket[];
previous: RangeBucket[];
periodToDate: RangeBucket[];
};

const defaultBuckets = {
previous: RILL_PREVIOUS_PERIOD.map((range) => ({
range,
label: RILL_TO_LABEL[range],
})),
latest: RILL_LATEST.map((range) => ({
range,
label: getDurationLabel(range),
shortLabel: getDurationLabel(range),
})),
latestIncomplete: [],
previous: RILL_PREVIOUS_PERIOD.map((range) => ({
range,
label: RILL_TO_LABEL[range],
shortLabel: RILL_TO_LABEL[range],
})),
periodToDate: RILL_PERIOD_TO_DATE.map((range) => ({
range,
label: RILL_TO_LABEL[range],
shortLabel: RILL_TO_LABEL[range],
})),
};

export function bucketYamlRanges(
availableRanges: MetricsViewSpecAvailableTimeRange[],
): RangeBuckets {
const showDefaults = !availableRanges.length;

export function bucketTimeRanges(timeRanges: V1TimeRange[]) {
const showDefaults = !timeRanges.length;
if (showDefaults) {
return defaultBuckets;
}

return availableRanges.reduce(
(record, { range }) => {
if (!range) return record;

if (isRillPeriodToDate(range)) {
record.periodToDate.push({ range, label: RILL_TO_LABEL[range] });
} else if (isRillPreviousPeriod(range)) {
record.previous.push({ range, label: RILL_TO_LABEL[range] });
} else {
record.latest.push({ range, label: getDurationLabel(range) });
return timeRanges.reduce(
(buckets, timeRange) => {
if (!timeRange) return buckets;
let rillTime: RillTime | null = null;
try {
rillTime = parseRillTime(timeRange.rillTime ?? "");
} catch {
// no-op
}
if (!rillTime) return buckets;

const bucket = <RangeBucket>{
range: timeRange.rillTime ?? "",
label: rillTime.getLabel({ completeness: false }),
shortLabel: rillTime.getLabel({ completeness: false }),
};

switch (rillTime.type) {
case RillTimeType.Unknown:
case RillTimeType.Latest:
if (rillTime.isComplete) {
buckets.latest.push(bucket);
} else {
buckets.latestIncomplete.push(bucket);
}
break;
case RillTimeType.PreviousPeriod:
buckets.previous.push(bucket);
break;
case RillTimeType.PeriodToDate:
buckets.periodToDate.push(bucket);
break;
}

return record;
return buckets;
},
<RangeBuckets>{
previous: [],
latest: [],
latestIncomplete: [],
periodToDate: [],
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
import { initLocalUserPreferenceStore } from "../../user-preferences";
import {
ALL_TIME_RANGE_ALIAS,
bucketTimeRanges,
CUSTOM_TIME_RANGE_ALIAS,
type ISODurationString,
type NamedRange,
type RangeBuckets,
} from "../new-time-controls";
import * as Elements from "./components";
Expand All @@ -32,7 +32,6 @@
const {
exploreName,
selectors: {
timeRangeSelectors: { timeRangeSelectorState },
charts: { canPanLeft, canPanRight, getNewPanRange },
},
validSpecStore,
Expand Down Expand Up @@ -61,33 +60,9 @@
$: availableTimeZones = exploreSpec.timeZones ?? [];
$: ({
latestWindowTimeRanges,
periodToDateRanges,
previousCompleteDateRanges,
showDefaultItem,
} = $timeRangeSelectorState);
$: ranges = <RangeBuckets>{
latest: [
...latestWindowTimeRanges.map((range) => ({
range: range.name,
label: range.label,
})),
...($timeRanges.data?.ranges?.map((r) => ({
range: r.rillTime,
label: r.rillTime,
})) ?? []),
],
periodToDate: periodToDateRanges.map((range) => ({
range: range.name,
label: range.label,
})),
previous: previousCompleteDateRanges.map((range) => ({
range: range.name,
label: range.label,
})),
};
let showDefaultItem = false; // TODO
$: ranges = bucketTimeRanges($timeRanges.data?.ranges ?? []);
$: activeTimeGrain = selectedTimeRange?.interval;
Expand Down Expand Up @@ -116,7 +91,6 @@
}
const tr = $timeRanges.data.ranges.find((r) => r.rillTime === name);
console.log(name, tr);
if (!tr) return;
const baseTimeRange: TimeRange = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<script lang="ts">
import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu/";
import { parseRillTime } from "@rilldata/web-common/features/dashboards/url-state/time-ranges/parser";
import type {
RangeBuckets,
NamedRange,
ISODurationString,
} from "../../new-time-controls";
import { getRangeLabel } from "../../new-time-controls";
import TimeRangeMenu from "./TimeRangeMenu.svelte";
import { DateTime, Interval } from "luxon";
import RangeDisplay from "./RangeDisplay.svelte";
Expand All @@ -25,6 +25,16 @@
let firstVisibleMonth: DateTime<true> = interval.start;
let open = false;
let showSelector = false;
let selectedLabel = "";
$: {
try {
const rt = parseRillTime(selected);
selectedLabel = rt.getLabel({ completeness: false });
} catch {
selectedLabel = selected;
}
}
</script>

<DropdownMenu.Root
Expand All @@ -45,7 +55,7 @@
class="flex gap-x-1"
aria-label="Select time range"
>
<b class="mr-1 line-clamp-1 flex-none">{getRangeLabel(selected)}</b>
<b class="mr-1 line-clamp-1 flex-none">{selectedLabel}</b>
{#if interval.isValid}
<RangeDisplay {interval} {grain} />
{/if}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

{#if ranges.latest.length}
<DropdownMenu.Separator />
<DropdownMenu.Label>COMPLETE</DropdownMenu.Label>
{/if}

{#each ranges.latest as { range, label } (range)}
Expand All @@ -52,6 +53,19 @@
</DropdownMenu.Item>
{/each}

{#if ranges.latestIncomplete.length}
<DropdownMenu.Separator />
<DropdownMenu.Label>INCOMPLETE</DropdownMenu.Label>
{/if}

{#each ranges.latestIncomplete as { range, label } (range)}
<DropdownMenu.Item on:click={handleClick} data-range={range}>
<span class:font-bold={selected === range}>
{label}
</span>
</DropdownMenu.Item>
{/each}

{#if ranges.periodToDate.length}
<DropdownMenu.Separator />
{/if}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
export enum RillTimeType {
Unknown = "Unknown",
Latest = "Latest",
PreviousPeriod = "Previous period",
PeriodToDate = "Period To Date",
}

export class RillTime {
public readonly isComplete: boolean;
public readonly end: RillTimeModifier;
public readonly type: RillTimeType;

public constructor(
public readonly start: RillTimeModifier,
end: RillTimeModifier,
public readonly modifier: RillTimeRangeModifier | undefined,
) {
this.type = start.getType();

this.end = end ?? RillTimeModifier.now();
this.isComplete =
this.end.type === RillTimeModifierType.Custom ||
this.end.truncate !== undefined;
}

public getLabel() {
const start = capitalizeFirstChar(this.start.getLabel(this.isComplete));
const completeSuffix = this.isComplete ? "complete" : "incomplete";
return `${start}, ${completeSuffix}`;
public getLabel({ completeness }: { completeness: boolean }) {
const start = capitalizeFirstChar(this.start.getLabel());
const completeSuffix = ", " + (this.isComplete ? "complete" : "incomplete");
if (completeness) {
return `${start}${completeSuffix}`;
}
return start;
}
}

Expand Down Expand Up @@ -65,7 +78,7 @@ export class RillTimeModifier {
return this;
}

public getLabel(isComplete: boolean) {
public getLabel() {
const grain = this.grain ?? this.truncate;
if (!grain) {
return RillTimeModifierType.Earliest.toString();
Expand All @@ -84,8 +97,22 @@ export class RillTimeModifier {
if (grain.count === -1) {
return `previous ${unit}`;
}
const completenessOffset = isComplete ? 0 : 1;
return `last ${-grain.count + completenessOffset} ${unit}s`;
return `last ${-grain.count} ${unit}s`;
}

public getType() {
const grain = this.grain ?? this.truncate;
if (!grain || grain.count > 0) {
return RillTimeType.Unknown;
}

if (grain.count === 0) {
return RillTimeType.PeriodToDate;
}
if (grain.count === -1) {
return RillTimeType.PreviousPeriod;
}
return RillTimeType.Latest;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ describe("rill time", () => {
expect(parser.results).length(1);

const rt = parseRillTime(rillTime);
expect(rt.getLabel()).toEqual(label);
expect(
rt.getLabel({
completeness: true,
}),
).toEqual(label);

Check failure on line 52 in web-common/src/features/dashboards/url-state/time-ranges/rill-time.spec.ts

View workflow job for this annotation

GitHub Actions / build

src/features/dashboards/url-state/time-ranges/rill-time.spec.ts > rill time > positive cases > -6d, now : |h|

AssertionError: expected 'Last 6 days, incomplete' to deeply equal 'Last 7 days, incomplete' Expected: "Last 7 days, incomplete" Received: "Last 6 days, incomplete" ❯ src/features/dashboards/url-state/time-ranges/rill-time.spec.ts:52:11

Check failure on line 52 in web-common/src/features/dashboards/url-state/time-ranges/rill-time.spec.ts

View workflow job for this annotation

GitHub Actions / build

src/features/dashboards/url-state/time-ranges/rill-time.spec.ts > rill time > positive cases > -6d, now : h

AssertionError: expected 'Last 6 days, incomplete' to deeply equal 'Last 7 days, incomplete' Expected: "Last 7 days, incomplete" Received: "Last 6 days, incomplete" ❯ src/features/dashboards/url-state/time-ranges/rill-time.spec.ts:52:11
});
}
});
Expand Down

0 comments on commit bbbe3d5

Please sign in to comment.