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) => (
-
{
- // Resetting search to prevent empty results
- setLabelSearchTerm('');
- onLabelKeyClick(name);
- }}
- searchTerm={labelSearchTerm}
- />
- ))}
-
+ {isLoadingLabelKeys ? (
+
+
+
+ ) : (
+
+ {filteredLabelKeys.map((label) => (
+
{
+ // Resetting search to prevent empty results
+ setLabelSearchTerm('');
+ onLabelKeyClick(name);
+ }}
+ searchTerm={labelSearchTerm}
+ />
+ ))}
+
+ )}
);
}
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),
}),
};
};