Skip to content

Commit

Permalink
Add timezone support to dashboard (#2775)
Browse files Browse the repository at this point in the history
* Basic timezone support added

* Use abbreviation mapping

* Use abbreviation for selector label

* Selector as per mocks

* Align comparisons selector

* Rename vars and text

* Decreasing x padding for icon menu item

* add timezone to quickstart template

* Add available time zones key

* Update time zone selector tooltip

* Add selectedTimezone to proto, label changes

* Timezone support for chart elements

* Convert custom calendar dates based on zone

* Clean up PR

* Fix broken unit tests

* More test fixes

* All time charts edge fix

* Save timezone preferene localy

* Fix all tests

* fix: wait for menu to close in e2e tests

* Move out user preference store

* Remove unused imports

---------

Co-authored-by: Speros Kokenes <[email protected]>
  • Loading branch information
djbarnwal and skokenes authored Jul 31, 2023
1 parent aecd263 commit a3afec1
Show file tree
Hide file tree
Showing 33 changed files with 800 additions and 97 deletions.
3 changes: 3 additions & 0 deletions proto/rill/ui/v1/dashboard.proto
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ message DashboardState {
optional bool all_dimensions_visible = 11;

optional bool show_percent_of_total = 12;

// Selected timezone for the dashboard
optional string selected_timezone = 13;
}

message DashboardTimeRange {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
import { createShiftClickAction } from "@rilldata/web-common/lib/actions/shift-click-action";
import {
datePortion,
removeTimezoneOffset,
timePortion,
} from "@rilldata/web-common/lib/formatters";
import { removeLocalTimezoneOffset } from "@rilldata/web-common/lib/time/timezone";
const { shiftClickAction } = createShiftClickAction();
Expand All @@ -26,7 +26,7 @@
export let align: "left" | "right" = "left";
let valueWithoutOffset = undefined;
$: if (value instanceof Date)
valueWithoutOffset = removeTimezoneOffset(value);
valueWithoutOffset = removeLocalTimezoneOffset(value);
</script>

<Tooltip alignment={align == "left" ? "start" : "end"} distance={8}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
import { notifications } from "@rilldata/web-common/components/notifications";
import Tooltip from "@rilldata/web-common/components/tooltip/Tooltip.svelte";
import { createShiftClickAction } from "@rilldata/web-common/lib/actions/shift-click-action";
import { removeTimezoneOffset } from "@rilldata/web-common/lib/formatters";
import { guidGenerator } from "@rilldata/web-common/lib/guid";
import { removeLocalTimezoneOffset } from "@rilldata/web-common/lib/time/timezone";
import type { V1TimeGrain } from "@rilldata/web-common/runtime-client";
import { bisector, extent, max, min } from "d3-array";
import type { ScaleLinear } from "d3-scale";
Expand Down Expand Up @@ -306,7 +306,7 @@
use:scrollAction
use:shiftClickAction
on:shift-click={async () => {
const exportedValue = `TIMESTAMP '${removeTimezoneOffset(
const exportedValue = `TIMESTAMP '${removeLocalTimezoneOffset(
nearestPoint[xAccessor]
).toISOString()}'`;
await navigator.clipboard.writeText(exportedValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import {
datePortion,
formatInteger,
removeTimezoneOffset,
timePortion,
} from "@rilldata/web-common/lib/formatters";
import type { ScaleLinear } from "d3-scale";
import { removeLocalTimezoneOffset } from "@rilldata/web-common/lib/time/timezone";
import { getContext } from "svelte";
import type { Writable } from "svelte/store";
import { fly } from "svelte/transition";
Expand All @@ -25,7 +25,7 @@
export let point;
export let xAccessor: string;
export let yAccessor: string;
$: xLabel = removeTimezoneOffset(point[xAccessor]);
$: xLabel = removeLocalTimezoneOffset(point[xAccessor]);
</script>

<g>
Expand Down
1 change: 1 addition & 0 deletions web-common/src/components/data-graphic/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export function getTicks(
isDate: boolean
) {
const tickCount = ~~(axisLength / (xOrY === "x" ? 150 : 50));

let ticks = scale.ticks(tickCount);

if (ticks.length <= 1) {
Expand Down
14 changes: 14 additions & 0 deletions web-common/src/components/icons/Globe.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8 1.33301C11.6819 1.33301 14.6667 4.31778 14.6667 7.99967C14.6667 11.6816 11.6819 14.6663 8 14.6663C4.3181 14.6663 1.33334 11.6816 1.33334 7.99967C1.33334 4.31778 4.3181 1.33301 8 1.33301ZM8.5 13.5451C8.88509 13.3635 9.31524 12.9614 9.71651 12.2318C10.2091 11.3363 10.562 10.0932 10.647 8.66634H8.5V13.5451ZM7.5 13.5451C7.11491 13.3635 6.68477 12.9614 6.28349 12.2318C5.79095 11.3363 5.43799 10.0932 5.35304 8.66634H7.5V13.5451ZM8.5 7.66634H10.6618C10.6156 6.09999 10.247 4.73216 9.71651 3.76755C9.31524 3.03796 8.88509 2.63587 8.5 2.4542V7.66634ZM7.5 2.4542V7.66634H5.33823C5.38438 6.09999 5.75296 4.73216 6.28349 3.76755C6.68477 3.03796 7.11491 2.63587 7.5 2.4542ZM10.3115 13.175C11.0493 12.0843 11.5494 10.4825 11.6486 8.66634H13.6279C13.3915 10.6836 12.0955 12.377 10.3115 13.175ZM11.6622 7.66634H13.657C13.5316 5.5034 12.193 3.66597 10.3115 2.82436C11.0941 3.98131 11.6093 5.71333 11.6622 7.66634ZM5.68847 2.82436C4.90586 3.98131 4.39073 5.71333 4.33784 7.66634H2.34298C2.46844 5.5034 3.80704 3.66597 5.68847 2.82436ZM5.68847 13.175C3.90448 12.377 2.60855 10.6836 2.37214 8.66634H4.35144C4.45062 10.4825 4.95068 12.0843 5.68847 13.175Z"
fill="#4B5563"
/>
</svg>
2 changes: 1 addition & 1 deletion web-common/src/components/menu/core/MenuItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
class="
text-left
py-1
px-3
{icon ? 'px-2' : 'px-3'}
focus:outline-none
active:outline-none
grid
Expand Down
8 changes: 8 additions & 0 deletions web-common/src/features/dashboards/dashboard-stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export interface MetricsExplorerEntity {
// user selected time range
selectedTimeRange?: DashboardTimeControls;
selectedComparisonTimeRange?: DashboardTimeControls;
// user selected timezone
selectedTimezone?: string;
// flag to show/hide comparison based on user preference
showComparison?: boolean;

Expand Down Expand Up @@ -322,6 +324,12 @@ const metricViewReducers = {
});
},

setTimeZone(name: string, zoneIANA: string) {
updateMetricsExplorerByName(name, (metricsExplorer) => {
metricsExplorer.selectedTimezone = zoneIANA;
});
},

displayComparison(name: string, showComparison: boolean) {
updateMetricsExplorerByName(name, (metricsExplorer) => {
metricsExplorer.showComparison = showComparison;
Expand Down
4 changes: 4 additions & 0 deletions web-common/src/features/dashboards/proto-state/fromProto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export function getDashboardStateFromProto(
entity.selectedDimensionName = dashboard.selectedDimension;
}

if (dashboard.selectedTimezone) {
entity.selectedTimezone = dashboard.selectedTimezone;
}

if (dashboard.allMeasuresVisible) {
entity.allMeasuresVisible = true;
entity.visibleMeasureKeys = new Set(
Expand Down
4 changes: 3 additions & 1 deletion web-common/src/features/dashboards/proto-state/toProto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ export function getProtoFromDashboardState(
);
}
state.showComparison = metrics.showComparison;

if (metrics.selectedTimezone) {
state.selectedTimezone = metrics.selectedTimezone;
}
if (metrics.leaderboardMeasureName) {
state.leaderboardMeasure = metrics.leaderboardMeasureName;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
shiftToUTC,
} from "@rilldata/web-common/components/date-picker/util";
import { getOffset } from "@rilldata/web-common/lib/time/transforms";
import { addZoneOffset } from "@rilldata/web-common/lib/time/timezone";
export let minTimeGrain: V1TimeGrain;
export let boundaryStart: Date;
export let boundaryEnd: Date;
export let defaultDate: DashboardTimeControls;
export let zone: string;
const dispatch = createEventDispatcher();
Expand Down Expand Up @@ -106,8 +108,8 @@
).toISOString();
dispatch("apply", {
startDate,
endDate,
startDate: addZoneOffset(new Date(startDate), zone).toISOString(),
endDate: addZoneOffset(new Date(endDate), zone).toISOString(),
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
export let active = false;
export let disabled = false;
export let label: string | undefined = undefined;
</script>

<button
{disabled}
aria-label={label}
class:bg-gray-200={active}
class="px-3 py-2 rounded grid gap-x-2 {!disabled
? 'hover:bg-gray-200'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ This component needs to do the following:
export let boundaryStart: Date;
export let boundaryEnd: Date;
export let minTimeGrain: V1TimeGrain;
export let zone: string;
export let showComparison = true;
export let selectedComparison;
Expand Down Expand Up @@ -115,7 +116,12 @@ This component needs to do the following:
: NO_COMPARISON_LABEL;
</script>

<WithTogglableFloatingElement let:toggleFloatingElement let:active>
<WithTogglableFloatingElement
distance={8}
alignment="start"
let:toggleFloatingElement
let:active
>
<Tooltip distance={8} suppress={active}>
<SelectorButton
{active}
Expand Down Expand Up @@ -190,6 +196,7 @@ This component needs to do the following:
{boundaryEnd}
defaultDate={selectedComparison}
{minTimeGrain}
{zone}
on:apply={(e) => {
onSelectCustomComparisonRange(
e.detail.startDate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
getComparisonRange,
getTimeComparisonParametersForComponent,
} from "@rilldata/web-common/lib/time/comparisons";
import { DEFAULT_TIME_RANGES } from "@rilldata/web-common/lib/time/config";
import {
DEFAULT_TIMEZONES,
DEFAULT_TIME_RANGES,
} from "@rilldata/web-common/lib/time/config";
import {
checkValidTimeGrain,
getDefaultTimeGrain,
Expand All @@ -36,19 +39,24 @@
import { useQueryClient } from "@tanstack/svelte-query";
import { runtime } from "../../../runtime-client/runtime-store";
import { metricsExplorerStore, useDashboardStore } from "../dashboard-stores";
import { initLocalUserPreferenceStore } from "../user-preferences";
import NoTimeDimensionCTA from "./NoTimeDimensionCTA.svelte";
import TimeComparisonSelector from "./TimeComparisonSelector.svelte";
import TimeGrainSelector from "./TimeGrainSelector.svelte";
import TimeRangeSelector from "./TimeRangeSelector.svelte";
import TimeZoneSelector from "./TimeZoneSelector.svelte";
export let metricViewName: string;
const localUserPreferences = initLocalUserPreferenceStore(metricViewName);
const queryClient = useQueryClient();
$: dashboardStore = useDashboardStore(metricViewName);
let baseTimeRange: TimeRange;
let defaultTimeRange: TimeRangeType;
let minTimeGrain: V1TimeGrain;
let availableTimeZones: string[] = [];
let metricsViewQuery;
$: if ($runtime.instanceId) {
Expand Down Expand Up @@ -87,6 +95,15 @@
minTimeGrain =
$metricsViewQuery.data.entry.metricsView?.smallestTimeGrain ||
V1TimeGrain.TIME_GRAIN_UNSPECIFIED;
availableTimeZones =
$metricsViewQuery?.data?.entry?.metricsView?.availableTimeZones;
// For legacy dashboards, we need to set the available time
// zones to the default if they are not defined.
if (!availableTimeZones?.length) {
availableTimeZones = DEFAULT_TIMEZONES;
}
}
$: allTimeRange = $allTimeRangeQuery?.data as TimeRange;
$: isDashboardDefined = $dashboardStore !== undefined;
Expand All @@ -103,10 +120,14 @@
}
function setDefaultTimeControls(allTimeRange: DashboardTimeControls) {
const defaultIANA = $localUserPreferences.timeZone;
metricsExplorerStore.setTimeZone(metricViewName, defaultIANA);
baseTimeRange = convertTimeRangePreset(
defaultTimeRange,
allTimeRange.start,
allTimeRange.end
allTimeRange.end,
defaultIANA
) || { ...allTimeRange, end: new Date(allTimeRange.end.getTime() + 1) };
const timeGrain = getDefaultTimeGrain(
Expand Down Expand Up @@ -138,7 +159,8 @@
convertTimeRangePreset(
$dashboardStore?.selectedTimeRange.name,
allTimeRange.start,
allTimeRange.end
allTimeRange.end,
$dashboardStore?.selectedTimezone
) || allTimeRange;
}
Expand Down Expand Up @@ -186,6 +208,11 @@
);
}
function onSelectTimeZone(timeZone: string) {
metricsExplorerStore.setTimeZone(metricViewName, timeZone);
localUserPreferences.set({ timeZone });
}
function onSelectComparisonRange(
name: TimeComparisonOption,
start: Date,
Expand Down Expand Up @@ -325,6 +352,12 @@
on:select-time-range={(e) =>
onSelectTimeRange(e.detail.name, e.detail.start, e.detail.end)}
/>
<TimeZoneSelector
on:select-time-zone={(e) => onSelectTimeZone(e.detail.timeZone)}
{metricViewName}
{availableTimeZones}
now={allTimeRange?.end}
/>
<TimeComparisonSelector
on:select-comparison={(e) => {
onSelectComparisonRange(e.detail.name, e.detail.start, e.detail.end);
Expand All @@ -336,6 +369,7 @@
currentEnd={$dashboardStore?.selectedTimeRange?.end}
boundaryStart={allTimeRange.start}
boundaryEnd={allTimeRange.end}
zone={$dashboardStore?.selectedTimezone}
showComparison={$dashboardStore?.showComparison}
selectedComparison={$dashboardStore?.selectedComparisonTimeRange}
comparisonOptions={availableComparisons}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
boundaryStart,
boundaryEnd,
LATEST_WINDOW_TIME_RANGES,
minTimeGrain
minTimeGrain,
$dashboardStore?.selectedTimezone
);
}
Expand All @@ -61,7 +62,8 @@
boundaryStart,
boundaryEnd,
PERIOD_TO_DATE_RANGES,
minTimeGrain
minTimeGrain,
$dashboardStore?.selectedTimezone
);
}
Expand Down Expand Up @@ -156,7 +158,8 @@
{prettyFormatTimeRange(
$dashboardStore?.selectedTimeRange?.start,
$dashboardStore?.selectedTimeRange?.end,
$dashboardStore?.selectedTimeRange?.name
$dashboardStore?.selectedTimeRange?.name,
$dashboardStore?.selectedTimezone
)}
</span>
</div>
Expand Down Expand Up @@ -233,6 +236,7 @@
{boundaryStart}
{boundaryEnd}
{minTimeGrain}
zone={$dashboardStore?.selectedTimezone}
defaultDate={selectedRange}
on:apply={(e) =>
onSelectCustomTimeRange(
Expand Down
Loading

1 comment on commit a3afec1

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.