Prometheus: Metrics browser (#33847)

* [WIP] Metrics browser

* Removed unused import

* Metrics selection logic

* Remove redundant tests

All data is fetched now regardless to the current range so test for checking reloading the data on the range change are no longer relevant.

* Remove commented out code blocks

* Add issue number to todos

Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>
This commit is contained in:
David
2021-05-12 11:49:20 +02:00
committed by GitHub
parent 8d442c9b44
commit 4b0b69292e
15 changed files with 1171 additions and 332 deletions

View File

@@ -2,10 +2,10 @@
import RCCascader from 'rc-cascader';
import React from 'react';
import PromQlLanguageProvider from '../language_provider';
import PromQueryField, { groupMetricsByPrefix, RECORDING_RULES_GROUP } from './PromQueryField';
import { DataSourceInstanceSettings, dateTime } from '@grafana/data';
import PromQueryField from './PromQueryField';
import { DataSourceInstanceSettings } from '@grafana/data';
import { PromOptions } from '../types';
import { fireEvent, render, screen } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
describe('PromQueryField', () => {
beforeAll(() => {
@@ -18,6 +18,8 @@ describe('PromQueryField', () => {
languageProvider: {
start: () => Promise.resolve([]),
syntax: () => {},
getLabelKeys: () => [],
metrics: [],
},
} as unknown) as DataSourceInstanceSettings<PromOptions>;
@@ -40,6 +42,8 @@ describe('PromQueryField', () => {
languageProvider: {
start: () => Promise.resolve([]),
syntax: () => {},
getLabelKeys: () => [],
metrics: [],
},
} as unknown) as DataSourceInstanceSettings<PromOptions>;
const queryField = render(
@@ -75,8 +79,6 @@ describe('PromQueryField', () => {
/>
);
checkMetricsInCascader(await screen.findByRole('button'), metrics);
const changedMetrics = ['baz', 'moo'];
queryField.rerender(
<PromQueryField
@@ -88,162 +90,9 @@ describe('PromQueryField', () => {
/>
);
// If we check the cascader right away it should be in loading state
let cascader = screen.getByRole('button');
expect(cascader.textContent).toContain('Loading');
checkMetricsInCascader(await screen.findByRole('button'), changedMetrics);
});
it('does not refreshes metrics when after rounding to minute time range does not change', async () => {
const defaultProps = {
query: { expr: '', refId: '' },
onRunQuery: () => {},
onChange: () => {},
history: [],
};
const metrics = ['foo', 'bar'];
const changedMetrics = ['foo', 'baz'];
const range = {
from: dateTime('2020-10-28T00:00:00Z'),
to: dateTime('2020-10-28T01:00:00Z'),
};
const languageProvider = makeLanguageProvider({ metrics: [metrics, changedMetrics] });
const queryField = render(
<PromQueryField
// @ts-ignore
datasource={{ languageProvider }}
range={{
...range,
raw: range,
}}
{...defaultProps}
/>
);
checkMetricsInCascader(await screen.findByRole('button'), metrics);
const newRange = {
from: dateTime('2020-10-28T00:00:01Z'),
to: dateTime('2020-10-28T01:00:01Z'),
};
queryField.rerender(
<PromQueryField
// @ts-ignore
datasource={{ languageProvider }}
range={{
...newRange,
raw: newRange,
}}
{...defaultProps}
/>
);
let cascader = screen.getByRole('button');
// Should not show loading
expect(cascader.textContent).toContain('Metrics');
checkMetricsInCascader(await screen.findByRole('button'), metrics);
});
it('refreshes metrics when time range changes but dont show loading state', async () => {
const defaultProps = {
query: { expr: '', refId: '' },
onRunQuery: () => {},
onChange: () => {},
history: [],
};
const metrics = ['foo', 'bar'];
const changedMetrics = ['baz', 'moo'];
const range = {
from: dateTime('2020-10-28T00:00:00Z'),
to: dateTime('2020-10-28T01:00:00Z'),
};
const languageProvider = makeLanguageProvider({ metrics: [metrics, changedMetrics] });
const queryField = render(
<PromQueryField
// @ts-ignore
datasource={{ languageProvider }}
range={{
...range,
raw: range,
}}
{...defaultProps}
/>
);
checkMetricsInCascader(await screen.findByRole('button'), metrics);
const newRange = {
from: dateTime('2020-10-28T01:00:00Z'),
to: dateTime('2020-10-28T02:00:00Z'),
};
queryField.rerender(
<PromQueryField
// @ts-ignore
datasource={{ languageProvider }}
range={{
...newRange,
raw: newRange,
}}
{...defaultProps}
/>
);
let cascader = screen.getByRole('button');
// Should not show loading
expect(cascader.textContent).toContain('Metrics');
checkMetricsInCascader(cascader, metrics);
});
});
describe('groupMetricsByPrefix()', () => {
it('returns an empty group for no metrics', () => {
expect(groupMetricsByPrefix([])).toEqual([]);
});
it('returns options grouped by prefix', () => {
expect(groupMetricsByPrefix(['foo_metric'])).toMatchObject([
{
value: 'foo',
children: [
{
value: 'foo_metric',
},
],
},
]);
});
it('returns options grouped by prefix with metadata', () => {
expect(groupMetricsByPrefix(['foo_metric'], { foo_metric: [{ type: 'TYPE', help: 'my help' }] })).toMatchObject([
{
value: 'foo',
children: [
{
value: 'foo_metric',
title: 'foo_metric\nTYPE\nmy help',
},
],
},
]);
});
it('returns options without prefix as toplevel option', () => {
expect(groupMetricsByPrefix(['metric'])).toMatchObject([
{
value: 'metric',
},
]);
});
it('returns recording rules grouped separately', () => {
expect(groupMetricsByPrefix([':foo_metric:'])).toMatchObject([
{
value: RECORDING_RULES_GROUP,
children: [
{
value: ':foo_metric:',
},
],
},
]);
// If we check the label browser right away it should be in loading state
let labelBrowser = screen.getByRole('button');
expect(labelBrowser.textContent).toContain('Loading');
});
});
@@ -254,17 +103,10 @@ function makeLanguageProvider(options: { metrics: string[][] }) {
metrics: [],
metricsMetadata: {},
lookupsDisabled: false,
getLabelKeys: () => [],
start() {
this.metrics = metricsStack.shift();
return Promise.resolve([]);
},
} as any) as PromQlLanguageProvider;
}
function checkMetricsInCascader(cascader: HTMLElement, metrics: string[]) {
fireEvent.keyDown(cascader, { keyCode: 40 });
let listNodes = screen.getAllByRole('menuitem');
for (const node of listNodes) {
expect(metrics).toContain(node.innerHTML);
}
}