use new methods in metrics browser and query field

This commit is contained in:
ismail simsek
2025-05-28 16:35:30 +02:00
parent d05bfc5d41
commit 8e32124a3a
9 changed files with 327 additions and 1088 deletions
@@ -30,7 +30,7 @@ function setup(app: CoreApp): { onRunQuery: jest.Mock } {
start: () => Promise.resolve([]),
syntax: () => {},
getLabelKeys: () => [],
metrics: [],
retrieveMetrics: () => [],
},
} as unknown as PrometheusDatasource;
const onRunQuery = jest.fn();
@@ -29,7 +29,7 @@ const defaultProps = {
start: () => Promise.resolve([]),
syntax: () => {},
getLabelKeys: () => [],
metrics: [],
retrieveMetrics: () => [],
},
} as unknown as PrometheusDatasource,
query: {
@@ -44,7 +44,7 @@ export const PromQueryField = (props: PromQueryFieldProps) => {
const [labelBrowserVisible, setLabelBrowserVisible] = useState(false);
const updateLanguage = useCallback(() => {
if (languageProvider.metrics) {
if (languageProvider.retrieveMetrics()) {
setSyntaxLoaded(true);
}
}, [languageProvider]);
@@ -32,20 +32,14 @@ Object.defineProperty(window, 'localStorage', { value: localStorageMock });
const setupLanguageProviderMock = () => {
const mockTimeRange = getMockTimeRange();
const mockLanguageProvider = {
metrics: ['metric1', 'metric2', 'metric3'],
labelKeys: ['__name__', 'instance', 'job', 'service'],
metricsMetadata: {
retrieveMetrics: () => ['metric1', 'metric2', 'metric3'],
retrieveLabelKeys: () => ['__name__', 'instance', 'job', 'service'],
retrieveMetricsMetadata: () => ({
metric1: { type: 'counter', help: 'Test metric 1' },
metric2: { type: 'gauge', help: 'Test metric 2' },
},
fetchLabels: jest.fn().mockResolvedValue(['__name__', 'instance', 'job', 'service']),
fetchSeriesLabelsMatch: jest.fn().mockResolvedValue({
__name__: ['metric1', 'metric2'],
instance: ['instance1', 'instance2'],
job: ['job1', 'job2'],
service: ['service1', 'service2'],
}),
fetchSeriesValuesWithMatch: jest.fn().mockImplementation((_timeRange: TimeRange, label: string) => {
queryLabelKeys: jest.fn().mockResolvedValue(['__name__', 'instance', 'job', 'service']),
queryLabelValues: jest.fn().mockImplementation((_timeRange: TimeRange, label: string) => {
if (label === 'job') {
return Promise.resolve(['grafana', 'prometheus']);
}
@@ -57,10 +51,6 @@ const setupLanguageProviderMock = () => {
}
return Promise.resolve([]);
}),
fetchLabelsWithMatch: jest.fn().mockResolvedValue({
job: ['job1', 'job2'],
instance: ['instance1', 'instance2'],
}),
} as unknown as PrometheusLanguageProviderInterface;
return { mockTimeRange, mockLanguageProvider };
@@ -298,34 +288,54 @@ describe('MetricsBrowserContext', () => {
describe('selector operations', () => {
it('should clear all selections', async () => {
const user = userEvent.setup();
const { renderWithProvider } = setupTest();
const { renderWithProvider, mockLanguageProvider } = setupTest();
renderWithProvider(<TestComponent />);
// Wait for component to be ready
// Wait for initial data load
await waitFor(() => {
expect(screen.getByTestId('metrics-count').textContent).toBe('3');
});
// Make selections
// Step 1: Select a metric
await user.click(screen.getByTestId('select-metric'));
await user.click(screen.getByTestId('select-label'));
await user.click(screen.getByTestId('select-label-value'));
// Verify selections
await waitFor(() => {
expect(mockLanguageProvider.queryLabelKeys).toHaveBeenCalled();
expect(screen.getByTestId('selected-metric').textContent).toBe('metric1');
expect(screen.getByTestId('selected-label-keys').textContent).toBe('job');
expect(screen.getByTestId('selector').textContent).not.toBe('{}');
});
// Clear all selections
// Step 2: Select a label
await user.click(screen.getByTestId('select-label'));
await waitFor(() => {
expect(mockLanguageProvider.queryLabelValues).toHaveBeenCalledWith(
expect.anything(),
'job',
expect.anything(),
expect.anything()
);
expect(screen.getByTestId('selected-label-keys').textContent).toBe('job');
});
// Step 3: Select a label value
await user.click(screen.getByTestId('select-label-value'));
await waitFor(() => {
expect(screen.getByTestId('selector').textContent).toContain('job="grafana"');
});
// Step 4: Clear all selections
await user.click(screen.getByTestId('clear'));
// Verify all fields are cleared
// Verify everything is cleared
await waitFor(() => {
// Check that all selections are cleared
expect(screen.getByTestId('selected-metric').textContent).toBe('');
expect(screen.getByTestId('selected-label-keys').textContent).toBe('');
expect(screen.getByTestId('selector').textContent).toBe('{}');
// Verify localStorage was cleared
const mockCalls = localStorageMock.setItem.mock.calls;
const lastCall = mockCalls[mockCalls.length - 1];
expect(lastCall[0]).toBe(LAST_USED_LABELS_KEY);
expect(JSON.parse(lastCall[1])).toEqual([]);
});
});
@@ -41,7 +41,7 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P
}
}, [timeRange]);
//Handler for error processing - logs the error and updates UI state
// Handler for error processing - logs the error and updates UI state
const handleError = useCallback((e: unknown, msg: string) => {
if (e instanceof Error) {
setErr(`${msg}: ${e.message}`);
@@ -54,10 +54,10 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P
// Get metadata details for a metric if available
const getMetricDetails = useCallback(
(metricName: string) => {
const meta = languageProvider.metricsMetadata;
const meta = languageProvider.retrieveMetricsMetadata();
return meta && meta[metricName] ? `(${meta[metricName].type}) ${meta[metricName].help}` : undefined;
},
[languageProvider.metricsMetadata]
[languageProvider]
);
// Builds a safe selector string from metric name and label values
@@ -89,11 +89,10 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P
const fetchMetrics = useCallback(
async (safeSelector?: string) => {
try {
const fetchedMetrics = await languageProvider.fetchSeriesValuesWithMatch(
const fetchedMetrics = await languageProvider.queryLabelValues(
timeRangeRef.current,
METRIC_LABEL,
safeSelector,
'MetricsBrowser_M',
effectiveLimit
);
return fetchedMetrics.map((m) => ({
@@ -114,11 +113,9 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P
async (safeSelector?: string) => {
try {
if (safeSelector) {
return Object.keys(
await languageProvider.fetchSeriesLabelsMatch(timeRangeRef.current, safeSelector, effectiveLimit)
);
return Object.keys(await languageProvider.queryLabelKeys(timeRangeRef.current, safeSelector, effectiveLimit));
} else {
return (await languageProvider.fetchLabels(timeRangeRef.current, undefined, effectiveLimit)) || [];
return (await languageProvider.queryLabelKeys(timeRangeRef.current, undefined, effectiveLimit)) || [];
}
} catch (e) {
handleError(e, 'Error fetching labels');
@@ -135,17 +132,18 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P
const newSelectedLabelValues: Record<string, string[]> = {};
for (const lk of labelKeys) {
try {
const values = await languageProvider.fetchSeriesValuesWithMatch(
const values = await languageProvider.queryLabelValues(
timeRangeRef.current,
lk,
safeSelector,
`MetricsBrowser_LV_${lk}`,
effectiveLimit
);
transformedLabelValues[lk] = values;
if (selectedLabelValues[lk]) {
newSelectedLabelValues[lk] = [...selectedLabelValues[lk]];
}
setErr('');
} catch (e) {
handleError(e, 'Error fetching label values');
}
@@ -294,11 +292,10 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P
if (selectedLabelKeys.length !== 0) {
for (const lk of selectedLabelKeys) {
try {
const fetchedLabelValues = await languageProvider.fetchSeriesValuesWithMatch(
const fetchedLabelValues = await languageProvider.queryLabelValues(
timeRange,
lk,
safeSelector,
`MetricsBrowser_LV_${lk}`,
effectiveLimit
);
@@ -314,6 +311,8 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P
fetchedLabelValues.includes(item)
);
}
setErr('');
} catch (e: unknown) {
handleError(e, 'Error fetching label values');
}
@@ -352,7 +351,7 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P
setErr('');
try {
const results = await languageProvider.fetchSeriesLabelsMatch(timeRangeRef.current, selector, effectiveLimit);
const results = await languageProvider.queryLabelKeys(timeRangeRef.current, selector, effectiveLimit);
setValidationStatus(`Selector is valid (${Object.keys(results).length} labels found)`);
} catch (e) {
handleError(e, 'Validation failed');
@@ -8,7 +8,7 @@ import { useMetricsState } from './useMetricsState';
// Mock implementations
const createMockLanguageProvider = (metrics: string[] = []): PrometheusLanguageProviderInterface =>
({
metrics,
retrieveMetrics: () => metrics,
}) as unknown as PrometheusLanguageProviderInterface;
const createMockDatasource = (lookupsDisabled = false): PrometheusDatasource =>
@@ -25,7 +25,7 @@ export function useMetricsState(
syntaxLoaded: boolean
) {
return useMemo(() => {
const hasMetrics = languageProvider.metrics.length > 0;
const hasMetrics = languageProvider.retrieveMetrics().length > 0;
const chooserText = getChooserText(datasource.lookupsDisabled, syntaxLoaded, hasMetrics);
const buttonDisabled = !(syntaxLoaded && hasMetrics);
@@ -34,5 +34,5 @@ export function useMetricsState(
chooserText,
buttonDisabled,
};
}, [languageProvider.metrics, datasource.lookupsDisabled, syntaxLoaded]);
}, [languageProvider, datasource.lookupsDisabled, syntaxLoaded]);
}
@@ -1,12 +1,15 @@
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/language_provider.mock.ts
export class EmptyLanguageProviderMock {
metrics = [];
constructor() {}
start() {
return new Promise((resolve) => {
resolve('');
});
}
getLabelKeys = jest.fn().mockReturnValue([]);
getLabelValues = jest.fn().mockReturnValue([]);
getSeries = jest.fn().mockReturnValue({ __name__: [] });
@@ -17,4 +20,5 @@ export class EmptyLanguageProviderMock {
fetchLabelsWithMatch = jest.fn().mockReturnValue([]);
fetchLabels = jest.fn();
loadMetricsMetadata = jest.fn();
retrieveMetrics = jest.fn().mockReturnValue(['metric']);
}