diff --git a/web-common/src/components/data-graphic/compositions/timestamp-profile/TimestampProfileSummary.svelte b/web-common/src/components/data-graphic/compositions/timestamp-profile/TimestampProfileSummary.svelte index fb80581fdfa..e9f50de13b1 100644 --- a/web-common/src/components/data-graphic/compositions/timestamp-profile/TimestampProfileSummary.svelte +++ b/web-common/src/components/data-graphic/compositions/timestamp-profile/TimestampProfileSummary.svelte @@ -23,6 +23,7 @@ TIME_GRAIN_DAY = "daily", TIME_GRAIN_WEEK = "weekly", TIME_GRAIN_MONTH = "monthly", + TIME_GRAIN_QUARTER = "quarterly", TIME_GRAIN_YEAR = "yearly", } diff --git a/web-common/src/features/dashboards/proto-state/fromProto.ts b/web-common/src/features/dashboards/proto-state/fromProto.ts index 6bf2d84fe86..f27745490db 100644 --- a/web-common/src/features/dashboards/proto-state/fromProto.ts +++ b/web-common/src/features/dashboards/proto-state/fromProto.ts @@ -149,6 +149,8 @@ function fromTimeGrainProto(timeGrain: TimeGrain): V1TimeGrain { return V1TimeGrain.TIME_GRAIN_WEEK; case TimeGrain.MONTH: return V1TimeGrain.TIME_GRAIN_MONTH; + case TimeGrain.QUARTER: + return V1TimeGrain.TIME_GRAIN_QUARTER; case TimeGrain.YEAR: return V1TimeGrain.TIME_GRAIN_YEAR; } diff --git a/web-common/src/features/dashboards/proto-state/toProto.ts b/web-common/src/features/dashboards/proto-state/toProto.ts index b038caf5714..dbcb2f1f55e 100644 --- a/web-common/src/features/dashboards/proto-state/toProto.ts +++ b/web-common/src/features/dashboards/proto-state/toProto.ts @@ -123,6 +123,8 @@ function toTimeGrainProto(timeGrain: V1TimeGrain) { return TimeGrainProto.WEEK; case V1TimeGrain.TIME_GRAIN_MONTH: return TimeGrainProto.MONTH; + case V1TimeGrain.TIME_GRAIN_QUARTER: + return TimeGrainProto.QUARTER; case V1TimeGrain.TIME_GRAIN_YEAR: return TimeGrainProto.YEAR; } diff --git a/web-common/src/features/dashboards/time-controls/time-range-utils.spec.disabled.ts b/web-common/src/features/dashboards/time-controls/time-range-utils.spec.disabled.ts index 7456dc451ea..1ca14da6c8b 100644 --- a/web-common/src/features/dashboards/time-controls/time-range-utils.spec.disabled.ts +++ b/web-common/src/features/dashboards/time-controls/time-range-utils.spec.disabled.ts @@ -1,40 +1,5 @@ import { V1TimeGrain } from "../../../runtime-client"; -import { getDefaultTimeGrain, getTimeGrainOptions } from "./time-range-utils"; - -describe("getTimeGrainOptions", () => { - it("should return an array of available time grains", () => { - const timeGrains = getTimeGrainOptions( - new Date("2020-03-01"), - new Date("2020-03-31") - ); - expect(timeGrains).toEqual([ - { - enabled: false, - timeGrain: V1TimeGrain.TIME_GRAIN_MINUTE, - }, - { - enabled: true, - timeGrain: V1TimeGrain.TIME_GRAIN_HOUR, - }, - { - enabled: true, - timeGrain: V1TimeGrain.TIME_GRAIN_DAY, - }, - { - enabled: true, - timeGrain: V1TimeGrain.TIME_GRAIN_WEEK, - }, - { - enabled: false, - timeGrain: V1TimeGrain.TIME_GRAIN_MONTH, - }, - { - enabled: false, - timeGrain: V1TimeGrain.TIME_GRAIN_YEAR, - }, - ]); - }); -}); +import { getDefaultTimeGrain } from "./time-range-utils"; describe("getDefaultTimeGrain", () => { it("should return the default time grain (for a 5 day time range)", () => { diff --git a/web-common/src/features/metrics-views/workspace/config-parameters/SmallestTimeGrainSelector.svelte b/web-common/src/features/metrics-views/workspace/config-parameters/SmallestTimeGrainSelector.svelte index e5b4a41341f..10cfc95e0ee 100644 --- a/web-common/src/features/metrics-views/workspace/config-parameters/SmallestTimeGrainSelector.svelte +++ b/web-common/src/features/metrics-views/workspace/config-parameters/SmallestTimeGrainSelector.svelte @@ -3,8 +3,8 @@ import Tooltip from "@rilldata/web-common/components/tooltip/Tooltip.svelte"; import TooltipContent from "@rilldata/web-common/components/tooltip/TooltipContent.svelte"; import { TIME_GRAIN } from "@rilldata/web-common/lib/time/config"; - import { getTimeGrainOptions } from "@rilldata/web-common/lib/time/grains"; - import type { TimeGrainOption } from "@rilldata/web-common/lib/time/types"; + import { getAllowedTimeGrains } from "@rilldata/web-common/lib/time/grains"; + import type { TimeGrain } from "@rilldata/web-common/lib/time/types"; import { createQueryServiceColumnTimeRange, V1Model, @@ -54,29 +54,27 @@ }; } - let selectableTimeGrains: TimeGrainOption[] = []; + let allowedTimeGrains: TimeGrain[] = []; let maxTimeGrainPossibleIndex = 0; + + const timeGrains = Object.values(TIME_GRAIN); + $: if (allTimeRange) { - // FIXME: we should be deprecating this getTimeGrainOptions in favor of getAllowedTimeGrains. - selectableTimeGrains = getTimeGrainOptions( + allowedTimeGrains = getAllowedTimeGrains( allTimeRange.start, allTimeRange.end ); - maxTimeGrainPossibleIndex = - selectableTimeGrains.length - - 1 - - selectableTimeGrains - .slice() - .reverse() - .findIndex((grain) => grain.enabled); + const maxTimeGrainPossible = + allowedTimeGrains[allowedTimeGrains.length - 1]; + maxTimeGrainPossibleIndex = timeGrains.findIndex( + (grain) => grain.label === maxTimeGrainPossible.label + ); } $: isValidTimeGrain = defaultTimeGrainValue === "__DEFAULT_VALUE__" || - Object.values(TIME_GRAIN).some( - (timeGrain) => timeGrain.label === defaultTimeGrainValue - ); + timeGrains.some((timeGrain) => timeGrain.label === defaultTimeGrainValue); $: level = isValidTimeGrain ? "" : "error"; @@ -102,7 +100,7 @@ }, ] : []), - ...(selectableTimeGrains.map((grain, i) => { + ...(timeGrains.map((grain, i) => { const isGrainPossible = i <= maxTimeGrainPossibleIndex; return { divider: false, diff --git a/web-common/src/lib/time/config.ts b/web-common/src/lib/time/config.ts index 76ca9a40a76..da2b416905d 100644 --- a/web-common/src/lib/time/config.ts +++ b/web-common/src/lib/time/config.ts @@ -384,6 +384,15 @@ export const TIME_GRAIN: Record = { month: "short", }, }, + TIME_GRAIN_QUARTER: { + grain: V1TimeGrain.TIME_GRAIN_QUARTER, + label: "quarter", + duration: Period.QUARTER, + formatDate: { + year: "numeric", + month: "short", + }, + }, TIME_GRAIN_YEAR: { grain: V1TimeGrain.TIME_GRAIN_YEAR, label: "year", diff --git a/web-common/src/lib/time/grains/index.spec.ts b/web-common/src/lib/time/grains/index.spec.ts index b484e031661..1965874cd95 100644 --- a/web-common/src/lib/time/grains/index.spec.ts +++ b/web-common/src/lib/time/grains/index.spec.ts @@ -11,11 +11,9 @@ import { Period, TimeGrain } from "../types"; const allowedGrainTests = [ { - test: "should return TIME_GRAIN_MINUTE for < 2 hours", + test: "should return TIME_GRAIN_MINUTE for < 1 hour", start: new Date(0), - end: new Date( - 2 * durationToMillis(TIME_GRAIN.TIME_GRAIN_HOUR.duration) - 1 - ), + end: new Date(durationToMillis(TIME_GRAIN.TIME_GRAIN_HOUR.duration) - 1), expected: [TIME_GRAIN.TIME_GRAIN_MINUTE], }, { @@ -32,18 +30,11 @@ const allowedGrainTests = [ end: new Date(durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration) - 1), expected: [TIME_GRAIN.TIME_GRAIN_HOUR], }, + { - test: "should return TIME_GRAIN_HOUR for 24 hours", - start: new Date(0), - end: new Date(durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration)), - expected: [TIME_GRAIN.TIME_GRAIN_HOUR], - }, - { - test: "should return TIME_GRAIN_HOUR and TIME_GRAIN_DAY if otherwise < 14 days", + test: "should return TIME_GRAIN_HOUR and TIME_GRAIN_DAY if otherwise < 7 days", start: new Date(0), - end: new Date( - durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration) * 14 - 1 - ), + end: new Date(durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration) * 7 - 1), expected: [TIME_GRAIN.TIME_GRAIN_HOUR, TIME_GRAIN.TIME_GRAIN_DAY], }, { @@ -59,27 +50,30 @@ const allowedGrainTests = [ ], }, { - test: "should return TIME_GRAIN_DAY, TIME_GRAIN_WEEK, and TIME_GRAIN_MONTH if otherwise < 90 days", + test: "should return TIME_GRAIN_WEEK, TIME_GRAIN_MONTH, and TIME_GRAIN_YEAR if otherwise < 90 days", start: new Date(0), end: new Date( - 3 * durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration) * 30 - 1 + durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration) * 3 * 30 - 1 ), - expected: [TIME_GRAIN.TIME_GRAIN_DAY, TIME_GRAIN.TIME_GRAIN_WEEK], + expected: [ + TIME_GRAIN.TIME_GRAIN_DAY, + TIME_GRAIN.TIME_GRAIN_WEEK, + TIME_GRAIN.TIME_GRAIN_MONTH, + ], }, { - test: "should return TIME_GRAIN_WEEK, TIME_GRAIN_MONTH, and TIME_GRAIN_YEAR if otherwise < 3 years", + test: "should return TIME_GRAIN_WEEK, TIME_GRAIN_MONTH, TIME_GRAIN_QUARTER and TIME_GRAIN_YEAR if otherwise < 1 year", start: new Date(0), - end: new Date( - 3 * durationToMillis(TIME_GRAIN.TIME_GRAIN_YEAR.duration) - 1 - ), + end: new Date(durationToMillis(TIME_GRAIN.TIME_GRAIN_YEAR.duration) - 1), expected: [ TIME_GRAIN.TIME_GRAIN_DAY, TIME_GRAIN.TIME_GRAIN_WEEK, TIME_GRAIN.TIME_GRAIN_MONTH, + TIME_GRAIN.TIME_GRAIN_QUARTER, ], }, { - test: "should return TIME_GRAIN_MONTH, TIME_GRAIN_YEAR, and TIME_GRAIN_QUARTER if otherwise < 10 years", + test: "should return TIME_GRAIN_WEEK, TIME_GRAIN_MONTH, TIME_GRAIN_QUARTER and TIME_GRAIN_YEAR if otherwise < 10 years", start: new Date(0), end: new Date( 10 * durationToMillis(TIME_GRAIN.TIME_GRAIN_YEAR.duration) - 1 @@ -87,6 +81,7 @@ const allowedGrainTests = [ expected: [ TIME_GRAIN.TIME_GRAIN_WEEK, TIME_GRAIN.TIME_GRAIN_MONTH, + TIME_GRAIN.TIME_GRAIN_QUARTER, TIME_GRAIN.TIME_GRAIN_YEAR, ], }, diff --git a/web-common/src/lib/time/grains/index.ts b/web-common/src/lib/time/grains/index.ts index bd6574c2a94..bdec49091fb 100644 --- a/web-common/src/lib/time/grains/index.ts +++ b/web-common/src/lib/time/grains/index.ts @@ -7,7 +7,7 @@ import { V1TimeGrain } from "@rilldata/web-common/runtime-client"; import { Duration } from "luxon"; import { TIME_GRAIN } from "../config"; import { getTimeWidth } from "../transforms"; -import type { TimeGrain, TimeGrainOption } from "../types"; +import type { TimeGrain } from "../types"; export function unitToTimeGrain(unit: string): V1TimeGrain { return ( @@ -20,30 +20,6 @@ export function durationToMillis(duration: string): number { return Duration.fromISO(duration).toMillis(); } -// FIXME: what is the difference between this and getAllowedTimeGrains? -// It appears that we're using this instead of getAllowedTimeGrains. -// I think we should deprecate this function as soon as possible. -export function getTimeGrainOptions(start: Date, end: Date): TimeGrainOption[] { - const timeGrains: TimeGrainOption[] = []; - const timeRangeDurationMs = getTimeWidth(start, end); - - for (const timeGrain of Object.values(TIME_GRAIN)) { - // only show a time grain if it results in a reasonable number of points on the line chart - const MINIMUM_POINTS_ON_LINE_CHART = 3; - const MAXIMUM_POINTS_ON_LINE_CHART = 2500; - const timeGrainDurationMs = durationToMillis(timeGrain.duration); - const pointsOnLineChart = timeRangeDurationMs / timeGrainDurationMs; - const showTimeGrain = - pointsOnLineChart >= MINIMUM_POINTS_ON_LINE_CHART && - pointsOnLineChart <= MAXIMUM_POINTS_ON_LINE_CHART; - timeGrains.push({ - ...timeGrain, - enabled: showTimeGrain, - }); - } - return timeGrains; -} - // Get the default grain for a given time range. export function getDefaultTimeGrain(start: Date, end: Date): TimeGrain { const timeRangeDurationMs = end.getTime() - start.getTime(); @@ -72,14 +48,11 @@ export function getDefaultTimeGrain(start: Date, end: Date): TimeGrain { } } -// Return time grains that are allowed for a given time range. Note that -// this function is similar to getTimeGrainOptions. We should deprecate getTimeGrainOptions -// in favor of this logic. +// Return time grains that are allowed for a given time range. export function getAllowedTimeGrains(start: Date, end: Date): TimeGrain[] { const timeRangeDurationMs = getTimeWidth(start, end); if ( - timeRangeDurationMs < - 2 * durationToMillis(TIME_GRAIN.TIME_GRAIN_HOUR.duration) + timeRangeDurationMs < durationToMillis(TIME_GRAIN.TIME_GRAIN_HOUR.duration) ) { return [TIME_GRAIN.TIME_GRAIN_MINUTE]; } else if ( @@ -88,17 +61,17 @@ export function getAllowedTimeGrains(start: Date, end: Date): TimeGrain[] { ) { return [TIME_GRAIN.TIME_GRAIN_MINUTE, TIME_GRAIN.TIME_GRAIN_HOUR]; } else if ( - timeRangeDurationMs <= durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration) + timeRangeDurationMs < durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration) ) { return [TIME_GRAIN.TIME_GRAIN_HOUR]; } else if ( - timeRangeDurationMs <= - 14 * durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration) + timeRangeDurationMs < + 7 * durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration) ) { return [TIME_GRAIN.TIME_GRAIN_HOUR, TIME_GRAIN.TIME_GRAIN_DAY]; } else if ( timeRangeDurationMs < - durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration) * 30 + 30 * durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration) ) { return [ TIME_GRAIN.TIME_GRAIN_HOUR, @@ -107,22 +80,27 @@ export function getAllowedTimeGrains(start: Date, end: Date): TimeGrain[] { ]; } else if ( timeRangeDurationMs < - 3 * durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration) * 30 + 3 * 30 * durationToMillis(TIME_GRAIN.TIME_GRAIN_DAY.duration) ) { - return [TIME_GRAIN.TIME_GRAIN_DAY, TIME_GRAIN.TIME_GRAIN_WEEK]; + return [ + TIME_GRAIN.TIME_GRAIN_DAY, + TIME_GRAIN.TIME_GRAIN_WEEK, + TIME_GRAIN.TIME_GRAIN_MONTH, + ]; } else if ( - timeRangeDurationMs < - 3 * durationToMillis(TIME_GRAIN.TIME_GRAIN_YEAR.duration) + timeRangeDurationMs < durationToMillis(TIME_GRAIN.TIME_GRAIN_YEAR.duration) ) { return [ TIME_GRAIN.TIME_GRAIN_DAY, TIME_GRAIN.TIME_GRAIN_WEEK, TIME_GRAIN.TIME_GRAIN_MONTH, + TIME_GRAIN.TIME_GRAIN_QUARTER, ]; } else { return [ TIME_GRAIN.TIME_GRAIN_WEEK, TIME_GRAIN.TIME_GRAIN_MONTH, + TIME_GRAIN.TIME_GRAIN_QUARTER, TIME_GRAIN.TIME_GRAIN_YEAR, ]; } diff --git a/web-common/src/lib/time/types.ts b/web-common/src/lib/time/types.ts index 2caf53882bd..7eb10d4630f 100644 --- a/web-common/src/lib/time/types.ts +++ b/web-common/src/lib/time/types.ts @@ -163,11 +163,6 @@ export interface TimeGrain { formatDate: Intl.DateTimeFormatOptions; } -// FIXME: is this needed? -export interface TimeGrainOption extends TimeGrain { - enabled: boolean; -} - // limit the set of available time grains to those supported // by th dashboard. export type AvailableTimeGrain = Exclude<