Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const BarHitsPlot: FC<Props> = ({ graphOptions, logHits, totalHits, data: _data,

const { xRange, setPlotScale } = usePlotScale({ period, setPeriod });
const { onReadyChart, isPanning } = useReadyChart(setPlotScale);
useZoomChart({ uPlotInst, xRange, setPlotScale });
useZoomChart({ uPlotInst, element: uPlotRef, xRange, setPlotScale });

const transformedData = useMemo(() => {
if (graphOptions.cumulative) return cumulativeMatrix(_data);
Expand Down Expand Up @@ -151,7 +151,7 @@ const BarHitsPlot: FC<Props> = ({ graphOptions, logHits, totalHits, data: _data,
ref={containerRef}
>
<div
className="vm-line-chart__u-plot"
className="vm-bar-hits-chart__u-plot"
ref={uPlotRef}
/>
{!isMobile && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
width: 100%;
height: 200px;

&__u-plot {
touch-action: pan-y;
}

&__wrapper {
display: flex;
flex-direction: column;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
padding-top: 0;

&_mobile {
max-height: 100%
max-height: 100%;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import TimeDurationSelector from "../TimeDurationSelector/TimeDurationSelector";
import { getAppModeEnable } from "../../../../utils/app-mode";
import { useTimeState } from "../../../../state/time/TimeStateContext";
import { ArrowDownIcon, ClockIcon } from "../../../Main/Icons";
import { ClockIcon } from "../../../Main/Icons";
import Button from "../../../Main/Button/Button";
import Popper from "../../../Main/Popper/Popper";
import Tooltip from "../../../Main/Tooltip/Tooltip";
Expand All @@ -24,6 +24,10 @@ import { useQueryState } from "../../../../state/query/QueryStateContext";
import { useTimePeriod } from "../../../../pages/QueryPage/hooks/useTimePeriod";
import { RelativeTimeOption } from "../../../../types";
import useEventListener from "../../../../hooks/useEventListener";
import {
formatDateTimeInputValue,
parseDateTimeInputValue
} from "../../../Main/DatePicker/DateTimeInput/utils";

type Props = {
onOpenSettings?: () => void;
Expand All @@ -38,8 +42,8 @@ export const TimeSelector: FC<Props> = ({ onOpenSettings }) => {
const documentSize = useWindowSize();
const displayFullDate = useMemo(() => documentSize.width > 1120, [documentSize]);

const [until, setUntil] = useState<string>();
const [from, setFrom] = useState<string>();
const [untilDraft, setUntilDraft] = useState("");
const [fromDraft, setFromDraft] = useState("");

const {
period: { end, start },
Expand Down Expand Up @@ -89,19 +93,20 @@ export const TimeSelector: FC<Props> = ({ onOpenSettings }) => {
const buttonRef = useRef<HTMLDivElement>(null);

const setTimeAndClosePicker = () => {
if (from && until) {
const nextPeriod = { from: from, to: until };
setPeriod({ nextPeriod });
}
const from = parseDateTimeInputValue(fromDraft);
const until = parseDateTimeInputValue(untilDraft);
if (!from || !until) return;

setPeriod({ nextPeriod: { from, to: until } });
handleCloseOptions();
};

const handleSetFrom = (start: bigint) => {
setFrom(nanosToIsoString(start));
setFromDraft(formatDateTimeInputValue(nanosToIsoString(start)));
};

const handleSetUntil = (end: bigint) => {
setUntil(nanosToIsoString(end));
setUntilDraft(formatDateTimeInputValue(nanosToIsoString(end)));
};

const handleOpenSettings = () => {
Expand Down Expand Up @@ -135,32 +140,18 @@ export const TimeSelector: FC<Props> = ({ onOpenSettings }) => {

return <>
<div ref={buttonRef}>
{isMobile ? (
<div
className="vm-mobile-option"
<Tooltip title={displayFullDate ? "Time range controls" : dateTitle}>
<Button
className={appModeEnable ? "" : "vm-header-button"}
variant="contained"
color="primary"
startIcon={<ClockIcon/>}
onClick={toggleOpenOptions}
aria-label="time range controls"
>
<span className="vm-mobile-option__icon"><ClockIcon/></span>
<div className="vm-mobile-option-text">
<span className="vm-mobile-option-text__label">Time range</span>
<span className="vm-mobile-option-text__value">{dateTitle}</span>
</div>
<span className="vm-mobile-option__arrow"><ArrowDownIcon/></span>
</div>
) : (
<Tooltip title={displayFullDate ? "Time range controls" : dateTitle}>
<Button
className={appModeEnable ? "" : "vm-header-button"}
variant="contained"
color="primary"
startIcon={<ClockIcon/>}
onClick={toggleOpenOptions}
aria-label="time range controls"
>
{displayFullDate && <span>{dateTitle}</span>}
</Button>
</Tooltip>
)}
{displayFullDate && <span>{dateTitle}</span>}
</Button>
</Tooltip>
</div>
<Popper
open={openOptions}
Expand Down Expand Up @@ -198,19 +189,19 @@ export const TimeSelector: FC<Props> = ({ onOpenSettings }) => {
})}
>
<DateTimeInput
value={from}
value={fromDraft}
label="From:"
pickerLabel="Date From"
pickerRef={fromPickerRef}
onChange={setFrom}
onChange={setFromDraft}
onEnter={setTimeAndClosePicker}
/>
<DateTimeInput
value={until}
value={untilDraft}
label="To:"
pickerLabel="Date To"
pickerRef={untilPickerRef}
onChange={setUntil}
onChange={setUntilDraft}
onEnter={setTimeAndClosePicker}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,24 @@
width: 100%;
max-height: calc(($vh * 100) - 70px);
overflow: auto;
padding-bottom: calc($padding-global * 5);
}

&_mobile &-left {
border-right: none;
border-bottom: $border-divider;
padding-bottom: $padding-global;

&__controls {
Comment thread
Loori-R marked this conversation as resolved.
position: fixed;
right: 0;
left: 0;
bottom: 0;
padding: $padding-global;
justify-content: flex-end;
border-top: $border-divider;
background-color: $color-background-body;
}
}

&-left {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { FC, useEffect, useRef, useState, RefObject, ChangeEvent, KeyboardEvent, useMemo } from "preact/compat";
import { FC, useEffect, useRef, useState, RefObject, useMemo } from "preact/compat";
import { CalendarIcon } from "../../Icons";
import DatePicker from "../DatePicker";
import Button from "../../Button/Button";
import { DATE_TIME_FORMAT } from "../../../../constants/date";
import InputMask from "react-input-mask";
import classNames from "classnames";
import "./style.scss";
import { vmDate } from "../../../../utils/time";

const formatStringDate = (val: string) => {
const localDate = vmDate(val);
return localDate.isValid() ? localDate.nano().format(DATE_TIME_FORMAT) : val;
};
import { parseDateTimeInputValue } from "./utils";
import { TargetedEvent } from "preact";

interface DateTimeInputProps {
value?: string;
Expand All @@ -33,52 +29,25 @@ const DateTimeInput: FC<DateTimeInputProps> = ({
const wrapperRef = useRef<HTMLDivElement>(null);
const [inputRef, setInputRef] = useState<HTMLInputElement | null>(null);

const [maskedValue, setMaskedValue] = useState(formatStringDate(value));
const isValidDate = !!maskedValue && vmDate(maskedValue).isValid();
const isoValue = useMemo(() => {
return isValidDate ? vmDate.tz(maskedValue).nano().toISOString() : "";
}, [maskedValue]);

const datePickerValue = useMemo(() => isValidDate ? vmDate.tz(maskedValue) : vmDate().tz(), [maskedValue]);
const isValidDate = useMemo(() => !!parseDateTimeInputValue(value), [value]);
const datePickerValue = useMemo(() => isValidDate ? vmDate.tz(value) : vmDate().tz(), [value]);

const [focusToTime, setFocusToTime] = useState(false);
const [awaitChangeForEnter, setAwaitChangeForEnter] = useState(false);
const error = isValidDate ? "" : "Invalid date format";

const handleMaskedChange = (e: ChangeEvent<HTMLInputElement>) => {
setMaskedValue(e.currentTarget.value);
const handleMaskedChange = (e: TargetedEvent<HTMLInputElement, Event>) => {
onChange(e.currentTarget.value);
};

const handleBlur = () => {
if (!isValidDate) return;
onChange(isoValue);
};

const handleKeyUp = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
if (!isValidDate) return;
onChange(isoValue);
setAwaitChangeForEnter(true);
}
const handleKeyUp = (e: KeyboardEvent) => {
if (e.key === "Enter" && isValidDate) onEnter();
};

const handleChangeDate = (val: string) => {
setMaskedValue(val);
onChange(val);
setFocusToTime(true);
};

useEffect(() => {
const newValue = formatStringDate(value);
if (newValue !== maskedValue) {
setMaskedValue(newValue);
}

if (awaitChangeForEnter) {
onEnter();
setAwaitChangeForEnter(false);
}
}, [value]);

useEffect(() => {
if (focusToTime && inputRef) {
inputRef.focus();
Expand All @@ -100,12 +69,11 @@ const DateTimeInput: FC<DateTimeInputProps> = ({
inputRef={setInputRef}
mask="9999-99-99 99:99:99.999999999"
placeholder="YYYY-MM-DD HH:mm:ss.SSSSSSSSS"
value={maskedValue}
value={value}
autoCapitalize={"none"}
inputMode={"numeric"}
maskChar={null}
onChange={handleMaskedChange}
onBlur={handleBlur}
onKeyUp={handleKeyUp}
/>
{error && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { afterEach, describe, expect, it } from "vitest";
import { vmDate } from "../../../../utils/time";
import { formatDateTimeInputValue, parseDateTimeInputValue } from "./utils";

describe("DateTimeInput utils", () => {
afterEach(() => {
vmDate.tz.setDefault();
});

it("parses a wall-clock draft in the selected timezone", () => {
vmDate.tz.setDefault("America/New_York");

expect(parseDateTimeInputValue("2026-07-22 10:31:01.168000000"))
.toBe("2026-07-22T14:31:01.168000000Z");
});

it("formats an ISO value in the selected timezone", () => {
vmDate.tz.setDefault("America/New_York");

expect(formatDateTimeInputValue("2026-07-22T14:31:01.168000000Z"))
.toBe("2026-07-22 10:31:01.168000000");
});

it("rejects empty and invalid drafts", () => {
expect(parseDateTimeInputValue("")).toBeNull();
expect(parseDateTimeInputValue("invalid")).toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DATE_TIME_FORMAT } from "../../../../constants/date";
import { vmDate } from "../../../../utils/time";

export const formatDateTimeInputValue = (value: string): string => {
const date = vmDate(value);
return date.isValid() ? date.nano().format(DATE_TIME_FORMAT) : value;
};

export const parseDateTimeInputValue = (value: string): string | null => {
if (!value || !vmDate(value).isValid()) return null;

try {
const date = vmDate.tz(value);
return date.isValid() ? date.nano().toISOString() : null;
} catch {
return null;
}
};
3 changes: 2 additions & 1 deletion app/vmui/packages/vmui/src/components/Main/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ const Modal: FC<ModalProps> = ({

const handleDisplayModal = () => {
if (!isOpen) return;
const previousOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";

return () => {
document.body.style.overflow = "auto";
document.body.style.overflow = previousOverflow;
};
};

Expand Down
6 changes: 5 additions & 1 deletion app/vmui/packages/vmui/src/components/Main/Tabs/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
position: relative;
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-start;
height: 100%;
gap: $padding-global;
user-select: none;
overflow-x: auto;
overflow-y: hidden;
overscroll-behavior-x: contain;

&-item {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const JsonLogsView: FC<ViewProps> = ({ data, settingsRef }) => {
if (!data.length) return <EmptyLogs />;

return (
<div className={"vm-json-view"}>
<div className="vm-json-view">
{renderSettings()}
<MemoizedJsonView
data={data}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
@use "src/styles/variables" as *;

.vm-json-view {
overflow: auto;

&__settings-container {
display: flex;
flex-direction: row;
Expand Down
Loading
Loading