diff --git a/packages/grafana-prometheus/src/components/metrics-browser/LabelSelector.tsx b/packages/grafana-prometheus/src/components/metrics-browser/LabelSelector.tsx index 08c7164bc04..58541d6b4f2 100644 --- a/packages/grafana-prometheus/src/components/metrics-browser/LabelSelector.tsx +++ b/packages/grafana-prometheus/src/components/metrics-browser/LabelSelector.tsx @@ -2,17 +2,18 @@ import { useMemo, useState } from 'react'; import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; -import { BrowserLabel as PromLabel, Input, Label, useStyles2 } from '@grafana/ui'; +import { BrowserLabel as PromLabel, Input, Label, useStyles2, Spinner } from '@grafana/ui'; import { METRIC_LABEL } from '../../constants'; import { useMetricsBrowser } from './MetricsBrowserContext'; -import { getStylesLabelSelector } from './styles'; +import { getStylesLabelSelector, getStylesMetricsBrowser } from './styles'; export function LabelSelector() { const styles = useStyles2(getStylesLabelSelector); + const sharedStyles = useStyles2(getStylesMetricsBrowser); const [labelSearchTerm, setLabelSearchTerm] = useState(''); - const { labelKeys, selectedLabelKeys, onLabelKeyClick } = useMetricsBrowser(); + const { labelKeys, isLoadingLabelKeys, selectedLabelKeys, onLabelKeyClick } = useMetricsBrowser(); const filteredLabelKeys = useMemo(() => { return labelKeys.filter( @@ -44,24 +45,29 @@ export function LabelSelector() { /> {/* Using fixed height here to prevent jumpy layout */} -
- {filteredLabelKeys.map((label) => ( -
+ {isLoadingLabelKeys ? ( +
+ +
+ ) : ( +
+ {filteredLabelKeys.map((label) => ( +
+ )} ); } diff --git a/packages/grafana-prometheus/src/components/metrics-browser/MetricsBrowserContext.tsx b/packages/grafana-prometheus/src/components/metrics-browser/MetricsBrowserContext.tsx index 0a0cedd9644..a541ed90ade 100644 --- a/packages/grafana-prometheus/src/components/metrics-browser/MetricsBrowserContext.tsx +++ b/packages/grafana-prometheus/src/components/metrics-browser/MetricsBrowserContext.tsx @@ -33,6 +33,8 @@ interface MetricsBrowserContextType { // Data and selection state metrics: Metric[]; labelKeys: string[]; + isLoadingLabelKeys: boolean; + isLoadingLabelValues: boolean; labelValues: Record; selectedMetric: string; selectedLabelKeys: string[]; @@ -78,6 +80,8 @@ export function MetricsBrowserProvider({ validationStatus, metrics, labelKeys, + isLoadingLabelKeys, + isLoadingLabelValues, labelValues, selectedMetric, selectedLabelKeys, @@ -109,6 +113,8 @@ export function MetricsBrowserProvider({ getSelector, metrics, labelKeys, + isLoadingLabelKeys, + isLoadingLabelValues, labelValues, selectedMetric, selectedLabelKeys, @@ -128,16 +134,18 @@ export function MetricsBrowserProvider({ setSeriesLimit, validationStatus, onChange, - metrics, getSelector, + metrics, labelKeys, + isLoadingLabelKeys, + isLoadingLabelValues, labelValues, selectedMetric, selectedLabelKeys, selectedLabelValues, + handleSelectedMetricChange, handleSelectedLabelKeyChange, handleSelectedLabelValueChange, - handleSelectedMetricChange, handleValidation, handleClear, ] diff --git a/packages/grafana-prometheus/src/components/metrics-browser/ValueSelector.tsx b/packages/grafana-prometheus/src/components/metrics-browser/ValueSelector.tsx index 726d40183dd..d2918498bec 100644 --- a/packages/grafana-prometheus/src/components/metrics-browser/ValueSelector.tsx +++ b/packages/grafana-prometheus/src/components/metrics-browser/ValueSelector.tsx @@ -3,17 +3,20 @@ import { FixedSizeList } from 'react-window'; import { selectors } from '@grafana/e2e-selectors'; import { t, Trans } from '@grafana/i18n'; -import { BrowserLabel as PromLabel, Input, Label, useStyles2 } from '@grafana/ui'; +import { BrowserLabel as PromLabel, Input, Label, useStyles2, Spinner } from '@grafana/ui'; import { LIST_ITEM_SIZE } from '../../constants'; import { useMetricsBrowser } from './MetricsBrowserContext'; -import { getStylesValueSelector } from './styles'; +import { getStylesMetricsBrowser, getStylesValueSelector } from './styles'; export function ValueSelector() { const styles = useStyles2(getStylesValueSelector); + const sharedStyles = useStyles2(getStylesMetricsBrowser); + const [valueSearchTerm, setValueSearchTerm] = useState(''); - const { labelValues, selectedLabelValues, onLabelValueClick, onLabelKeyClick } = useMetricsBrowser(); + const { labelValues, selectedLabelValues, isLoadingLabelValues, onLabelValueClick, onLabelKeyClick } = + useMetricsBrowser(); return (
@@ -38,63 +41,62 @@ export function ValueSelector() { data-testid={selectors.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.labelValuesFilter} />
-
- {Object.entries(labelValues).map(([lk, lv]) => { - if (!lk || !lv) { - console.error('label values are empty:', { lk, lv }); - return null; - } - return ( -
-
-
- lv[i]} - width={200} - className={styles.valueList} + {isLoadingLabelValues ? ( +
+ +
+ ) : ( +
+ {Object.entries(labelValues).map(([lk, lv]) => { + if (!lk || !lv) { + console.error('label values are empty:', { lk, lv }); + return null; + } + return ( +
- {({ index, style }) => { - const value = lv[index]; - const isSelected = selectedLabelValues[lk]?.includes(value); - return ( -
- onLabelValueClick(lk, name, !isSelected)} - searchTerm={valueSearchTerm} - /> -
- ); - }} - -
- ); - })} -
+
+
+ lv[i]} + width={200} + className={styles.valueList} + > + {({ index, style }) => { + const value = lv[index]; + const isSelected = selectedLabelValues[lk]?.includes(value); + return ( +
+ onLabelValueClick(lk, name, !isSelected)} + searchTerm={valueSearchTerm} + /> +
+ ); + }} +
+
+ ); + })} +
+ )} ); } diff --git a/packages/grafana-prometheus/src/components/metrics-browser/styles.ts b/packages/grafana-prometheus/src/components/metrics-browser/styles.ts index 4ef89b716b6..d60f3902f08 100644 --- a/packages/grafana-prometheus/src/components/metrics-browser/styles.ts +++ b/packages/grafana-prometheus/src/components/metrics-browser/styles.ts @@ -8,6 +8,12 @@ export const getStylesMetricsBrowser = (theme: GrafanaTheme2) => ({ padding: theme.spacing(1), width: '100%', }), + spinner: css({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: 120, + }), }); export const getStylesMetricSelector = (theme: GrafanaTheme2) => ({ diff --git a/packages/grafana-prometheus/src/components/metrics-browser/useMetricsLabelsValues.ts b/packages/grafana-prometheus/src/components/metrics-browser/useMetricsLabelsValues.ts index 08e91411972..6298ba41fe6 100644 --- a/packages/grafana-prometheus/src/components/metrics-browser/useMetricsLabelsValues.ts +++ b/packages/grafana-prometheus/src/components/metrics-browser/useMetricsLabelsValues.ts @@ -26,6 +26,8 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P const [lastSelectedLabelKey, setLastSelectedLabelKey] = useState(''); const [labelValues, setLabelValues] = useState>({}); const [selectedLabelValues, setSelectedLabelValues] = useState>({}); + const [isLoadingLabelKeys, setIsLoadingLabelKeys] = useState(false); + const [isLoadingLabelValues, setIsLoadingLabelValues] = useState(false); // Memoize the effective series limit to use the default when seriesLimit is empty const effectiveLimit = useMemo(() => seriesLimit, [seriesLimit]); @@ -163,6 +165,8 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P const transformedMetrics: Metric[] = await fetchMetrics(safeSelector); // Labels + setIsLoadingLabelKeys(true); + setIsLoadingLabelValues(true); const transformedLabelKeys: string[] = await fetchLabelKeys(safeSelector); // Selected Labels @@ -173,8 +177,10 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P setMetrics(transformedMetrics); setLabelKeys(transformedLabelKeys); + setIsLoadingLabelKeys(false); setSelectedLabelKeys(labelKeysInLocalStorage); setLabelValues(transformedLabelValues); + setIsLoadingLabelValues(false); }, [fetchLabelKeys, fetchLabelValues, fetchMetrics, loadSelectedLabelsFromStorage] ); @@ -209,8 +215,11 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P const selector = buildSafeSelector(newSelectedMetric, selectedLabelValues); try { const fetchedMetrics = await fetchMetrics(selector); + setIsLoadingLabelKeys(true); const fetchedLabelKeys = await fetchLabelKeys(selector); const newSelectedLabelKeys = selectedLabelKeys.filter((slk) => fetchedLabelKeys.includes(slk)); + + setIsLoadingLabelValues(true); const [transformedLabelValues, newSelectedLabelValues] = await fetchLabelValues( newSelectedLabelKeys, newSelectedMetric === '' ? undefined : selector @@ -219,8 +228,10 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P setMetrics(fetchedMetrics); setSelectedMetric(newSelectedMetric); setLabelKeys(fetchedLabelKeys); + setIsLoadingLabelKeys(false); setSelectedLabelKeys(newSelectedLabelKeys); setLabelValues(transformedLabelValues); + setIsLoadingLabelValues(false); setSelectedLabelValues(newSelectedLabelValues); } catch (e: unknown) { handleError(e, 'Error fetching labels'); @@ -240,6 +251,7 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P // Label key is not in the selectedLabelKeys. Let's add it. newSelectedLabelKeys.push(labelKey); const safeSelector = buildSafeSelector(selectedMetric, selectedLabelValues); + setIsLoadingLabelValues(true); const [values] = await fetchLabelValues([labelKey], safeSelector); newLabelValues[labelKey] = values[labelKey]; } else { @@ -252,6 +264,7 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P localStorage.setItem(LAST_USED_LABELS_KEY, JSON.stringify(newSelectedLabelKeys)); setSelectedLabelKeys(newSelectedLabelKeys); setLabelValues(newLabelValues); + setIsLoadingLabelValues(false); setSelectedLabelValues(newSelectedLabelValues); }; @@ -289,6 +302,7 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P // Fetch new values let newLabelValues: Record = {}; if (selectedLabelKeys.length !== 0) { + setIsLoadingLabelValues(true); for (const lk of selectedLabelKeys) { try { const fetchedLabelValues = await languageProvider.queryLabelValues( @@ -326,6 +340,7 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P // Fetch label keys // If there is no metric or label value selected fetch all the keys instead of creating a selector + setIsLoadingLabelKeys(true); let newLabelKeys: string[] = []; if (!safeSelector) { newLabelKeys = await fetchLabelKeys(undefined); @@ -337,9 +352,11 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P setMetrics(newMetrics); setLabelKeys(newLabelKeys); + setIsLoadingLabelKeys(false); setSelectedLabelKeys(newSelectedLabelKeys); setLastSelectedLabelKey(newLastSelectedLabelKey); setLabelValues(newLabelValues); + setIsLoadingLabelValues(false); setSelectedLabelValues(newSelectedLabelValues); }; @@ -384,6 +401,8 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P metrics, labelKeys, labelValues, + isLoadingLabelKeys, + isLoadingLabelValues, selectedMetric, selectedLabelKeys, selectedLabelValues, diff --git a/packages/grafana-prometheus/src/querybuilder/shared/QueryBuilderHints.tsx b/packages/grafana-prometheus/src/querybuilder/shared/QueryBuilderHints.tsx index d60dc22f278..9068f7bbd4e 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/QueryBuilderHints.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/QueryBuilderHints.tsx @@ -92,6 +92,7 @@ const getStyles = (theme: GrafanaTheme2) => { }), hint: css({ marginRight: theme.spacing(1), + marginBottom: theme.spacing(1), }), }; };