From f06f55edb64e6848ae93659f40b7c45619fd2d58 Mon Sep 17 00:00:00 2001 From: Yury Molodov Date: Mon, 15 Apr 2024 09:25:52 +0200 Subject: [PATCH] vmui/vmanomaly: integrate vmanomaly query_server (#6017) * vmui: fix parsing of fractional values * vmui/vmanomaly: update display logic to align with vmanomaly /query_range API * vmui/vmanomaly: rename flag anomalyView to isAnomalyView --- .../packages/vmui/public/dashboards/README.md | 2 +- .../components/Chart/Line/Legend/Legend.tsx | 4 +- .../Line/Legend/LegendItem/LegendItem.tsx | 5 +- .../Line/LegendAnomaly/LegendAnomaly.tsx | 7 +- .../Chart/Line/LineChart/LineChart.tsx | 6 +- .../AdditionalSettings/AdditionalSettings.tsx | 46 +++-- .../components/Views/GraphView/GraphView.tsx | 46 +++-- .../packages/vmui/src/constants/navigation.ts | 4 - .../vmui/src/hooks/uplot/useLineTooltip.ts | 8 +- .../packages/vmui/src/hooks/useFetchQuery.ts | 11 +- .../CustomPanel/CustomPanelTabs/GraphTab.tsx | 6 +- .../QueryConfigurator/QueryConfigurator.tsx | 72 ++++--- .../vmui/src/pages/CustomPanel/style.scss | 2 +- .../pages/ExploreAnomaly/ExploreAnomaly.tsx | 107 +++++----- .../ExploreAnomalyHeader.tsx | 112 ----------- .../ExploreAnomalyHeader/style.scss | 37 ---- .../hooks/useFetchAnomalySeries.ts | 75 ------- .../ExploreAnomaly/hooks/useSetQueryParams.ts | 31 --- app/vmui/packages/vmui/src/types/uplot.ts | 3 +- .../vmui/src/utils/default-server-url.ts | 13 +- app/vmui/packages/vmui/src/utils/time.ts | 2 +- .../packages/vmui/src/utils/uplot/series.ts | 183 +++++++++++------- 22 files changed, 308 insertions(+), 474 deletions(-) delete mode 100644 app/vmui/packages/vmui/src/pages/ExploreAnomaly/ExploreAnomalyHeader/ExploreAnomalyHeader.tsx delete mode 100644 app/vmui/packages/vmui/src/pages/ExploreAnomaly/ExploreAnomalyHeader/style.scss delete mode 100644 app/vmui/packages/vmui/src/pages/ExploreAnomaly/hooks/useFetchAnomalySeries.ts delete mode 100644 app/vmui/packages/vmui/src/pages/ExploreAnomaly/hooks/useSetQueryParams.ts diff --git a/app/vmui/packages/vmui/public/dashboards/README.md b/app/vmui/packages/vmui/public/dashboards/README.md index 1fab50792..1208e19c5 100644 --- a/app/vmui/packages/vmui/public/dashboards/README.md +++ b/app/vmui/packages/vmui/public/dashboards/README.md @@ -1,3 +1,3 @@ ## Predefined dashboards -See [this docs](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui#predefined-dashboards) +See [this doc](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui#predefined-dashboards) diff --git a/app/vmui/packages/vmui/src/components/Chart/Line/Legend/Legend.tsx b/app/vmui/packages/vmui/src/components/Chart/Line/Legend/Legend.tsx index 677349131..186c59920 100644 --- a/app/vmui/packages/vmui/src/components/Chart/Line/Legend/Legend.tsx +++ b/app/vmui/packages/vmui/src/components/Chart/Line/Legend/Legend.tsx @@ -7,10 +7,11 @@ import "./style.scss"; interface LegendProps { labels: LegendItemType[]; query: string[]; + isAnomalyView?: boolean; onChange: (item: LegendItemType, metaKey: boolean) => void; } -const Legend: FC = ({ labels, query, onChange }) => { +const Legend: FC = ({ labels, query, isAnomalyView, onChange }) => { const groups = useMemo(() => { return Array.from(new Set(labels.map(l => l.group))); }, [labels]); @@ -39,6 +40,7 @@ const Legend: FC = ({ labels, query, onChange }) => { )} diff --git a/app/vmui/packages/vmui/src/components/Chart/Line/Legend/LegendItem/LegendItem.tsx b/app/vmui/packages/vmui/src/components/Chart/Line/Legend/LegendItem/LegendItem.tsx index 2566f3c7b..f0277f3f6 100644 --- a/app/vmui/packages/vmui/src/components/Chart/Line/Legend/LegendItem/LegendItem.tsx +++ b/app/vmui/packages/vmui/src/components/Chart/Line/Legend/LegendItem/LegendItem.tsx @@ -11,9 +11,10 @@ interface LegendItemProps { legend: LegendItemType; onChange?: (item: LegendItemType, metaKey: boolean) => void; isHeatmap?: boolean; + isAnomalyView?: boolean; } -const LegendItem: FC = ({ legend, onChange, isHeatmap }) => { +const LegendItem: FC = ({ legend, onChange, isHeatmap, isAnomalyView }) => { const copyToClipboard = useCopyToClipboard(); const freeFormFields = useMemo(() => { @@ -47,7 +48,7 @@ const LegendItem: FC = ({ legend, onChange, isHeatmap }) => { })} onClick={createHandlerClick(legend)} > - {!isHeatmap && ( + {!isAnomalyView && !isHeatmap && (
> = { [ForecastType.yhat]: "yhat", - [ForecastType.yhatLower]: "yhat_lower/_upper", - [ForecastType.yhatUpper]: "yhat_lower/_upper", + [ForecastType.yhatLower]: "yhat_upper - yhat_lower", + [ForecastType.yhatUpper]: "yhat_upper - yhat_lower", [ForecastType.anomaly]: "anomalies", [ForecastType.training]: "training data", [ForecastType.actual]: "y" @@ -42,9 +42,6 @@ const LegendAnomaly: FC = ({ series }) => { })); }, [series]); - const container = document.getElementById("legendAnomaly"); - if (!container) return null; - return <>
{/* TODO: remove .filter() after the correct training data has been added */} diff --git a/app/vmui/packages/vmui/src/components/Chart/Line/LineChart/LineChart.tsx b/app/vmui/packages/vmui/src/components/Chart/Line/LineChart/LineChart.tsx index 570a1a10c..3ee7b38e0 100644 --- a/app/vmui/packages/vmui/src/components/Chart/Line/LineChart/LineChart.tsx +++ b/app/vmui/packages/vmui/src/components/Chart/Line/LineChart/LineChart.tsx @@ -40,7 +40,7 @@ export interface LineChartProps { setPeriod: ({ from, to }: { from: Date, to: Date }) => void; layoutSize: ElementSize; height?: number; - anomalyView?: boolean; + isAnomalyView?: boolean; spanGaps?: boolean; } @@ -54,7 +54,7 @@ const LineChart: FC = ({ setPeriod, layoutSize, height, - anomalyView, + isAnomalyView, spanGaps = false }) => { const { isDarkTheme } = useAppState(); @@ -73,7 +73,7 @@ const LineChart: FC = ({ seriesFocus, setCursor, resetTooltips - } = useLineTooltip({ u: uPlotInst, metrics, series, unit, anomalyView }); + } = useLineTooltip({ u: uPlotInst, metrics, series, unit, isAnomalyView }); const options: uPlotOptions = { ...getDefaultOptions({ width: layoutSize.width, height }), 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 70ea7f8c7..789759b31 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/AdditionalSettings/AdditionalSettings.tsx +++ b/app/vmui/packages/vmui/src/components/Configurators/AdditionalSettings/AdditionalSettings.tsx @@ -12,8 +12,11 @@ import useBoolean from "../../../hooks/useBoolean"; import useEventListener from "../../../hooks/useEventListener"; import Tooltip from "../../Main/Tooltip/Tooltip"; import { AUTOCOMPLETE_QUICK_KEY } from "../../Main/ShortcutKeys/constants/keyList"; +import { QueryConfiguratorProps } from "../../../pages/CustomPanel/QueryConfigurator/QueryConfigurator"; -const AdditionalSettingsControls: FC<{isMobile?: boolean}> = ({ isMobile }) => { +type Props = Pick; + +const AdditionalSettingsControls: FC = ({ isMobile, hideButtons }) => { const { autocomplete } = useQueryState(); const queryDispatch = useQueryDispatch(); @@ -54,31 +57,35 @@ const AdditionalSettingsControls: FC<{isMobile?: boolean}> = ({ isMobile }) => { "vm-additional-settings_mobile": isMobile })} > - Quick tip: {AUTOCOMPLETE_QUICK_KEY}}> - - + {!hideButtons?.autocomplete && ( + Quick tip: {AUTOCOMPLETE_QUICK_KEY}}> + + + )} - + {!hideButtons?.traceQuery && ( + + )}
); }; -const AdditionalSettings: FC = () => { +const AdditionalSettings: FC = (props) => { const { isMobile } = useDeviceDetect(); const targetRef = useRef(null); @@ -106,13 +113,16 @@ const AdditionalSettings: FC = () => { onClose={handleCloseList} title={"Query settings"} > - + ); } - return ; + return ; }; export default AdditionalSettings; diff --git a/app/vmui/packages/vmui/src/components/Views/GraphView/GraphView.tsx b/app/vmui/packages/vmui/src/components/Views/GraphView/GraphView.tsx index ede3833c3..05006a58e 100644 --- a/app/vmui/packages/vmui/src/components/Views/GraphView/GraphView.tsx +++ b/app/vmui/packages/vmui/src/components/Views/GraphView/GraphView.tsx @@ -25,6 +25,7 @@ import useDeviceDetect from "../../../hooks/useDeviceDetect"; import useElementSize from "../../../hooks/useElementSize"; import { ChartTooltipProps } from "../../Chart/ChartTooltip/ChartTooltip"; import LegendAnomaly from "../../Chart/Line/LegendAnomaly/LegendAnomaly"; +import { groupByMultipleKeys } from "../../../utils/array"; export interface GraphViewProps { data?: MetricResult[]; @@ -40,7 +41,7 @@ export interface GraphViewProps { fullWidth?: boolean; height?: number; isHistogram?: boolean; - anomalyView?: boolean; + isAnomalyView?: boolean; spanGaps?: boolean; } @@ -58,7 +59,7 @@ const GraphView: FC = ({ fullWidth = true, height, isHistogram, - anomalyView, + isAnomalyView, spanGaps }) => { const { isMobile } = useDeviceDetect(); @@ -74,8 +75,8 @@ const GraphView: FC = ({ const [legendValue, setLegendValue] = useState(null); const getSeriesItem = useMemo(() => { - return getSeriesItemContext(data, hideSeries, alias, anomalyView); - }, [data, hideSeries, alias, anomalyView]); + return getSeriesItemContext(data, hideSeries, alias, isAnomalyView); + }, [data, hideSeries, alias, isAnomalyView]); const setLimitsYaxis = (values: { [key: string]: number[] }) => { const limits = getLimitsYAxis(values, !isHistogram); @@ -83,7 +84,7 @@ const GraphView: FC = ({ }; const onChangeLegend = (legend: LegendItemType, metaKey: boolean) => { - setHideSeries(getHideSeries({ hideSeries, legend, metaKey, series })); + setHideSeries(getHideSeries({ hideSeries, legend, metaKey, series, isAnomalyView })); }; const prepareHistogramData = (data: (number | null)[][]) => { @@ -108,6 +109,20 @@ const GraphView: FC = ({ return [null, [xs, ys, counts]]; }; + const prepareAnomalyLegend = (legend: LegendItemType[]): LegendItemType[] => { + if (!isAnomalyView) return legend; + + // For vmanomaly: Only select the first series per group (due to API specs) and clear __name__ in freeFormFields. + const grouped = groupByMultipleKeys(legend, ["group", "label"]); + return grouped.map((group) => { + const firstEl = group.values[0]; + return { + ...firstEl, + freeFormFields: { ...firstEl.freeFormFields, __name__: "" } + }; + }); + }; + useEffect(() => { const tempTimes: number[] = []; const tempValues: { [key: string]: number[] } = {}; @@ -153,14 +168,18 @@ const GraphView: FC = ({ const range = getMinMaxBuffer(getMinFromArray(resultAsNumber), getMaxFromArray(resultAsNumber)); const rangeStep = Math.abs(range[1] - range[0]); - return (avg > rangeStep * 1e10) && !anomalyView ? results.map(() => avg) : results; + return (avg > rangeStep * 1e10) && !isAnomalyView ? results.map(() => avg) : results; }); timeDataSeries.unshift(timeSeries); setLimitsYaxis(tempValues); const result = isHistogram ? prepareHistogramData(timeDataSeries) : timeDataSeries; setDataChart(result as uPlotData); setSeries(tempSeries); - setLegend(tempLegend); + const legend = prepareAnomalyLegend(tempLegend); + setLegend(legend); + if (isAnomalyView) { + setHideSeries(legend.map(s => s.label || "").slice(1)); + } }, [data, timezone, isHistogram]); useEffect(() => { @@ -172,7 +191,7 @@ const GraphView: FC = ({ tempLegend.push(getLegendItem(seriesItem, d.group)); }); setSeries(tempSeries); - setLegend(tempLegend); + setLegend(prepareAnomalyLegend(tempLegend)); }, [hideSeries]); const [containerRef, containerSize] = useElementSize(); @@ -197,7 +216,7 @@ const GraphView: FC = ({ setPeriod={setPeriod} layoutSize={containerSize} height={height} - anomalyView={anomalyView} + isAnomalyView={isAnomalyView} spanGaps={spanGaps} /> )} @@ -213,10 +232,12 @@ const GraphView: FC = ({ onChangeLegend={setLegendValue} /> )} - {!isHistogram && !anomalyView && showLegend && ( + {isAnomalyView && showLegend && ()} + {!isHistogram && showLegend && ( )} @@ -228,11 +249,6 @@ const GraphView: FC = ({ legendValue={legendValue} /> )} - {anomalyView && showLegend && ( - - )}
); }; diff --git a/app/vmui/packages/vmui/src/constants/navigation.ts b/app/vmui/packages/vmui/src/constants/navigation.ts index 1b7c4700f..d19863674 100644 --- a/app/vmui/packages/vmui/src/constants/navigation.ts +++ b/app/vmui/packages/vmui/src/constants/navigation.ts @@ -62,10 +62,6 @@ export const anomalyNavigation: NavigationItem[] = [ { label: routerOptions[router.anomaly].title, value: router.home, - }, - { - label: routerOptions[router.home].title, - value: router.query, } ]; diff --git a/app/vmui/packages/vmui/src/hooks/uplot/useLineTooltip.ts b/app/vmui/packages/vmui/src/hooks/uplot/useLineTooltip.ts index bc6012019..f72d5c9c3 100644 --- a/app/vmui/packages/vmui/src/hooks/uplot/useLineTooltip.ts +++ b/app/vmui/packages/vmui/src/hooks/uplot/useLineTooltip.ts @@ -14,10 +14,10 @@ interface LineTooltipHook { metrics: MetricResult[]; series: uPlotSeries[]; unit?: string; - anomalyView?: boolean; + isAnomalyView?: boolean; } -const useLineTooltip = ({ u, metrics, series, unit, anomalyView }: LineTooltipHook) => { +const useLineTooltip = ({ u, metrics, series, unit, isAnomalyView }: LineTooltipHook) => { const [showTooltip, setShowTooltip] = useState(false); const [tooltipIdx, setTooltipIdx] = useState({ seriesIdx: -1, dataIdx: -1 }); const [stickyTooltips, setStickyToolTips] = useState([]); @@ -61,14 +61,14 @@ const useLineTooltip = ({ u, metrics, series, unit, anomalyView }: LineTooltipHo point, u: u, id: `${seriesIdx}_${dataIdx}`, - title: groups.size > 1 && !anomalyView ? `Query ${group}` : "", + title: groups.size > 1 && !isAnomalyView ? `Query ${group}` : "", dates: [date ? dayjs(date * 1000).tz().format(DATE_FULL_TIMEZONE_FORMAT) : "-"], value: formatPrettyNumber(value, min, max), info: getMetricName(metricItem), statsFormatted: seriesItem?.statsFormatted, marker: `${seriesItem?.stroke}`, }; - }, [u, tooltipIdx, metrics, series, unit, anomalyView]); + }, [u, tooltipIdx, metrics, series, unit, isAnomalyView]); const handleClick = useCallback(() => { if (!showTooltip) return; diff --git a/app/vmui/packages/vmui/src/hooks/useFetchQuery.ts b/app/vmui/packages/vmui/src/hooks/useFetchQuery.ts index 4e4aaf06c..345958fe7 100644 --- a/app/vmui/packages/vmui/src/hooks/useFetchQuery.ts +++ b/app/vmui/packages/vmui/src/hooks/useFetchQuery.ts @@ -12,7 +12,8 @@ import { useTimeState } from "../state/time/TimeStateContext"; import { useCustomPanelState } from "../state/customPanel/CustomPanelStateContext"; import { isHistogramData } from "../utils/metric"; import { useGraphState } from "../state/graph/GraphStateContext"; -import { getStepFromDuration } from "../utils/time"; +import { getSecondsFromDuration, getStepFromDuration } from "../utils/time"; +import { AppType } from "../types/appType"; interface FetchQueryParams { predefinedQuery?: string[] @@ -47,13 +48,15 @@ interface FetchDataParams { hideQuery?: number[] } +const isAnomalyUI = AppType.anomaly === process.env.REACT_APP_TYPE; + export const useFetchQuery = ({ predefinedQuery, visible, display, customStep, hideQuery, - showAllSeries + showAllSeries, }: FetchQueryParams): FetchQueryReturn => { const { query } = useQueryState(); const { period } = useTimeState(); @@ -124,7 +127,7 @@ export const useFetchQuery = ({ tempTraces.push(trace); } - isHistogramResult = isDisplayChart && isHistogramData(resp.data.result); + isHistogramResult = !isAnomalyUI && isDisplayChart && isHistogramData(resp.data.result); seriesLimit = isHistogramResult ? Infinity : defaultLimit; const freeTempSize = seriesLimit - tempData.length; resp.data.result.slice(0, freeTempSize).forEach((d: MetricBase) => { @@ -172,7 +175,7 @@ export const useFetchQuery = ({ setQueryErrors(expr.map(() => ErrorTypes.validQuery)); } else if (isValidHttpUrl(serverUrl)) { const updatedPeriod = { ...period }; - updatedPeriod.step = customStep; + updatedPeriod.step = isAnomalyUI ? `${getSecondsFromDuration(customStep)*1000}ms` : customStep; return expr.map(q => displayChart ? getQueryRangeUrl(serverUrl, q, updatedPeriod, nocache, isTracingEnabled) : getQueryUrl(serverUrl, q, updatedPeriod, nocache, isTracingEnabled)); diff --git a/app/vmui/packages/vmui/src/pages/CustomPanel/CustomPanelTabs/GraphTab.tsx b/app/vmui/packages/vmui/src/pages/CustomPanel/CustomPanelTabs/GraphTab.tsx index 05185bb24..03a7d2090 100644 --- a/app/vmui/packages/vmui/src/pages/CustomPanel/CustomPanelTabs/GraphTab.tsx +++ b/app/vmui/packages/vmui/src/pages/CustomPanel/CustomPanelTabs/GraphTab.tsx @@ -14,10 +14,10 @@ type Props = { isHistogram: boolean; graphData: MetricResult[]; controlsRef: React.RefObject; - anomalyView?: boolean; + isAnomalyView?: boolean; } -const GraphTab: FC = ({ isHistogram, graphData, controlsRef, anomalyView }) => { +const GraphTab: FC = ({ isHistogram, graphData, controlsRef, isAnomalyView }) => { const { isMobile } = useDeviceDetect(); const { customStep, yaxis, spanGaps } = useGraphState(); @@ -68,7 +68,7 @@ const GraphTab: FC = ({ isHistogram, graphData, controlsRef, anomalyView setPeriod={setPeriod} height={isMobile ? window.innerHeight * 0.5 : 500} isHistogram={isHistogram} - anomalyView={anomalyView} + isAnomalyView={isAnomalyView} spanGaps={spanGaps} /> diff --git a/app/vmui/packages/vmui/src/pages/CustomPanel/QueryConfigurator/QueryConfigurator.tsx b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryConfigurator/QueryConfigurator.tsx index f49177950..bd687041e 100644 --- a/app/vmui/packages/vmui/src/pages/CustomPanel/QueryConfigurator/QueryConfigurator.tsx +++ b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryConfigurator/QueryConfigurator.tsx @@ -30,8 +30,14 @@ export interface QueryConfiguratorProps { setQueryErrors: StateUpdater; setHideError: StateUpdater; stats: QueryStats[]; - onHideQuery: (queries: number[]) => void - onRunQuery: () => void + onHideQuery?: (queries: number[]) => void + onRunQuery: () => void; + hideButtons?: { + addQuery?: boolean; + prettify?: boolean; + autocomplete?: boolean; + traceQuery?: boolean; + } } const QueryConfigurator: FC = ({ @@ -40,7 +46,8 @@ const QueryConfigurator: FC = ({ setHideError, stats, onHideQuery, - onRunQuery + onRunQuery, + hideButtons }) => { const { isMobile } = useDeviceDetect(); @@ -159,7 +166,7 @@ const QueryConfigurator: FC = ({ }, [stateQuery]); useEffect(() => { - onHideQuery(hideQuery); + onHideQuery && onHideQuery(hideQuery); }, [hideQuery]); useEffect(() => { @@ -188,40 +195,43 @@ const QueryConfigurator: FC = ({ > 1 ? i + 1 : ""}`} disabled={hideQuery.includes(i)} /> - -
-
-
+ {onHideQuery && ( + +
+
+
+ )} - -
-
-
+ {!hideButtons?.prettify && ( + +
+
+
)} {stateQuery.length > 1 && ( @@ -240,10 +250,10 @@ const QueryConfigurator: FC = ({ ))}
- +
- {stateQuery.length < MAX_QUERY_FIELDS && ( + {!hideButtons?.addQuery && stateQuery.length < MAX_QUERY_FIELDS && (