vmui: fix handling invalid timezone (#5758)

* vmui: fix handling invalid timezone (#5732)

* vmui: switch browser timezone flag to isValid

---------

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Yury Molodov 2024-02-06 21:47:30 +01:00 committed by Aliaksandr Valialkin
parent 293617028d
commit 0b5f5d456c
No known key found for this signature in database
GPG key ID: 52C003EE2BCDB9EB
7 changed files with 60 additions and 11 deletions

View file

@ -1,15 +1,15 @@
import React, { FC, useMemo, useRef, useState } from "preact/compat"; import React, { FC, useMemo, useRef, useState } from "preact/compat";
import { getTimezoneList, getUTCByTimezone } from "../../../../utils/time"; import { getBrowserTimezone, getTimezoneList, getUTCByTimezone } from "../../../../utils/time";
import { ArrowDropDownIcon } from "../../../Main/Icons"; import { ArrowDropDownIcon } from "../../../Main/Icons";
import classNames from "classnames"; import classNames from "classnames";
import Popper from "../../../Main/Popper/Popper"; import Popper from "../../../Main/Popper/Popper";
import Accordion from "../../../Main/Accordion/Accordion"; import Accordion from "../../../Main/Accordion/Accordion";
import dayjs from "dayjs";
import TextField from "../../../Main/TextField/TextField"; import TextField from "../../../Main/TextField/TextField";
import { Timezone } from "../../../../types"; import { Timezone } from "../../../../types";
import "./style.scss"; import "./style.scss";
import useDeviceDetect from "../../../../hooks/useDeviceDetect"; import useDeviceDetect from "../../../../hooks/useDeviceDetect";
import useBoolean from "../../../../hooks/useBoolean"; import useBoolean from "../../../../hooks/useBoolean";
import WarningTimezone from "./WarningTimezone";
interface TimezonesProps { interface TimezonesProps {
timezoneState: string; timezoneState: string;
@ -18,9 +18,12 @@ interface TimezonesProps {
} }
interface PinnedTimezone extends Timezone { interface PinnedTimezone extends Timezone {
title: string title: string;
isInvalid?: boolean;
} }
const browserTimezone = getBrowserTimezone();
const Timezones: FC<TimezonesProps> = ({ timezoneState, defaultTimezone, onChange }) => { const Timezones: FC<TimezonesProps> = ({ timezoneState, defaultTimezone, onChange }) => {
const { isMobile } = useDeviceDetect(); const { isMobile } = useDeviceDetect();
const timezones = getTimezoneList(); const timezones = getTimezoneList();
@ -41,9 +44,10 @@ const Timezones: FC<TimezonesProps> = ({ timezoneState, defaultTimezone, onChang
utc: defaultTimezone ? getUTCByTimezone(defaultTimezone) : "UTC" utc: defaultTimezone ? getUTCByTimezone(defaultTimezone) : "UTC"
}, },
{ {
title: `Browser Time (${dayjs.tz.guess()})`, title: browserTimezone.title,
region: dayjs.tz.guess(), region: browserTimezone.region,
utc: getUTCByTimezone(dayjs.tz.guess()) utc: getUTCByTimezone(browserTimezone.region),
isInvalid: !browserTimezone.isValid
}, },
{ {
title: "UTC (Coordinated Universal Time)", title: "UTC (Coordinated Universal Time)",
@ -132,7 +136,7 @@ const Timezones: FC<TimezonesProps> = ({ timezoneState, defaultTimezone, onChang
className="vm-timezones-item vm-timezones-list-group-options__item" className="vm-timezones-item vm-timezones-list-group-options__item"
onClick={createHandlerSetTimezone(t)} onClick={createHandlerSetTimezone(t)}
> >
<div className="vm-timezones-item__title">{t.title}</div> <div className="vm-timezones-item__title">{t.title}{t.isInvalid && <WarningTimezone/>}</div>
<div className="vm-timezones-item__utc">{t.utc}</div> <div className="vm-timezones-item__utc">{t.utc}</div>
</div> </div>
))} ))}

View file

@ -0,0 +1,16 @@
import React, { FC } from "preact/compat";
import Tooltip from "../../../Main/Tooltip/Tooltip";
import { WarningIcon } from "../../../Main/Icons";
const waringText = "Browser timezone is not recognized, supported, or could not be determined.";
const WarningTimezone: FC = () => {
return (
<Tooltip title={waringText}>
<WarningIcon/>
</Tooltip>
);
};
export default WarningTimezone;

View file

@ -16,7 +16,15 @@
} }
&__title { &__title {
display: flex;
align-items: center;
gap: $padding-small;
text-transform: capitalize; text-transform: capitalize;
svg {
width: 14px;
color: $color-warning;
}
} }
&__utc { &__utc {

View file

@ -4,6 +4,7 @@ import { useAppState } from "../state/common/StateContext";
import { useTimeDispatch } from "../state/time/TimeStateContext"; import { useTimeDispatch } from "../state/time/TimeStateContext";
import { getFromStorage } from "../utils/storage"; import { getFromStorage } from "../utils/storage";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { getBrowserTimezone } from "../utils/time";
const disabledDefaultTimezone = Boolean(getFromStorage("DISABLED_DEFAULT_TIMEZONE")); const disabledDefaultTimezone = Boolean(getFromStorage("DISABLED_DEFAULT_TIMEZONE"));
@ -15,7 +16,7 @@ const useFetchDefaultTimezone = () => {
const [error, setError] = useState<ErrorTypes | string>(""); const [error, setError] = useState<ErrorTypes | string>("");
const setTimezone = (timezoneStr: string) => { const setTimezone = (timezoneStr: string) => {
const timezone = timezoneStr.toLowerCase() === "local" ? dayjs.tz.guess() : timezoneStr; const timezone = timezoneStr.toLowerCase() === "local" ? getBrowserTimezone().region : timezoneStr;
try { try {
dayjs().tz(timezone).isValid(); dayjs().tz(timezone).isValid();
timeDispatch({ type: "SET_DEFAULT_TIMEZONE", payload: timezone }); timeDispatch({ type: "SET_DEFAULT_TIMEZONE", payload: timezone });

View file

@ -6,10 +6,10 @@ import {
getDurationFromPeriod, getDurationFromPeriod,
getTimeperiodForDuration, getTimeperiodForDuration,
getRelativeTime, getRelativeTime,
setTimezone setTimezone,
getBrowserTimezone
} from "../../utils/time"; } from "../../utils/time";
import { getQueryStringValue } from "../../utils/query-string"; import { getQueryStringValue } from "../../utils/query-string";
import dayjs from "dayjs";
import { getFromStorage, saveToStorage } from "../../utils/storage"; import { getFromStorage, saveToStorage } from "../../utils/storage";
export interface TimeState { export interface TimeState {
@ -29,7 +29,7 @@ export type TimeAction =
| { type: "SET_TIMEZONE", payload: string } | { type: "SET_TIMEZONE", payload: string }
| { type: "SET_DEFAULT_TIMEZONE", payload: string } | { type: "SET_DEFAULT_TIMEZONE", payload: string }
const timezone = getFromStorage("TIMEZONE") as string || dayjs.tz.guess(); const timezone = getFromStorage("TIMEZONE") as string || getBrowserTimezone().region;
setTimezone(timezone); setTimezone(timezone);
const defaultDuration = getQueryStringValue("g0.range_input") as string; const defaultDuration = getQueryStringValue("g0.range_input") as string;

View file

@ -227,3 +227,22 @@ export const getTimezoneList = (search = "") => {
export const setTimezone = (timezone: string) => { export const setTimezone = (timezone: string) => {
dayjs.tz.setDefault(timezone); dayjs.tz.setDefault(timezone);
}; };
const isValidTimezone = (timezone: string) => {
try {
dayjs().tz(timezone);
return true;
} catch (e) {
return false;
}
};
export const getBrowserTimezone = () => {
const timezone = dayjs.tz.guess();
const isValid = isValidTimezone(timezone);
return {
isValid,
title: isValid ? `Browser Time (${timezone})` : "Browser timezone (UTC)",
region: isValid ? timezone : "UTC",
};
};

View file

@ -35,6 +35,7 @@ The sandbox cluster installation is running under the constant load generated by
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly propagate [label filters](https://docs.victoriametrics.com/keyconcepts/#filtering) from multiple arguments passed to [aggregate functions](https://docs.victoriametrics.com/metricsql/#aggregate-functions). For example, `sum({job="foo"}, {job="bar"}) by (job) + a` was improperly optimized to `sum({job="foo"}, {job="bar"}) by (job) + a{job="foo"}` before being executed. This could lead to unexpected results. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5604). * BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly propagate [label filters](https://docs.victoriametrics.com/keyconcepts/#filtering) from multiple arguments passed to [aggregate functions](https://docs.victoriametrics.com/metricsql/#aggregate-functions). For example, `sum({job="foo"}, {job="bar"}) by (job) + a` was improperly optimized to `sum({job="foo"}, {job="bar"}) by (job) + a{job="foo"}` before being executed. This could lead to unexpected results. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5604).
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix the graph dragging for Firefox and Safari. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5764). * BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix the graph dragging for Firefox and Safari. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5764).
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix handling invalid timezone. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5732).
## [v1.97.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.97.1) ## [v1.97.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.97.1)