From 1df87807fd7b360a8c7eb9eb770b5773385a9e30 Mon Sep 17 00:00:00 2001
From: Yury Molodov <yurymolodov@gmail.com>
Date: Thu, 22 Dec 2022 23:54:28 +0100
Subject: [PATCH] vmui: step (#3521)

* feat: add step rounding

* fix: change step in URL parameters

* refactor: change comment for roundStep
---
 .../AdditionalSettings/AdditionalSettings.tsx | 20 ++++++++++++++++---
 .../StepConfigurator/StepConfigurator.tsx     | 16 +++++++--------
 .../CustomPanel/hooks/useSetQueryParams.ts    |  7 +++++--
 app/vmui/packages/vmui/src/utils/time.ts      | 20 ++++++++++++++++++-
 4 files changed, 49 insertions(+), 14 deletions(-)

diff --git a/app/vmui/packages/vmui/src/components/Configurators/AdditionalSettings/AdditionalSettings.tsx b/app/vmui/packages/vmui/src/components/Configurators/AdditionalSettings/AdditionalSettings.tsx
index 46959aa035..7dbe65f5bd 100644
--- a/app/vmui/packages/vmui/src/components/Configurators/AdditionalSettings/AdditionalSettings.tsx
+++ b/app/vmui/packages/vmui/src/components/Configurators/AdditionalSettings/AdditionalSettings.tsx
@@ -1,6 +1,6 @@
-import React, { FC } from "preact/compat";
+import React, { FC, useEffect } from "preact/compat";
 import StepConfigurator from "../StepConfigurator/StepConfigurator";
-import { useGraphDispatch } from "../../../state/graph/GraphStateContext";
+import { useGraphDispatch, useGraphState } from "../../../state/graph/GraphStateContext";
 import { getAppModeParams } from "../../../utils/app-mode";
 import TenantsConfiguration from "../TenantsConfiguration/TenantsConfiguration";
 import { useCustomPanelDispatch, useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext";
@@ -8,10 +8,13 @@ import { useTimeState } from "../../../state/time/TimeStateContext";
 import { useQueryDispatch, useQueryState } from "../../../state/query/QueryStateContext";
 import "./style.scss";
 import Switch from "../../Main/Switch/Switch";
+import usePrevious from "../../../hooks/usePrevious";
 
 const AdditionalSettings: FC = () => {
 
+  const { customStep } = useGraphState();
   const graphDispatch = useGraphDispatch();
+
   const { inputTenantID } = getAppModeParams();
 
   const { autocomplete } = useQueryState();
@@ -20,7 +23,8 @@ const AdditionalSettings: FC = () => {
   const { nocache, isTracingEnabled } = useCustomPanelState();
   const customPanelDispatch = useCustomPanelDispatch();
 
-  const { period: { step } } = useTimeState();
+  const { period: { step }, duration } = useTimeState();
+  const prevDuration = usePrevious(duration);
 
   const onChangeCache = () => {
     customPanelDispatch({ type: "TOGGLE_NO_CACHE" });
@@ -38,6 +42,15 @@ const AdditionalSettings: FC = () => {
     graphDispatch({ type: "SET_CUSTOM_STEP", payload: value });
   };
 
+  useEffect(() => {
+    if (!customStep && step) onChangeStep(step);
+  }, [step]);
+
+  useEffect(() => {
+    if (duration === prevDuration || !prevDuration) return;
+    if (step) onChangeStep(step);
+  }, [duration, prevDuration]);
+
   return <div className="vm-additional-settings">
     <Switch
       label={"Autocomplete"}
@@ -58,6 +71,7 @@ const AdditionalSettings: FC = () => {
       <StepConfigurator
         defaultStep={step}
         setStep={onChangeStep}
+        value={customStep}
       />
     </div>
     {!!inputTenantID && (
diff --git a/app/vmui/packages/vmui/src/components/Configurators/StepConfigurator/StepConfigurator.tsx b/app/vmui/packages/vmui/src/components/Configurators/StepConfigurator/StepConfigurator.tsx
index 9c32522ae6..97f18d30e2 100644
--- a/app/vmui/packages/vmui/src/components/Configurators/StepConfigurator/StepConfigurator.tsx
+++ b/app/vmui/packages/vmui/src/components/Configurators/StepConfigurator/StepConfigurator.tsx
@@ -1,5 +1,4 @@
-import React, { FC, useCallback, useState } from "preact/compat";
-import { useEffect } from "react";
+import React, { FC, useCallback, useEffect, useState } from "preact/compat";
 import debounce from "lodash.debounce";
 import { RestartIcon } from "../../Main/Icons";
 import TextField from "../../Main/TextField/TextField";
@@ -8,16 +7,17 @@ import Tooltip from "../../Main/Tooltip/Tooltip";
 
 interface StepConfiguratorProps {
   defaultStep?: number,
+  value?: number,
   setStep: (step: number) => void,
 }
 
-const StepConfigurator: FC<StepConfiguratorProps> = ({ defaultStep, setStep }) => {
+const StepConfigurator: FC<StepConfiguratorProps> = ({ value, defaultStep, setStep }) => {
 
-  const [customStep, setCustomStep] = useState(defaultStep);
+  const [customStep, setCustomStep] = useState(value || defaultStep);
   const [error, setError] = useState("");
 
   const handleApply = (step: number) => setStep(step || 1);
-  const debouncedHandleApply = useCallback(debounce(handleApply, 700), []);
+  const debouncedHandleApply = useCallback(debounce(handleApply, 500), []);
 
   const onChangeStep = (val: string) => {
     const value = +val;
@@ -40,12 +40,12 @@ const StepConfigurator: FC<StepConfiguratorProps> = ({ defaultStep, setStep }) =
   };
 
   useEffect(() => {
-    if (defaultStep) handleSetStep(defaultStep);
-  }, [defaultStep]);
+    if (value) handleSetStep(value);
+  }, [value]);
 
   return (
     <TextField
-      label="Step value"
+      label="Step value of seconds"
       type="number"
       value={customStep}
       error={error}
diff --git a/app/vmui/packages/vmui/src/pages/CustomPanel/hooks/useSetQueryParams.ts b/app/vmui/packages/vmui/src/pages/CustomPanel/hooks/useSetQueryParams.ts
index 8f85222b4b..8c51aad28a 100644
--- a/app/vmui/packages/vmui/src/pages/CustomPanel/hooks/useSetQueryParams.ts
+++ b/app/vmui/packages/vmui/src/pages/CustomPanel/hooks/useSetQueryParams.ts
@@ -6,12 +6,14 @@ import { useQueryState } from "../../../state/query/QueryStateContext";
 import { displayTypeTabs } from "../DisplayTypeSwitch";
 import { setQueryStringWithoutPageReload } from "../../../utils/query-string";
 import { compactObject } from "../../../utils/object";
+import { useGraphState } from "../../../state/graph/GraphStateContext";
 
 export const useSetQueryParams = () => {
   const { tenantId } = useAppState();
   const { displayType } = useCustomPanelState();
   const { query } = useQueryState();
   const { duration, relativeTime, period: { date, step } } = useTimeState();
+  const { customStep } = useGraphState();
 
   const setSearchParamsFromState = () => {
     const params: Record<string, unknown> = {};
@@ -20,15 +22,16 @@ export const useSetQueryParams = () => {
       params[`${group}.expr`] = q;
       params[`${group}.range_input`] = duration;
       params[`${group}.end_input`] = date;
-      params[`${group}.step_input`] = step;
       params[`${group}.tab`] = displayTypeTabs.find(t => t.value === displayType)?.prometheusCode || 0;
       params[`${group}.relative_time`] = relativeTime;
       params[`${group}.tenantID`] = tenantId;
+
+      if ((step !== customStep) && customStep) params[`${group}.step_input`] = customStep;
     });
 
     setQueryStringWithoutPageReload(compactObject(params));
   };
 
-  useEffect(setSearchParamsFromState, [tenantId, displayType, query, duration, relativeTime, date, step]);
+  useEffect(setSearchParamsFromState, [tenantId, displayType, query, duration, relativeTime, date, step, customStep]);
   useEffect(setSearchParamsFromState, []);
 };
diff --git a/app/vmui/packages/vmui/src/utils/time.ts b/app/vmui/packages/vmui/src/utils/time.ts
index b6c13713ab..c6d40ab3c5 100644
--- a/app/vmui/packages/vmui/src/utils/time.ts
+++ b/app/vmui/packages/vmui/src/utils/time.ts
@@ -26,6 +26,23 @@ const shortDurations = supportedDurations.map(d => d.short);
 
 export const roundToMilliseconds = (num: number): number => Math.round(num*1000)/1000;
 
+const roundStep = (step: number) => {
+  const integerStep = Math.round(step);
+  if (step >= 100) {
+    return integerStep - (integerStep%10); // integer multiple of 10
+  }
+  if (step < 100 && step >= 10) {
+    return integerStep - (integerStep%5); // integer multiple of 5
+  }
+  if (step < 10 && step >= 1) {
+    return integerStep; // integer
+  }
+  if (step < 1 && step > 0.01) {
+    return Math.round(step * 40) / 40; // float to thousandths multiple of 5
+  }
+  return roundToMilliseconds(step);
+};
+
 export const isSupportedDuration = (str: string): Partial<Record<UnitTypeShort, string>> | undefined => {
 
   const digits = str.match(/\d+/g);
@@ -57,7 +74,8 @@ export const getTimeperiodForDuration = (dur: string, date?: Date): TimeParams =
   }, {});
 
   const delta = dayjs.duration(durObject).asSeconds();
-  const step = roundToMilliseconds(delta / MAX_ITEMS_PER_CHART) || 0.001;
+  const rawStep = delta / MAX_ITEMS_PER_CHART;
+  const step = roundStep(rawStep) || 0.001;
 
   return {
     start: n - delta,