diff --git a/app/vmui/packages/vmui/src/api/tsdb.ts b/app/vmui/packages/vmui/src/api/tsdb.ts index 6248edabf2..b3277d088a 100644 --- a/app/vmui/packages/vmui/src/api/tsdb.ts +++ b/app/vmui/packages/vmui/src/api/tsdb.ts @@ -1,6 +1,5 @@ export interface CardinalityRequestsParams { topN: number, - extraLabel: string | null, match: string | null, date: string | null, focusLabel: string | null, diff --git a/app/vmui/packages/vmui/src/components/Chart/SimpleBarChart/SimpleBarChart.tsx b/app/vmui/packages/vmui/src/components/Chart/SimpleBarChart/SimpleBarChart.tsx new file mode 100644 index 0000000000..90a6eed7ea --- /dev/null +++ b/app/vmui/packages/vmui/src/components/Chart/SimpleBarChart/SimpleBarChart.tsx @@ -0,0 +1,67 @@ +import React, { FC, useEffect, useState } from "preact/compat"; +import Tooltip from "../../Main/Tooltip/Tooltip"; +import "./style.scss"; + +type BarChartData = { + value: number, + name: string, + percentage?: number, +} + +interface SimpleBarChartProps { + data: BarChartData[], +} + +const SimpleBarChart: FC = ({ data }) => { + + const [bars, setBars] = useState([]); + const [yAxis, setYAxis] = useState([0, 0]); + + const generateYAxis = (sortedValues: BarChartData[]) => { + const numbers = sortedValues.map(b => b.value); + const max = Math.ceil(numbers[0] || 1); + const ticks = 10; + const step = max / (ticks - 1); + return new Array(ticks + 1).fill(max + step).map((v, i) => Math.round(v - (step * i))); + }; + + useEffect(() => { + const sortedValues = data.sort((a, b) => b.value - a.value); + const yAxis = generateYAxis(sortedValues); + setYAxis(yAxis); + + setBars(sortedValues.map(b => ({ + ...b, + percentage: (b.value / yAxis[0]) * 100, + }))); + }, [data]); + + return ( +
+
+ {yAxis.map(v => ( +
{v}
+ ))} +
+
+ {bars.map(({ name, value, percentage }) => ( + +
+ + ))} +
+
+ ); +}; + +export default SimpleBarChart; diff --git a/app/vmui/packages/vmui/src/components/Chart/SimpleBarChart/style.scss b/app/vmui/packages/vmui/src/components/Chart/SimpleBarChart/style.scss new file mode 100644 index 0000000000..a274c1cfe8 --- /dev/null +++ b/app/vmui/packages/vmui/src/components/Chart/SimpleBarChart/style.scss @@ -0,0 +1,74 @@ +@use "src/styles/variables" as *; + +$color-bar: #33BB55; +$color-bar-highest: #F79420; + +.vm-simple-bar-chart { + display: grid; + grid-template-columns: auto 1fr; + height: 100%; + padding-bottom: #{$font-size-small/2}; + overflow: hidden; + + &-y-axis { + position: relative; + display: grid; + transform: translateY(#{$font-size-small}); + + &__tick { + position: relative; + display: flex; + align-items: center; + justify-content: flex-end; + transform-style: preserve-3d; + text-align: right; + padding-right: $padding-small; + font-size: $font-size-small; + line-height: 2; + z-index: 1; + + &:after { + content: ''; + position: absolute; + top: auto; + left: 100%; + width: 100vw; + height: 0; + border-bottom: $border-divider; + transform: translateY(-1px) translateZ(-1); + } + } + } + + &-data { + position: relative; + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 1%; + + &-item { + display: flex; + align-items: flex-start; + justify-content: center; + flex-grow: 1; + width: 100%; + min-width: 1px; + height: calc(100% - ($font-size-small*4)); + background-color: $color-bar; + transition: background-color 200ms ease-in; + + &:hover { + background-color: lighten($color-bar, 10%); + } + + &:first-child { + background-color: $color-bar-highest; + + &:hover { + background-color: lighten($color-bar-highest, 10%); + } + } + } + } +} diff --git a/app/vmui/packages/vmui/src/components/Configurators/CardinalityDatePicker/CardinalityDatePicker.tsx b/app/vmui/packages/vmui/src/components/Configurators/CardinalityDatePicker/CardinalityDatePicker.tsx index ad88363bf0..fa1fe982fc 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/CardinalityDatePicker/CardinalityDatePicker.tsx +++ b/app/vmui/packages/vmui/src/components/Configurators/CardinalityDatePicker/CardinalityDatePicker.tsx @@ -1,5 +1,4 @@ -import React, { FC, useMemo, useRef } from "preact/compat"; -import { useCardinalityState, useCardinalityDispatch } from "../../../state/cardinality/CardinalityStateContext"; +import React, { FC, useEffect, useMemo, useRef } from "preact/compat"; import dayjs from "dayjs"; import Button from "../../Main/Button/Button"; import { ArrowDownIcon, CalendarIcon } from "../../Main/Icons"; @@ -8,21 +7,28 @@ import { getAppModeEnable } from "../../../utils/app-mode"; import { DATE_FORMAT } from "../../../constants/date"; import DatePicker from "../../Main/DatePicker/DatePicker"; import useDeviceDetect from "../../../hooks/useDeviceDetect"; +import { useSearchParams } from "react-router-dom"; const CardinalityDatePicker: FC = () => { const { isMobile } = useDeviceDetect(); const appModeEnable = getAppModeEnable(); const buttonRef = useRef(null); - const { date } = useCardinalityState(); - const cardinalityDispatch = useCardinalityDispatch(); + const [searchParams, setSearchParams] = useSearchParams(); + + const date = searchParams.get("date") || dayjs().tz().format(DATE_FORMAT); const dateFormatted = useMemo(() => dayjs.tz(date).format(DATE_FORMAT), [date]); const handleChangeDate = (val: string) => { - cardinalityDispatch({ type: "SET_DATE", payload: val }); + searchParams.set("date", val); + setSearchParams(searchParams); }; + useEffect(() => { + handleChangeDate(date); + }, []); + return (
diff --git a/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx b/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx index f5abdfd342..bbbb0d0fa4 100644 --- a/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx @@ -387,3 +387,14 @@ export const TuneIcon = () => ( > ); + +export const TipIcon = () => ( + + + +); diff --git a/app/vmui/packages/vmui/src/contexts/AppContextProvider.tsx b/app/vmui/packages/vmui/src/contexts/AppContextProvider.tsx index 4e5bf9a745..e580e2da08 100644 --- a/app/vmui/packages/vmui/src/contexts/AppContextProvider.tsx +++ b/app/vmui/packages/vmui/src/contexts/AppContextProvider.tsx @@ -3,7 +3,6 @@ import { TimeStateProvider } from "../state/time/TimeStateContext"; import { QueryStateProvider } from "../state/query/QueryStateContext"; import { CustomPanelStateProvider } from "../state/customPanel/CustomPanelStateContext"; import { GraphStateProvider } from "../state/graph/GraphStateContext"; -import { CardinalityStateProvider } from "../state/cardinality/CardinalityStateContext"; import { TopQueriesStateProvider } from "../state/topQueries/TopQueriesStateContext"; import { SnackbarProvider } from "./Snackbar"; @@ -16,7 +15,6 @@ const providers = [ QueryStateProvider, CustomPanelStateProvider, GraphStateProvider, - CardinalityStateProvider, TopQueriesStateProvider, SnackbarProvider, DashboardsStateProvider diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityConfigurator/CardinalityConfigurator.tsx b/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityConfigurator/CardinalityConfigurator.tsx index 5f3f1a3c58..a7c10aa0c2 100644 --- a/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityConfigurator/CardinalityConfigurator.tsx +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityConfigurator/CardinalityConfigurator.tsx @@ -1,96 +1,76 @@ import React, { FC, useMemo } from "react"; -import QueryEditor from "../../../components/Configurators/QueryEditor/QueryEditor"; -import { useFetchQueryOptions } from "../../../hooks/useFetchQueryOptions"; -import { ErrorTypes } from "../../../types"; -import { useQueryDispatch, useQueryState } from "../../../state/query/QueryStateContext"; -import Switch from "../../../components/Main/Switch/Switch"; -import { InfoIcon, PlayIcon, QuestionIcon, WikiIcon } from "../../../components/Main/Icons"; +import { PlayIcon, QuestionIcon, RestartIcon, TipIcon, WikiIcon } from "../../../components/Main/Icons"; import Button from "../../../components/Main/Button/Button"; import TextField from "../../../components/Main/TextField/TextField"; import "./style.scss"; import Tooltip from "../../../components/Main/Tooltip/Tooltip"; import useDeviceDetect from "../../../hooks/useDeviceDetect"; import classNames from "classnames"; +import { useEffect, useState } from "preact/compat"; +import { useSearchParams } from "react-router-dom"; +import CardinalityTotals, { CardinalityTotalsProps } from "../CardinalityTotals/CardinalityTotals"; -export interface CardinalityConfiguratorProps { - onSetHistory: (step: number) => void; - onSetQuery: (query: string) => void; - onRunQuery: () => void; - onTopNChange: (value: string) => void; - onFocusLabelChange: (value: string) => void; - query: string; - topN: number; - error?: ErrorTypes | string; - totalSeries: number; - totalLabelValuePairs: number; - date: string | null; - match: string | null; - focusLabel: string | null; -} - -const CardinalityConfigurator: FC = ({ - topN, - error, - query, - onSetHistory, - onRunQuery, - onSetQuery, - onTopNChange, - onFocusLabelChange, - totalSeries, - totalLabelValuePairs, - date, - match, - focusLabel -}) => { - const { autocomplete } = useQueryState(); - const queryDispatch = useQueryDispatch(); +const CardinalityConfigurator: FC = (props) => { const { isMobile } = useDeviceDetect(); + const [searchParams, setSearchParams] = useSearchParams(); - const { queryOptions } = useFetchQueryOptions(); + const showTips = searchParams.get("tips") || ""; + const [match, setMatch] = useState(searchParams.get("match") || ""); + const [focusLabel, setFocusLabel] = useState(searchParams.get("focusLabel") || ""); + const [topN, setTopN] = useState(+(searchParams.get("topN") || 10)); - const errorTopN = useMemo(() => topN < 1 ? "Number must be bigger than zero" : "", [topN]); + const errorTopN = useMemo(() => topN < 0 ? "Number must be bigger than zero" : "", [topN]); - const onChangeAutocomplete = () => { - queryDispatch({ type: "TOGGLE_AUTOCOMPLETE" }); + const handleTopNChange = (val: string) => { + const num = +val; + setTopN(isNaN(num) ? 0 : num); }; - const handleArrowUp = () => { - onSetHistory(-1); + const handleRunQuery = () => { + searchParams.set("match", match); + searchParams.set("topN", topN.toString()); + searchParams.set("focusLabel", focusLabel); + setSearchParams(searchParams); }; - const handleArrowDown = () => { - onSetHistory(1); + const handleResetQuery = () => { + searchParams.set("match", ""); + searchParams.set("focusLabel", ""); + setSearchParams(searchParams); }; + const handleToggleTips = () => { + const showTips = searchParams.get("tips") || ""; + if (showTips) searchParams.delete("tips"); + else searchParams.set("tips", "true"); + setSearchParams(searchParams); + }; + + useEffect(() => { + const matchQuery = searchParams.get("match"); + const topNQuery = +(searchParams.get("topN") || 10); + const focusLabelQuery = searchParams.get("focusLabel"); + if (matchQuery !== match) setMatch(matchQuery || ""); + if (topNQuery !== topN) setTopN(topNQuery); + if (focusLabelQuery !== focusLabel) setFocusLabel(focusLabelQuery || ""); + }, [searchParams]); + return
- -
-
@@ -98,41 +78,36 @@ const CardinalityConfigurator: FC = ({ label="Focus label" type="text" value={focusLabel || ""} - onChange={onFocusLabelChange} + onChange={setFocusLabel} + onEnter={handleRunQuery} endIcon={(

To identify values with the highest number of series for the selected label.

-

Adds a table showing the series with the highest number of series.

)} > - + )} />
-
-
- -
-
-
- Analyzed {totalSeries} series with {totalLabelValuePairs} "label=value" pairs - at {date}{match && for series selector {match}}. - Show top {topN} entries per table. +
+
-
+
+
+ + + - + +
+ + + +
; }; diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityConfigurator/style.scss b/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityConfigurator/style.scss index 0250536473..f2699eb566 100644 --- a/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityConfigurator/style.scss +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityConfigurator/style.scss @@ -9,58 +9,65 @@ align-items: center; justify-content: flex-start; flex-wrap: wrap; - gap: 0 $padding-medium; + gap: $padding-small $padding-medium; &__query { - flex-grow: 8; + flex-grow: 10; } &__item { - flex-grow: 1; - } - } + flex-grow: 2; - &-additional { - display: flex; - align-items: center; - margin-bottom: $padding-small; + &_limit { + flex-grow: 1; + } + + svg { + color: $color-text-disabled + } + } } &-bottom { display: flex; - flex-wrap: wrap; align-items: center; + justify-content: flex-end; + flex-wrap: wrap; gap: $padding-global; + width: 100%; - &__docs { + &-helpful { display: flex; align-items: center; - gap: $padding-global; + justify-content: flex-end; + flex-wrap: wrap; + gap: $padding-small $padding-global; + + a { + color: $color-text-secondary; + } } - &_mobile &__docs { - justify-content: space-between; + &__execute { + display: flex; + align-items: center; + gap: $padding-small; } + } - &__info { + &_mobile &-bottom { + justify-content: center; + + &-helpful { flex-grow: 1; - font-size: $font-size; + justify-content: center; } - a { - color: $color-text-secondary; - } + &__execute { + width: 100%; - button { - margin: 0 0 0 auto; - } - - &_mobile { - display: grid; - grid-template-columns: 1fr; - - button { - margin: 0; + button:nth-child(3) { + width: 100%; } } } diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityTips/index.tsx b/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityTips/index.tsx new file mode 100644 index 0000000000..4d4be5d712 --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityTips/index.tsx @@ -0,0 +1,124 @@ +import { TipIcon } from "../../../components/Main/Icons"; +import React, { FC } from "preact/compat"; +import { ReactNode } from "react"; +import "./style.scss"; + +const Link: FC<{ href: string, children: ReactNode, target?: string }> = ({ href, children, target }) => ( + + {children} + +); + +const TipCard: FC<{ title?: string, children: ReactNode }> = ({ title, children }) => ( +
+
+
+

{title || "Tips"}

+
+

+ {children} +

+
+); + +export const TipDocumentation: FC = () => ( + +
Helpful for analyzing VictoriaMetrics TSDB data
+
    +
  • + + Cardinality explorer documentation + +
  • +
  • + See the + example of using the cardinality explorer +
  • +
+
+); + +export const TipHighNumberOfSeries: FC = () => ( + +
    +
  • + Identify and eliminate labels with frequently changed values to reduce their  + cardinality and  + high churn rate +
  • +
  • + Find unused time series and  + drop entire metrics +
  • +
  • + Aggregate time series before they got ingested into the database via  + streaming aggregation +
  • +
+
+); + +export const TipHighNumberOfValues: FC = () => ( + +
    +
  • Decrease the number of unique label values to reduce cardinality
  • +
  • Drop the label entirely via  + relabeling
  • +
  • For volatile label values (such as URL path, user session, etc.) + consider printing them to the log file instead of adding to time series
  • +
+
+); + +export const TipCardinalityOfSingle: FC = () => ( + +

This dashboard helps to understand the cardinality of a single metric.

+

+ Each time series is a unique combination of key-value label pairs. + Therefore a label key with many values can create a lot of time series for a particular metric. + If you’re trying to decrease the cardinality of a metric, + start by looking at the labels with the highest number of values. +

+

Use the series selector at the top of the page to apply additional filters.

+
+); + +export const TipCardinalityOfLabel: FC = () => ( + +

+ This dashboard helps you understand the count of time series per label. +

+

+ Use the selector at the top of the page to pick a label name you’d like to inspect. + For the selected label name, you’ll see the label values that have the highest number of series associated with + them. + So if you’ve chosen `instance` as your label name, you may see that `657` time series have value + “host-1” attached to them and `580` time series have value `host-2` attached to them. +

+

+ This can be helpful in allowing you to determine where the bulk of your time series are coming from. + If the label “instance=host-1” was applied to 657 series and the label “instance=host-2” + was only applied to 580 series, you’d know, for example, that host-01 was responsible for sending + the majority of the time series. +

+
+); diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityTips/style.scss b/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityTips/style.scss new file mode 100644 index 0000000000..bd96b7bd30 --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityTips/style.scss @@ -0,0 +1,87 @@ +@use "src/styles/variables" as *; + +.vm-cardinality-tip { + display: grid; + grid-template-rows: auto 1fr; + background-color: $color-background-block; + border-radius: $border-radius-medium; + box-shadow: $box-shadow; + overflow: hidden; + color: $color-text-secondary; + flex-grow: 1; + width: 300px; + + &-header { + position: relative; + display: flex; + align-items: center; + justify-content: center; + padding: $padding-small $padding-global; + border-bottom: $border-divider; + gap: 4px; + + &:after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: $color-warning; + opacity: 0.1; + pointer-events: none + } + + &__tip-icon { + width: 12px; + display: flex; + align-items: center; + justify-content: center; + color: $color-warning; + } + + &__title { + font-weight: bold; + text-align: center; + color: $color-text; + } + + &__tooltip { + max-width: 280px; + white-space: normal; + padding: $padding-small; + line-height: 130%; + font-size: $font-size; + } + } + + &__description { + padding: $padding-small $padding-global; + line-height: 130%; + + p { + margin-bottom: $padding-small; + + &:last-child { + margin-bottom: 0; + } + } + + h5 { + font-size: $font-size-medium; + margin-bottom: $padding-small; + } + + h6 { + margin-bottom: $padding-small; + } + + ul, ol { + list-style-position: inside; + + li { + margin-bottom: calc($padding-small/2); + } + } + } +} diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityTotals/CardinalityTotals.tsx b/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityTotals/CardinalityTotals.tsx new file mode 100644 index 0000000000..07a828428a --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityTotals/CardinalityTotals.tsx @@ -0,0 +1,80 @@ +import React, { FC } from "preact/compat"; +import { InfoIcon } from "../../../components/Main/Icons"; +import Tooltip from "../../../components/Main/Tooltip/Tooltip"; +import { TopHeapEntry } from "../types"; +import { useSearchParams } from "react-router-dom"; +import classNames from "classnames"; +import useDeviceDetect from "../../../hooks/useDeviceDetect"; +import "./style.scss"; + +export interface CardinalityTotalsProps { + totalSeries: number; + totalSeriesAll: number; + totalLabelValuePairs: number; + seriesCountByMetricName: TopHeapEntry[]; +} + +const CardinalityTotals: FC = ({ + totalSeries, + totalSeriesAll, + seriesCountByMetricName +}) => { + const { isMobile } = useDeviceDetect(); + + const [searchParams] = useSearchParams(); + const match = searchParams.get("match"); + const focusLabel = searchParams.get("focusLabel"); + const isMetric = /__name__/.test(match || ""); + + const progress = seriesCountByMetricName[0]?.value / totalSeriesAll * 100; + + const totals = [ + { + title: "Total series", + value: totalSeries.toLocaleString("en-US"), + display: !focusLabel, + info: `The total number of active time series. + A time series is uniquely identified by its name plus a set of its labels. + For example, temperature{city="NY",country="US"} and temperature{city="SF",country="US"} + are two distinct series, since they differ by the city label.` + }, + { + title: "Percentage from total", + value: isNaN(progress) ? "-" : `${progress.toFixed(2)}%`, + display: isMetric, + info: "The share of these series in the total number of time series." + } + ].filter(t => t.display); + + if (!totals.length) { + return null; + } + + return ( +
+ {totals.map(({ title, value, info }) => ( +
+
+ {info && ( + {info}

}> +
+
+ )} +

{title}

+
+ {value} +
+ ))} +
+ ); +}; + +export default CardinalityTotals; diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityTotals/style.scss b/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityTotals/style.scss new file mode 100644 index 0000000000..459cb2c090 --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/CardinalityTotals/style.scss @@ -0,0 +1,60 @@ +@use "src/styles/variables" as *; + +.vm-cardinality-totals { + display: inline-flex; + flex-wrap: wrap; + align-content: flex-start; + justify-content: flex-start; + gap: $padding-global; + flex-grow: 1; + + &_mobile { + gap: $padding-small; + justify-content: center; + } + + &-card { + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + + &-header { + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + + &__info-icon { + width: 12px; + display: flex; + align-items: center; + justify-content: center; + color: $color-primary; + } + + &__title { + font-weight: bold; + color: $color-text; + + &:after { + content: ':'; + } + } + + &__tooltip { + max-width: 280px; + white-space: normal; + padding: $padding-small; + line-height: 130%; + font-size: $font-size; + } + } + + &__value { + font-weight: bold; + color: $color-primary; + font-size: $font-size-medium; + } + } +} diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/MetricsContent/MetricsContent.tsx b/app/vmui/packages/vmui/src/pages/CardinalityPanel/MetricsContent/MetricsContent.tsx index fbe3d4abbf..8210fcfa21 100644 --- a/app/vmui/packages/vmui/src/pages/CardinalityPanel/MetricsContent/MetricsContent.tsx +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/MetricsContent/MetricsContent.tsx @@ -1,43 +1,40 @@ import React, { FC } from "react"; import EnhancedTable from "../Table/Table"; import TableCells from "../Table/TableCells/TableCells"; -import BarChart from "../../../components/Chart/BarChart/BarChart"; -import { barOptions } from "../../../components/Chart/BarChart/consts"; import { Data, HeadCell } from "../Table/types"; import { MutableRef } from "preact/hooks"; import Tabs from "../../../components/Main/Tabs/Tabs"; -import { useMemo } from "preact/compat"; -import { ChartIcon, TableIcon } from "../../../components/Main/Icons"; +import { useMemo, useState } from "preact/compat"; +import { ChartIcon, InfoIcon, TableIcon } from "../../../components/Main/Icons"; import "./style.scss"; import classNames from "classnames"; import useDeviceDetect from "../../../hooks/useDeviceDetect"; +import Tooltip from "../../../components/Main/Tooltip/Tooltip"; +import SimpleBarChart from "../../../components/Chart/SimpleBarChart/SimpleBarChart"; interface MetricsProperties { rows: Data[]; - activeTab: number; - onChange: (newValue: string, tabId: string) => void; onActionClick: (name: string) => void; tabs: string[]; chartContainer: MutableRef | undefined; totalSeries: number, - tabId: string; sectionTitle: string; + tip?: string; tableHeaderCells: HeadCell[]; } const MetricsContent: FC = ({ rows, - activeTab, - onChange, - tabs: tabsProps, + tabs: tabsProps = [], chartContainer, totalSeries, - tabId, onActionClick, sectionTitle, + tip, tableHeaderCells, }) => { const { isMobile } = useDeviceDetect(); + const [activeTab, setActiveTab] = useState("table"); const tableCells = (row: Data) => ( = ({ ); const tabs = useMemo(() => tabsProps.map((t, i) => ({ - value: String(i), + value: t, label: t, icon: i === 0 ? : })), [tabsProps]); - const handleChangeTab = (newValue: string) => { - onChange(newValue, tabId); - }; - return (
= ({
{sectionTitle}
+ > + {!isMobile && tip && ( + } + > +
+
+ )} + {sectionTitle} +
-
- {activeTab === 0 && ( + + {activeTab === "table" && ( +
- )} - {activeTab === 1 && ( - v.name), - rows.map((v) => v.value), - rows.map((_, i) => i % 12 == 0 ? 1 : i % 10 == 0 ? 2 : 0), - ]} - container={chartContainer?.current || null} - configs={barOptions} - /> - )} -
+
+ )} + {activeTab === "graph" && ( +
+ ({ name, value }))}/> +
+ )}
); }; diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/MetricsContent/style.scss b/app/vmui/packages/vmui/src/pages/CardinalityPanel/MetricsContent/style.scss index 461eb81505..42a27aaeb3 100644 --- a/app/vmui/packages/vmui/src/pages/CardinalityPanel/MetricsContent/style.scss +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/MetricsContent/style.scss @@ -3,6 +3,33 @@ .vm-metrics-content { &-header { margin: -$padding-medium 0-$padding-medium 0; + + &__title { + display: flex; + align-items: center; + justify-content: flex-start; + } + + &__tip { + max-width: 300px; + white-space: normal; + padding: $padding-small; + line-height: 130%; + font-size: $font-size; + + p { + margin-bottom: $padding-small; + } + } + + &__tip-icon { + width: 12px; + display: flex; + align-items: center; + justify-content: center; + color: $color-primary; + margin-right: 4px; + } } &_mobile &-header { @@ -30,4 +57,8 @@ &_mobile &__table { width: calc(100vw - ($padding-global * 2) - var(--scrollbar-width)); } + + &__chart { + padding-top: $padding-medium; + } } diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/Table/Table.tsx b/app/vmui/packages/vmui/src/pages/CardinalityPanel/Table/Table.tsx index 9fc7125ffa..d23d5f2b90 100644 --- a/app/vmui/packages/vmui/src/pages/CardinalityPanel/Table/Table.tsx +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/Table/Table.tsx @@ -1,9 +1,8 @@ import React, { FC, useState } from "preact/compat"; -import { ChangeEvent, MouseEvent } from "react"; +import { MouseEvent } from "react"; import { Data, Order, TableProps, } from "./types"; import { EnhancedTableHead } from "./TableHead"; import { getComparator, stableSort } from "./helpers"; -import classNames from "classnames"; const EnhancedTable: FC = ({ rows, @@ -14,7 +13,6 @@ const EnhancedTable: FC = ({ const [order, setOrder] = useState("desc"); const [orderBy, setOrderBy] = useState(defaultSortColumn); - const [selected, setSelected] = useState([]); const handleRequestSort = ( event: MouseEvent, @@ -25,45 +23,13 @@ const EnhancedTable: FC = ({ setOrderBy(property); }; - const handleSelectAllClick = (event: ChangeEvent) => { - if (event.target.checked) { - const newSelecteds = rows.map((n) => n.name) as string[]; - setSelected(newSelecteds); - return; - } - setSelected([]); - }; - - const handleClick = (name: string) => () => { - const selectedIndex = selected.indexOf(name); - let newSelected: readonly string[] = []; - - if (selectedIndex === -1) { - newSelected = newSelected.concat(selected, name); - } else if (selectedIndex === 0) { - newSelected = newSelected.concat(selected.slice(1)); - } else if (selectedIndex === selected.length - 1) { - newSelected = newSelected.concat(selected.slice(0, -1)); - } else if (selectedIndex > 0) { - newSelected = newSelected.concat( - selected.slice(0, selectedIndex), - selected.slice(selectedIndex + 1), - ); - } - - setSelected(newSelected); - }; - - const isSelected = (name: string) => selected.indexOf(name) !== -1; const sortedData = stableSort(rows, getComparator(order, orderBy)); return ( = ({ {sortedData.map((row) => ( {tableCells(row)} diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/Table/TableCells/TableCells.tsx b/app/vmui/packages/vmui/src/pages/CardinalityPanel/Table/TableCells/TableCells.tsx index ae37ab2c4c..120f5722a8 100644 --- a/app/vmui/packages/vmui/src/pages/CardinalityPanel/Table/TableCells/TableCells.tsx +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/Table/TableCells/TableCells.tsx @@ -23,7 +23,12 @@ const TableCells: FC = ({ row, totalSeries, onActionClick className="vm-table-cell" key={row.name} > - {row.name} + + {row.name} +
- {headCell.label} + { + headCell.info ? + +
+ {headCell.label} +
: <>{headCell.label} + } {headCell.id !== "action" && headCell.id !== "percentage" && (
, property: keyof Data) => void; - onSelectAllClick: (event: ChangeEvent) => void; order: Order; orderBy: string; rowCount: number; diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/appConfigurator.ts b/app/vmui/packages/vmui/src/pages/CardinalityPanel/appConfigurator.ts index f53be24ef5..9a938ebf1b 100644 --- a/app/vmui/packages/vmui/src/pages/CardinalityPanel/appConfigurator.ts +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/appConfigurator.ts @@ -1,11 +1,10 @@ -import { Containers, DefaultActiveTab, Tabs, TSDBStatus } from "./types"; +import { Containers, Tabs, TSDBStatus } from "./types"; import { useRef } from "preact/compat"; import { HeadCell } from "./Table/types"; interface AppState { tabs: Tabs; containerRefs: Containers; - defaultActiveTab: DefaultActiveTab, } export default class AppConfigurator { @@ -15,6 +14,7 @@ export default class AppConfigurator { constructor() { this.tsdbStatus = this.defaultTSDBStatus; this.tabsNames = ["table", "graph"]; + this.getDefaultState = this.getDefaultState.bind(this); } set tsdbStatusData(tsdbStatus: TSDBStatus) { @@ -29,6 +29,7 @@ export default class AppConfigurator { return { totalSeries: 0, totalLabelValuePairs: 0, + totalSeriesByAll: 0, seriesCountByMetricName: [], seriesCountByLabelName: [], seriesCountByFocusLabelValue: [], @@ -37,22 +38,26 @@ export default class AppConfigurator { }; } - keys(focusLabel: string | null): string[] { + keys(match?: string | null, focusLabel?: string | null): string[] { + const isMetric = match && /__name__=".+"/.test(match); + const isLabel = match && /{.+=".+"}/g.test(match); + const isMetricWithLabel = match && /__name__=".+", .+!=""/g.test(match); + let keys: string[] = []; - if (focusLabel) { + if (focusLabel || isMetricWithLabel) { keys = keys.concat("seriesCountByFocusLabelValue"); + } else if (isMetric) { + keys = keys.concat("labelValueCountByLabelName"); + } else if (isLabel) { + keys = keys.concat("seriesCountByMetricName", "seriesCountByLabelName"); + } else { + keys = keys.concat("seriesCountByMetricName", "seriesCountByLabelName", "seriesCountByLabelValuePair"); } - keys = keys.concat( - "seriesCountByMetricName", - "seriesCountByLabelName", - "seriesCountByLabelValuePair", - "labelValueCountByLabelName", - ); return keys; } - get defaultState(): AppState { - return this.keys("job").reduce((acc, cur) => { + getDefaultState(match?: string | null, label?: string | null): AppState { + return this.keys(match, label).reduce((acc, cur) => { return { ...acc, tabs: { @@ -63,15 +68,10 @@ export default class AppConfigurator { ...acc.containerRefs, [cur]: useRef(null), }, - defaultActiveTab: { - ...acc.defaultActiveTab, - [cur]: 0, - }, }; }, { tabs: {} as Tabs, containerRefs: {} as Containers, - defaultActiveTab: {} as DefaultActiveTab, } as AppState); } @@ -85,6 +85,53 @@ export default class AppConfigurator { }; } + get sectionsTips(): Record { + return { + seriesCountByMetricName: ` +

+ This table returns a list of metrics with the highest cardinality. + The cardinality of a metric is the number of time series associated with that metric, + where each time series is defined as a unique combination of key-value label pairs. +

+

+ When looking to reduce the number of active series in your data source, + you can start by inspecting individual metrics with high cardinality + (i.e. that have lots of active time series associated with them), + since that single metric contributes a large fraction of the series that make up your total series count. +

`, + seriesCountByLabelName: ` +

+ This table returns a list of the labels with the highest number of series. +

+

+ Use this table to identify labels that are storing dimensions with high cardinality + (many different label values). +

+

+ It is recommended to choose labels such that they have a finite set of values, + since every unique combination of key-value label pairs creates a new time series + and therefore can dramatically increase the number of time series in your system. +

`, + seriesCountByFocusLabelValue: ` +

+ This table returns a list of unique label values per selected label. +

+

+ Use this table to identify label values that are storing per each selected series. +

`, + labelValueCountByLabelName: "", + seriesCountByLabelValuePair: ` +

+ This table returns a list of the label values pairs with the highest number of series. +

+

+ Use this table to identify unique label values pairs. This helps to identify same labels + is applied to count timeseries in your system, since every unique combination of key-value label pairs + creates a new time series and therefore can dramatically increase the number of time series in your system +

`, + }; + } + get tablesHeaders(): Record { return { seriesCountByMetricName: METRIC_NAMES_HEADERS, @@ -114,11 +161,12 @@ const METRIC_NAMES_HEADERS = [ }, { id: "percentage", - label: "Percent of series", + label: "Share in total", + info: "Shows the share of a metric to the total number of series" }, { id: "action", - label: "Action", + label: "", } ] as HeadCell[]; @@ -133,11 +181,12 @@ const LABEL_NAMES_HEADERS = [ }, { id: "percentage", - label: "Percent of series", + label: "Share in total", + info: "Shows the share of the label to the total number of series" }, { id: "action", - label: "Action", + label: "", } ] as HeadCell[]; @@ -152,12 +201,12 @@ const FOCUS_LABEL_VALUES_HEADERS = [ }, { id: "percentage", - label: "Percent of series", + label: "Share in total", }, { disablePadding: false, id: "action", - label: "Action", + label: "", numeric: false, } ] as HeadCell[]; @@ -173,11 +222,12 @@ export const LABEL_VALUE_PAIRS_HEADERS = [ }, { id: "percentage", - label: "Percent of series", + label: "Share in total", + info: "Shows the share of the label value pair to the total number of series" }, { id: "action", - label: "Action", + label: "", } ] as HeadCell[]; @@ -192,6 +242,6 @@ export const LABEL_NAMES_WITH_UNIQUE_VALUES_HEADERS = [ }, { id: "action", - label: "Action", + label: "", } ] as HeadCell[]; diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/helpers.ts b/app/vmui/packages/vmui/src/pages/CardinalityPanel/helpers.ts index 930f12f371..b7c6a3e40e 100644 --- a/app/vmui/packages/vmui/src/pages/CardinalityPanel/helpers.ts +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/helpers.ts @@ -1,20 +1,24 @@ import { QueryUpdater } from "./types"; export const queryUpdater: QueryUpdater = { - seriesCountByMetricName: (focusLabel: string | null, query: string): string => { + seriesCountByMetricName: ({ query }): string => { return getSeriesSelector("__name__", query); }, - seriesCountByLabelName: (focusLabel: string | null, query: string): string => `{${query}!=""}`, - seriesCountByFocusLabelValue: (focusLabel: string | null, query: string): string => { + seriesCountByLabelName: ({ query }): string => { + return `{${query}!=""}`; + }, + seriesCountByFocusLabelValue: ({ query, focusLabel }): string => { return getSeriesSelector(focusLabel, query); }, - seriesCountByLabelValuePair: (focusLabel: string | null, query: string): string => { + seriesCountByLabelValuePair: ({ query }): string => { const a = query.split("="); const label = a[0]; const value = a.slice(1).join("="); return getSeriesSelector(label, value); }, - labelValueCountByLabelName: (focusLabel: string | null, query: string): string => `{${query}!=""}`, + labelValueCountByLabelName: ({ query, match }): string => { + return `${match.replace("}", "")}, ${query}!=""}`; + }, }; const getSeriesSelector = (label: string | null, value: string): string => { diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/hooks/useCardinalityFetch.ts b/app/vmui/packages/vmui/src/pages/CardinalityPanel/hooks/useCardinalityFetch.ts index 1bec1c209f..55c126167b 100644 --- a/app/vmui/packages/vmui/src/pages/CardinalityPanel/hooks/useCardinalityFetch.ts +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/hooks/useCardinalityFetch.ts @@ -3,8 +3,10 @@ import { useAppState } from "../../../state/common/StateContext"; import { useEffect, useState } from "preact/compat"; import { CardinalityRequestsParams, getCardinalityInfo } from "../../../api/tsdb"; import { TSDBStatus } from "../types"; -import { useCardinalityState } from "../../../state/cardinality/CardinalityStateContext"; import AppConfigurator from "../appConfigurator"; +import { useSearchParams } from "react-router-dom"; +import dayjs from "dayjs"; +import { DATE_FORMAT } from "../../../constants/date"; export const useFetchQuery = (): { fetchUrl?: string[], @@ -13,33 +15,43 @@ export const useFetchQuery = (): { appConfigurator: AppConfigurator, } => { const appConfigurator = new AppConfigurator(); - const { topN, extraLabel, match, date, runQuery, focusLabel } = useCardinalityState(); + + const [searchParams] = useSearchParams(); + const match = searchParams.get("match"); + const focusLabel = searchParams.get("focusLabel"); + const topN = +(searchParams.get("topN") || 10); + const date = searchParams.get("date") || dayjs().tz().format(DATE_FORMAT); const { serverUrl } = useAppState(); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(); const [tsdbStatus, setTSDBStatus] = useState(appConfigurator.defaultTSDBStatus); - useEffect(() => { - if (error) { - setTSDBStatus(appConfigurator.defaultTSDBStatus); - setIsLoading(false); - } - }, [error]); - const fetchCardinalityInfo = async (requestParams: CardinalityRequestsParams) => { if (!serverUrl) return; setError(""); setIsLoading(true); setTSDBStatus(appConfigurator.defaultTSDBStatus); + + const defaultParams = { date: requestParams.date, topN: 0, match: "", focusLabel: "" } as CardinalityRequestsParams; const url = getCardinalityInfo(serverUrl, requestParams); + const urlDefault = getCardinalityInfo(serverUrl, defaultParams); try { const response = await fetch(url); const resp = await response.json(); + const responseTotal = await fetch(urlDefault); + const respTotals = await responseTotal.json(); if (response.ok) { const { data } = resp; - setTSDBStatus({ ...data }); + const { totalSeries } = respTotals.data; + const result = { ...data } as TSDBStatus; + result.totalSeriesByAll = totalSeries; + + const name = match?.replace(/[{}"]/g, ""); + result.seriesCountByLabelValuePair = result.seriesCountByLabelValuePair.filter(s => s.name !== name); + + setTSDBStatus(result); setIsLoading(false); } else { setError(resp.error); @@ -54,8 +66,15 @@ export const useFetchQuery = (): { useEffect(() => { - fetchCardinalityInfo({ topN, extraLabel, match, date, focusLabel }); - }, [serverUrl, runQuery, date]); + fetchCardinalityInfo({ topN, match, date, focusLabel }); + }, [serverUrl, match, focusLabel, topN, date]); + + useEffect(() => { + if (error) { + setTSDBStatus(appConfigurator.defaultTSDBStatus); + setIsLoading(false); + } + }, [error]); appConfigurator.tsdbStatusData = tsdbStatus; return { isLoading, appConfigurator: appConfigurator, error }; diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/hooks/useSetQueryParams.ts b/app/vmui/packages/vmui/src/pages/CardinalityPanel/hooks/useSetQueryParams.ts deleted file mode 100644 index abcdf5354c..0000000000 --- a/app/vmui/packages/vmui/src/pages/CardinalityPanel/hooks/useSetQueryParams.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useEffect } from "react"; -import { useCardinalityState } from "../../../state/cardinality/CardinalityStateContext"; -import { compactObject } from "../../../utils/object"; -import { useSearchParams } from "react-router-dom"; - -export const useSetQueryParams = () => { - const { topN, match, date, focusLabel, extraLabel } = useCardinalityState(); - const [, setSearchParams] = useSearchParams(); - - const setSearchParamsFromState = () => { - const params = compactObject({ - topN, - date, - match, - extraLabel, - focusLabel, - }); - - setSearchParams(params as Record); - }; - - useEffect(setSearchParamsFromState, [topN, match, date, focusLabel, extraLabel]); - useEffect(setSearchParamsFromState, []); -}; diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/index.tsx b/app/vmui/packages/vmui/src/pages/CardinalityPanel/index.tsx index 1088a9fc42..c333f0d393 100644 --- a/app/vmui/packages/vmui/src/pages/CardinalityPanel/index.tsx +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/index.tsx @@ -1,75 +1,48 @@ -import React, { FC, useState } from "react"; +import React, { FC } from "react"; import { useFetchQuery } from "./hooks/useCardinalityFetch"; import { queryUpdater } from "./helpers"; import { Data } from "./Table/types"; import CardinalityConfigurator from "./CardinalityConfigurator/CardinalityConfigurator"; import Spinner from "../../components/Main/Spinner/Spinner"; -import { useCardinalityDispatch, useCardinalityState } from "../../state/cardinality/CardinalityStateContext"; import MetricsContent from "./MetricsContent/MetricsContent"; -import { DefaultActiveTab, Tabs, TSDBStatus, Containers } from "./types"; -import { useSetQueryParams } from "./hooks/useSetQueryParams"; +import { Tabs, TSDBStatus, Containers } from "./types"; import Alert from "../../components/Main/Alert/Alert"; import "./style.scss"; import classNames from "classnames"; import useDeviceDetect from "../../hooks/useDeviceDetect"; +import { useSearchParams } from "react-router-dom"; +import { + TipCardinalityOfLabel, + TipCardinalityOfSingle, + TipHighNumberOfSeries, + TipHighNumberOfValues +} from "./CardinalityTips"; const spinnerMessage = `Please wait while cardinality stats is calculated. This may take some time if the db contains big number of time series.`; const Index: FC = () => { const { isMobile } = useDeviceDetect(); - const { topN, match, date, focusLabel } = useCardinalityState(); - const cardinalityDispatch = useCardinalityDispatch(); - useSetQueryParams(); - const configError = ""; - const [query, setQuery] = useState(match || ""); - const [queryHistoryIndex, setQueryHistoryIndex] = useState(0); - const [queryHistory, setQueryHistory] = useState([]); - - const onRunQuery = () => { - setQueryHistory(prev => [...prev, query]); - setQueryHistoryIndex(prev => prev + 1); - cardinalityDispatch({ type: "SET_MATCH", payload: query }); - cardinalityDispatch({ type: "RUN_QUERY" }); - }; - - const onSetHistory = (step: number) => { - const newIndexHistory = queryHistoryIndex + step; - if (newIndexHistory < 0 || newIndexHistory >= queryHistory.length) return; - setQueryHistoryIndex(newIndexHistory); - setQuery(queryHistory[newIndexHistory]); - }; - - const onTopNChange = (value: string) => { - cardinalityDispatch({ type: "SET_TOP_N", payload: +value }); - }; - - const onFocusLabelChange = (value: string) => { - cardinalityDispatch({ type: "SET_FOCUS_LABEL", payload: value }); - }; + const [searchParams, setSearchParams] = useSearchParams(); + const showTips = searchParams.get("tips") || ""; + const match = searchParams.get("match") || ""; + const focusLabel = searchParams.get("focusLabel") || ""; const { isLoading, appConfigurator, error } = useFetchQuery(); - const [stateTabs, setTab] = useState(appConfigurator.defaultState.defaultActiveTab); - const { tsdbStatusData, defaultState, tablesHeaders } = appConfigurator; - const handleTabChange = (newValue: string, tabId: string) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - setTab({ ...stateTabs, [tabId]: +newValue }); - }; + const { tsdbStatusData, getDefaultState, tablesHeaders, sectionsTips } = appConfigurator; + const defaultState = getDefaultState(match, focusLabel); - const handleFilterClick = (key: string) => (name: string) => { - const query = queryUpdater[key](focusLabel, name); - setQuery(query); - setQueryHistory(prev => [...prev, query]); - setQueryHistoryIndex(prev => prev + 1); - cardinalityDispatch({ type: "SET_MATCH", payload: query }); - let newFocusLabel = ""; + const handleFilterClick = (key: string) => (query: string) => { + const value = queryUpdater[key]({ query, focusLabel, match }); + searchParams.set("match", value); if (key === "labelValueCountByLabelName" || key == "seriesCountByLabelName") { - newFocusLabel = name; + searchParams.set("focusLabel", query); } - cardinalityDispatch({ type: "SET_FOCUS_LABEL", payload: newFocusLabel }); - cardinalityDispatch({ type: "RUN_QUERY" }); + if (key == "seriesCountByFocusLabelValue") { + searchParams.set("focusLabel", ""); + } + setSearchParams(searchParams); }; return ( @@ -81,35 +54,33 @@ const Index: FC = () => { > {isLoading && } + {showTips && ( +
+ {!match && !focusLabel && } + {match && !focusLabel && } + {!match && !focusLabel && } + {focusLabel && } +
+ )} + {error && {error}} - {appConfigurator.keys(focusLabel).map((keyName) => ( + {appConfigurator.keys(match, focusLabel).map((keyName) => ( ]} totalSeries={appConfigurator.totalSeries(keyName)} - tabId={keyName} tableHeaderCells={tablesHeaders[keyName]} /> ))} diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/style.scss b/app/vmui/packages/vmui/src/pages/CardinalityPanel/style.scss index 4c9730e77e..f85dc95eb8 100644 --- a/app/vmui/packages/vmui/src/pages/CardinalityPanel/style.scss +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/style.scss @@ -5,7 +5,17 @@ align-items: flex-start; gap: $padding-medium; - &_mobile { + &_mobile, &_mobile &-tips { gap: $padding-small; } + + &-tips { + display: inline-flex; + flex-wrap: wrap; + align-content: flex-start; + justify-content: flex-start; + gap: $padding-medium; + flex-grow: 1; + width: 100%; + } } diff --git a/app/vmui/packages/vmui/src/pages/CardinalityPanel/types.ts b/app/vmui/packages/vmui/src/pages/CardinalityPanel/types.ts index a3ecadee61..14bd3316e3 100644 --- a/app/vmui/packages/vmui/src/pages/CardinalityPanel/types.ts +++ b/app/vmui/packages/vmui/src/pages/CardinalityPanel/types.ts @@ -3,6 +3,7 @@ import { MutableRef } from "preact/hooks"; export interface TSDBStatus { totalSeries: number; totalLabelValuePairs: number; + totalSeriesByAll: number, seriesCountByMetricName: TopHeapEntry[]; seriesCountByLabelName: TopHeapEntry[]; seriesCountByFocusLabelValue: TopHeapEntry[]; @@ -12,11 +13,17 @@ export interface TSDBStatus { export interface TopHeapEntry { name: string; - count: number; + value: number; +} + +interface QueryUpdaterArgs { + query: string; + focusLabel: string; + match: string; } export type QueryUpdater = { - [key: string]: (focusLabel: string | null, query: string) => string, + [key: string]: (args: QueryUpdaterArgs) => string, } export interface Tabs { @@ -34,11 +41,3 @@ export interface Containers { seriesCountByLabelValuePair: MutableRef; labelValueCountByLabelName: MutableRef; } - -export interface DefaultActiveTab { - seriesCountByMetricName: number; - seriesCountByLabelName: number; - seriesCountByFocusLabelValue: number; - seriesCountByLabelValuePair: number; - labelValueCountByLabelName: number; -} diff --git a/app/vmui/packages/vmui/src/state/cardinality/CardinalityStateContext.tsx b/app/vmui/packages/vmui/src/state/cardinality/CardinalityStateContext.tsx deleted file mode 100644 index d66d3feb02..0000000000 --- a/app/vmui/packages/vmui/src/state/cardinality/CardinalityStateContext.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React, { createContext, FC, useContext, useMemo, useReducer } from "preact/compat"; -import { Action, CardinalityState, initialState, reducer } from "./reducer"; -import { Dispatch } from "react"; - -type CardinalityStateContextType = { state: CardinalityState, dispatch: Dispatch }; - -export const CardinalityStateContext = createContext({} as CardinalityStateContextType); - -export const useCardinalityState = (): CardinalityState => useContext(CardinalityStateContext).state; -export const useCardinalityDispatch = (): Dispatch => useContext(CardinalityStateContext).dispatch; - -export const CardinalityStateProvider: FC = ({ children }) => { - const [state, dispatch] = useReducer(reducer, initialState); - - const contextValue = useMemo(() => { - return { state, dispatch }; - }, [state, dispatch]); - - return - {children} - ; -}; - - diff --git a/app/vmui/packages/vmui/src/state/cardinality/reducer.ts b/app/vmui/packages/vmui/src/state/cardinality/reducer.ts deleted file mode 100644 index 86531cc9ef..0000000000 --- a/app/vmui/packages/vmui/src/state/cardinality/reducer.ts +++ /dev/null @@ -1,67 +0,0 @@ -import dayjs from "dayjs"; -import { getQueryStringValue } from "../../utils/query-string"; -import { DATE_FORMAT } from "../../constants/date"; - -export interface CardinalityState { - runQuery: number, - topN: number - date: string | null - match: string | null - extraLabel: string | null - focusLabel: string | null -} - -export type Action = - | { type: "SET_TOP_N", payload: number } - | { type: "SET_DATE", payload: string | null } - | { type: "SET_MATCH", payload: string | null } - | { type: "SET_EXTRA_LABEL", payload: string | null } - | { type: "SET_FOCUS_LABEL", payload: string | null } - | { type: "RUN_QUERY" } - - -export const initialState: CardinalityState = { - runQuery: 0, - topN: getQueryStringValue("topN", 10) as number, - date: getQueryStringValue("date", dayjs().tz().format(DATE_FORMAT)) as string, - focusLabel: getQueryStringValue("focusLabel", "") as string, - match: getQueryStringValue("match", "") as string, - extraLabel: getQueryStringValue("extra_label", "") as string, -}; - -export function reducer(state: CardinalityState, action: Action): CardinalityState { - switch (action.type) { - case "SET_TOP_N": - return { - ...state, - topN: action.payload - }; - case "SET_DATE": - return { - ...state, - date: action.payload - }; - case "SET_MATCH": - return { - ...state, - match: action.payload - }; - case "SET_EXTRA_LABEL": - return { - ...state, - extraLabel: action.payload - }; - case "SET_FOCUS_LABEL": - return { - ...state, - focusLabel: action.payload, - }; - case "RUN_QUERY": - return { - ...state, - runQuery: state.runQuery + 1 - }; - default: - throw new Error(); - } -}