mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Prometheus Datasource: Improve Prom query variable editor (#58292)
* add prom query var editor with tests and migrations * fix migration, now query not expr * fix label_values migration * remove comments * fix label_values() variables order * update UI and use more clear language * fix tests * use null coalescing operators * allow users to query label values with label and metric if they have not set there flavor and version * use enums instead of numbers for readability * fix label&metrics switch * update type in qv editor * reuse datasource function to get all label names, getLabelNames(), prev named getTagKeys() * use getLabelNames in the query var editor * make label_values() variables label and metric more readable in the migration * fix tooltip for label_values to remove API reference * clean up tooltips and allow newlines in query_result function * change function wording and exprType to query type/qryType for readability * update prometheus query variable docs * Update public/app/plugins/datasource/prometheus/components/VariableQueryEditor.tsx Co-authored-by: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com> --------- Co-authored-by: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
1f984409a2
commit
eedcd7d5b1
@@ -6261,9 +6261,8 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
[0, 0, 0, "Do not use any type assertions.", "3"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "4"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
|
|
||||||
],
|
],
|
||||||
"public/app/plugins/datasource/prometheus/query_hints.ts:5381": [
|
"public/app/plugins/datasource/prometheus/query_hints.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||||
|
|||||||
@@ -23,17 +23,17 @@ For an introduction to templating and template variables, refer to the [Templati
|
|||||||
|
|
||||||
## Use query variables
|
## Use query variables
|
||||||
|
|
||||||
You can use variables of the type _Query_ to query Prometheus for a list of metrics, labels, or label values.
|
Use variables of the type _Query_ to query Prometheus for a list of metrics, labels, or label values.
|
||||||
|
|
||||||
You can use these Prometheus data source functions in the **Query** input field:
|
Select a Prometheus data source query type and enter the required inputs:
|
||||||
|
|
||||||
| Name | Description | Used API endpoints |
|
| Query Type | Input(\* required) | Description | Used API endpoints |
|
||||||
| ----------------------------- | ----------------------------------------------------------------------- | --------------------------------- |
|
| -------------- | ------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------- |
|
||||||
| `label_names()` | Returns a list of label names. | /api/v1/labels |
|
| `Label names` | none | Returns a list of all label names. | /api/v1/labels |
|
||||||
| `label_values(label)` | Returns a list of label values for the `label` in every metric. | /api/v1/label/`label`/values |
|
| `Label values` | `label`\*, `metric` | Returns a list of label values for the `label` in all metrics or the optional metric. | /api/v1/label/`label`/values or /api/v1/series |
|
||||||
| `label_values(metric, label)` | Returns a list of label values for the `label` in the specified metric. | /api/v1/series |
|
| `Metrics` | `metric` | Returns a list of metrics matching the specified `metric` regex. | /api/v1/label/\_\_name\_\_/values |
|
||||||
| `metrics(metric)` | Returns a list of metrics matching the specified `metric` regex. | /api/v1/label/\_\_name\_\_/values |
|
| `Query result` | `query` | Returns a list of Prometheus query result for the `query`. | /api/v1/query |
|
||||||
| `query_result(query)` | Returns a list of Prometheus query result for the `query`. | /api/v1/query |
|
| `Series query` | `metric`, `label` or both | Returns a list of time series associated with the entered data. | /api/v1/series |
|
||||||
|
|
||||||
For details on _metric names_, _label names_, and _label values_, refer to the [Prometheus documentation](http://prometheus.io/docs/concepts/data_model/#metric-names-and-labels).
|
For details on _metric names_, _label names_, and _label values_, refer to the [Prometheus documentation](http://prometheus.io/docs/concepts/data_model/#metric-names-and-labels).
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import React from 'react';
|
||||||
|
import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
|
||||||
|
|
||||||
|
import { PrometheusDatasource } from '../datasource';
|
||||||
|
|
||||||
|
import { PromVariableQueryEditor, Props } from './VariableQueryEditor';
|
||||||
|
|
||||||
|
const refId = 'PrometheusVariableQueryEditor-VariableQuery';
|
||||||
|
|
||||||
|
describe('PromVariableQueryEditor', () => {
|
||||||
|
let props: Props;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
props = {
|
||||||
|
datasource: {
|
||||||
|
hasLabelsMatchAPISupport: () => 1,
|
||||||
|
languageProvider: {
|
||||||
|
start: () => Promise.resolve([]),
|
||||||
|
syntax: () => {},
|
||||||
|
getLabelKeys: () => [],
|
||||||
|
metrics: [],
|
||||||
|
},
|
||||||
|
getInitHints: () => [],
|
||||||
|
} as unknown as PrometheusDatasource,
|
||||||
|
query: {
|
||||||
|
refId: 'test',
|
||||||
|
query: 'label_names()',
|
||||||
|
},
|
||||||
|
onRunQuery: () => {},
|
||||||
|
onChange: () => {},
|
||||||
|
history: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Displays a group of function options', async () => {
|
||||||
|
render(<PromVariableQueryEditor {...props} />);
|
||||||
|
|
||||||
|
const select = screen.getByLabelText('Query type').parentElement!;
|
||||||
|
await userEvent.click(select);
|
||||||
|
|
||||||
|
await waitFor(() => expect(screen.getAllByText('Label names')).toHaveLength(2));
|
||||||
|
await waitFor(() => expect(screen.getByText('Label values')).toBeInTheDocument());
|
||||||
|
await waitFor(() => expect(screen.getByText('Metrics')).toBeInTheDocument());
|
||||||
|
await waitFor(() => expect(screen.getByText('Query result')).toBeInTheDocument());
|
||||||
|
await waitFor(() => expect(screen.getByText('Series query')).toBeInTheDocument());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Calls onChange for label_names() query', async () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
props.query = {
|
||||||
|
refId: 'test',
|
||||||
|
query: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<PromVariableQueryEditor {...props} onChange={onChange} />);
|
||||||
|
|
||||||
|
await selectOptionInTest(screen.getByLabelText('Query type'), 'Label names');
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalledWith({
|
||||||
|
query: 'label_names()',
|
||||||
|
refId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Does not call onChange for other queries', async () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
render(<PromVariableQueryEditor {...props} onChange={onChange} />);
|
||||||
|
|
||||||
|
await selectOptionInTest(screen.getByLabelText('Query type'), 'Metrics');
|
||||||
|
await selectOptionInTest(screen.getByLabelText('Query type'), 'Query result');
|
||||||
|
await selectOptionInTest(screen.getByLabelText('Query type'), 'Series query');
|
||||||
|
|
||||||
|
expect(onChange).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Calls onChange for metrics() with argument onBlur', async () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
props.query = {
|
||||||
|
refId: 'test',
|
||||||
|
query: 'metrics(a)',
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<PromVariableQueryEditor {...props} onChange={onChange} />);
|
||||||
|
|
||||||
|
const labelSelect = screen.getByLabelText('Metric selector');
|
||||||
|
await userEvent.click(labelSelect);
|
||||||
|
const functionSelect = screen.getByLabelText('Query type').parentElement!;
|
||||||
|
await userEvent.click(functionSelect);
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalledWith({
|
||||||
|
query: 'metrics(a)',
|
||||||
|
refId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Calls onChange for query_result() with argument onBlur', async () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
props.query = {
|
||||||
|
refId: 'test',
|
||||||
|
query: 'query_result(a)',
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<PromVariableQueryEditor {...props} onChange={onChange} />);
|
||||||
|
|
||||||
|
const labelSelect = screen.getByLabelText('Prometheus Query');
|
||||||
|
await userEvent.click(labelSelect);
|
||||||
|
const functionSelect = screen.getByLabelText('Query type').parentElement!;
|
||||||
|
await userEvent.click(functionSelect);
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalledWith({
|
||||||
|
query: 'query_result(a)',
|
||||||
|
refId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Calls onChange for Match[] series with argument onBlur', async () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
props.query = {
|
||||||
|
refId: 'test',
|
||||||
|
query: '{a: "example"}',
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<PromVariableQueryEditor {...props} onChange={onChange} />);
|
||||||
|
|
||||||
|
const labelSelect = screen.getByLabelText('Series Query');
|
||||||
|
await userEvent.click(labelSelect);
|
||||||
|
const functionSelect = screen.getByLabelText('Query type').parentElement!;
|
||||||
|
await userEvent.click(functionSelect);
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalledWith({
|
||||||
|
query: '{a: "example"}',
|
||||||
|
refId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
import React, { FC, FormEvent, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { QueryEditorProps, SelectableValue } from '@grafana/data';
|
||||||
|
import { InlineField, InlineFieldRow, Input, Select, TextArea } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { PrometheusDatasource } from '../datasource';
|
||||||
|
import {
|
||||||
|
migrateVariableEditorBackToVariableSupport,
|
||||||
|
migrateVariableQueryToEditor,
|
||||||
|
} from '../migrations/variableMigration';
|
||||||
|
import { PromOptions, PromQuery, PromVariableQuery, PromVariableQueryType as QueryType } from '../types';
|
||||||
|
|
||||||
|
export const variableOptions = [
|
||||||
|
{ label: 'Label names', value: QueryType.LabelNames },
|
||||||
|
{ label: 'Label values', value: QueryType.LabelValues },
|
||||||
|
{ label: 'Metrics', value: QueryType.MetricNames },
|
||||||
|
{ label: 'Query result', value: QueryType.VarQueryResult },
|
||||||
|
{ label: 'Series query', value: QueryType.SeriesQuery },
|
||||||
|
];
|
||||||
|
|
||||||
|
export type Props = QueryEditorProps<PrometheusDatasource, PromQuery, PromOptions, PromVariableQuery>;
|
||||||
|
|
||||||
|
const refId = 'PrometheusVariableQueryEditor-VariableQuery';
|
||||||
|
|
||||||
|
export const PromVariableQueryEditor: FC<Props> = ({ onChange, query, datasource }) => {
|
||||||
|
// to select the query type, i.e. label_names, label_values, etc.
|
||||||
|
const [qryType, setQryType] = useState<number | undefined>(undefined);
|
||||||
|
|
||||||
|
// list of variables for each function
|
||||||
|
const [label, setLabel] = useState('');
|
||||||
|
// metric is used for both label_values() and metric()
|
||||||
|
// label_values() metric requires a whole/complete metric
|
||||||
|
// metric() is expected to be a part of a metric string
|
||||||
|
const [metric, setMetric] = useState('');
|
||||||
|
// varQuery is a whole query, can include math/rates/etc
|
||||||
|
const [varQuery, setVarQuery] = useState('');
|
||||||
|
// seriesQuery is only a whole
|
||||||
|
const [seriesQuery, setSeriesQuery] = useState('');
|
||||||
|
|
||||||
|
// list of label names for label_values(), /api/v1/labels, contains the same results as label_names() function
|
||||||
|
const [labelOptions, setLabelOptions] = useState<Array<SelectableValue<string>>>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!query) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Changing from standard to custom variable editor changes the string attr from expr to query
|
||||||
|
const variableQuery = query.query ? migrateVariableQueryToEditor(query.query) : query;
|
||||||
|
|
||||||
|
setQryType(variableQuery.qryType);
|
||||||
|
setLabel(variableQuery.label ?? '');
|
||||||
|
setMetric(variableQuery.metric ?? '');
|
||||||
|
setVarQuery(variableQuery.varQuery ?? '');
|
||||||
|
setSeriesQuery(variableQuery.seriesQuery ?? '');
|
||||||
|
|
||||||
|
// set the migrated label in the label options
|
||||||
|
if (variableQuery.label) {
|
||||||
|
setLabelOptions([{ label: variableQuery.label, value: variableQuery.label }]);
|
||||||
|
}
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
// set the label names options for the label values var query
|
||||||
|
useEffect(() => {
|
||||||
|
if (qryType !== QueryType.LabelValues) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource.getLabelNames().then((labelNames: Array<{ text: string }>) => {
|
||||||
|
setLabelOptions(labelNames.map(({ text }) => ({ label: text, value: text })));
|
||||||
|
});
|
||||||
|
}, [datasource, qryType]);
|
||||||
|
|
||||||
|
const onChangeWithVariableString = (qryType: QueryType) => {
|
||||||
|
const queryVar = {
|
||||||
|
qryType: qryType,
|
||||||
|
label,
|
||||||
|
metric,
|
||||||
|
varQuery,
|
||||||
|
seriesQuery,
|
||||||
|
refId: 'PrometheusVariableQueryEditor-VariableQuery',
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryString = migrateVariableEditorBackToVariableSupport(queryVar);
|
||||||
|
|
||||||
|
onChange({
|
||||||
|
query: queryString,
|
||||||
|
refId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onQueryTypeChange = (newType: SelectableValue<QueryType>) => {
|
||||||
|
setQryType(newType.value);
|
||||||
|
if (newType.value === QueryType.LabelNames) {
|
||||||
|
onChangeWithVariableString(newType.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLabelChange = (newLabel: SelectableValue<string>) => {
|
||||||
|
setLabel(newLabel.value ?? '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMetricChange = (e: FormEvent<HTMLInputElement>) => {
|
||||||
|
setMetric(e.currentTarget.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onVarQueryChange = (e: FormEvent<HTMLTextAreaElement>) => {
|
||||||
|
setVarQuery(e.currentTarget.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSeriesQueryChange = (e: FormEvent<HTMLInputElement>) => {
|
||||||
|
setSeriesQuery(e.currentTarget.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
if (qryType === QueryType.LabelNames) {
|
||||||
|
onChangeWithVariableString(qryType);
|
||||||
|
} else if (qryType === QueryType.LabelValues && label) {
|
||||||
|
onChangeWithVariableString(qryType);
|
||||||
|
} else if (qryType === QueryType.MetricNames && metric) {
|
||||||
|
onChangeWithVariableString(qryType);
|
||||||
|
} else if (qryType === QueryType.VarQueryResult && varQuery) {
|
||||||
|
onChangeWithVariableString(qryType);
|
||||||
|
} else if (qryType === QueryType.SeriesQuery && seriesQuery) {
|
||||||
|
onChangeWithVariableString(qryType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InlineFieldRow>
|
||||||
|
<InlineField
|
||||||
|
label="Query Type"
|
||||||
|
labelWidth={20}
|
||||||
|
tooltip={
|
||||||
|
<div>The Prometheus data source plugin provides the following query types for template variables.</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder="Select query type"
|
||||||
|
aria-label="Query type"
|
||||||
|
onChange={onQueryTypeChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={qryType}
|
||||||
|
options={variableOptions}
|
||||||
|
width={25}
|
||||||
|
/>
|
||||||
|
</InlineField>
|
||||||
|
{qryType === QueryType.LabelValues && (
|
||||||
|
<>
|
||||||
|
<InlineField
|
||||||
|
label="Label"
|
||||||
|
labelWidth={20}
|
||||||
|
required
|
||||||
|
tooltip={
|
||||||
|
<div>
|
||||||
|
Returns a list of label values for the label name in all metrics unless the metric is specified.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
aria-label="label-select"
|
||||||
|
onChange={onLabelChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={label}
|
||||||
|
options={labelOptions}
|
||||||
|
width={25}
|
||||||
|
allowCustomValue
|
||||||
|
/>
|
||||||
|
</InlineField>
|
||||||
|
<InlineField
|
||||||
|
label="Metric"
|
||||||
|
labelWidth={20}
|
||||||
|
tooltip={<div>Optional: returns a list of label values for the label name in the specified metric.</div>}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
aria-label="Metric selector"
|
||||||
|
placeholder="Optional metric selector"
|
||||||
|
value={metric}
|
||||||
|
onChange={onMetricChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
width={25}
|
||||||
|
/>
|
||||||
|
</InlineField>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{qryType === QueryType.MetricNames && (
|
||||||
|
<>
|
||||||
|
<InlineField
|
||||||
|
label="Metric Regex"
|
||||||
|
labelWidth={20}
|
||||||
|
tooltip={<div>Returns a list of metrics matching the specified metric regex.</div>}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
aria-label="Metric selector"
|
||||||
|
placeholder="Metric Regex"
|
||||||
|
value={metric}
|
||||||
|
onChange={onMetricChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
width={25}
|
||||||
|
/>
|
||||||
|
</InlineField>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{qryType === QueryType.VarQueryResult && (
|
||||||
|
<>
|
||||||
|
<InlineField
|
||||||
|
label="Query"
|
||||||
|
labelWidth={20}
|
||||||
|
tooltip={
|
||||||
|
<div>
|
||||||
|
Returns a list of Prometheus query results for the query. This can include Prometheus functions, i.e.
|
||||||
|
sum(go_goroutines).
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
type="text"
|
||||||
|
aria-label="Prometheus Query"
|
||||||
|
placeholder="Prometheus Query"
|
||||||
|
value={varQuery}
|
||||||
|
onChange={onVarQueryChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
cols={100}
|
||||||
|
/>
|
||||||
|
</InlineField>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{qryType === QueryType.SeriesQuery && (
|
||||||
|
<>
|
||||||
|
<InlineField
|
||||||
|
label="Series Query"
|
||||||
|
labelWidth={20}
|
||||||
|
tooltip={
|
||||||
|
<div>
|
||||||
|
Enter enter a metric with labels, only a metric or only labels, i.e.
|
||||||
|
go_goroutines{instance="localhost:9090"}, go_goroutines, or
|
||||||
|
{instance="localhost:9090"}. Returns a list of time series associated with the
|
||||||
|
entered data.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
aria-label="Series Query"
|
||||||
|
placeholder="Series Query"
|
||||||
|
value={seriesQuery}
|
||||||
|
onChange={onSeriesQueryChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
width={100}
|
||||||
|
/>
|
||||||
|
</InlineField>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</InlineFieldRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -153,7 +153,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
).rejects.toMatchObject({
|
).rejects.toMatchObject({
|
||||||
message: expect.stringMatching('Browser access'),
|
message: expect.stringMatching('Browser access'),
|
||||||
});
|
});
|
||||||
await expect(directDs.getTagKeys()).rejects.toMatchObject({
|
await expect(directDs.getLabelNames()).rejects.toMatchObject({
|
||||||
message: expect.stringMatching('Browser access'),
|
message: expect.stringMatching('Browser access'),
|
||||||
});
|
});
|
||||||
await expect(directDs.getTagValues()).rejects.toMatchObject({
|
await expect(directDs.getTagValues()).rejects.toMatchObject({
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ export class PrometheusDatasource
|
|||||||
hideFromInspector: true,
|
hideFromInspector: true,
|
||||||
...options,
|
...options,
|
||||||
})
|
})
|
||||||
); // toPromise until we change getTagValues, getTagKeys to Observable
|
); // toPromise until we change getTagValues, getLabelNames to Observable
|
||||||
}
|
}
|
||||||
|
|
||||||
interpolateQueryExpr(value: string | string[] = [], variable: any) {
|
interpolateQueryExpr(value: string | string[] = [], variable: any) {
|
||||||
@@ -908,7 +908,10 @@ export class PrometheusDatasource
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTagKeys(options?: any) {
|
// this is used to get label keys, a.k.a label names
|
||||||
|
// it is used in metric_find_query.ts
|
||||||
|
// and in Tempo here grafana/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx
|
||||||
|
async getLabelNames(options?: any) {
|
||||||
if (options?.series) {
|
if (options?.series) {
|
||||||
// Get tags for the provided series only
|
// Get tags for the provided series only
|
||||||
const seriesLabels: Array<Record<string, string[]>> = await Promise.all(
|
const seriesLabels: Array<Record<string, string[]>> = await Promise.all(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default class PrometheusMetricFindQuery {
|
|||||||
const queryResultRegex = /^query_result\((.+)\)\s*$/;
|
const queryResultRegex = /^query_result\((.+)\)\s*$/;
|
||||||
const labelNamesQuery = this.query.match(labelNamesRegex);
|
const labelNamesQuery = this.query.match(labelNamesRegex);
|
||||||
if (labelNamesQuery) {
|
if (labelNamesQuery) {
|
||||||
return this.labelNamesQuery();
|
return this.datasource.getLabelNames();
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelValuesQuery = this.query.match(labelValuesRegex);
|
const labelValuesQuery = this.query.match(labelValuesRegex);
|
||||||
@@ -47,24 +47,12 @@ export default class PrometheusMetricFindQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if query contains full metric name, return metric name and label list
|
// if query contains full metric name, return metric name and label list
|
||||||
return this.metricNameAndLabelsQuery(this.query);
|
const expressions = ['label_values()', 'metrics()', 'query_result()'];
|
||||||
}
|
if (!expressions.includes(this.query)) {
|
||||||
|
return this.metricNameAndLabelsQuery(this.query);
|
||||||
|
}
|
||||||
|
|
||||||
labelNamesQuery() {
|
return Promise.resolve([]);
|
||||||
const start = this.datasource.getPrometheusTime(this.range.from, false);
|
|
||||||
const end = this.datasource.getPrometheusTime(this.range.to, true);
|
|
||||||
const params = {
|
|
||||||
start: start.toString(),
|
|
||||||
end: end.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const url = `/api/v1/labels`;
|
|
||||||
|
|
||||||
return this.datasource.metadataRequest(url, params).then((result: any) => {
|
|
||||||
return _map(result.data.data, (value) => {
|
|
||||||
return { text: value };
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
labelValuesQuery(label: string, metric?: string) {
|
labelValuesQuery(label: string, metric?: string) {
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { PromVariableQuery, PromVariableQueryType as QueryType } from '../types';
|
||||||
|
|
||||||
|
const labelNamesRegex = /^label_names\(\)\s*$/;
|
||||||
|
const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/;
|
||||||
|
const metricNamesRegex = /^metrics\((.+)\)\s*$/;
|
||||||
|
const queryResultRegex = /^query_result\((.+)\)\s*$/;
|
||||||
|
|
||||||
|
export function migrateVariableQueryToEditor(rawQuery: string | PromVariableQuery): PromVariableQuery {
|
||||||
|
// If not string, we assume PromVariableQuery
|
||||||
|
if (typeof rawQuery !== 'string') {
|
||||||
|
return rawQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryBase = {
|
||||||
|
refId: 'PrometheusDatasource-VariableQuery',
|
||||||
|
qryType: QueryType.LabelNames,
|
||||||
|
};
|
||||||
|
|
||||||
|
const labelNames = rawQuery.match(labelNamesRegex);
|
||||||
|
if (labelNames) {
|
||||||
|
return {
|
||||||
|
...queryBase,
|
||||||
|
qryType: QueryType.LabelNames,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelValues = rawQuery.match(labelValuesRegex);
|
||||||
|
|
||||||
|
if (labelValues) {
|
||||||
|
const label = labelValues[2];
|
||||||
|
const metric = labelValues[1];
|
||||||
|
if (metric) {
|
||||||
|
return {
|
||||||
|
...queryBase,
|
||||||
|
qryType: QueryType.LabelValues,
|
||||||
|
label,
|
||||||
|
metric,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...queryBase,
|
||||||
|
qryType: QueryType.LabelValues,
|
||||||
|
label,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const metricNames = rawQuery.match(metricNamesRegex);
|
||||||
|
if (metricNames) {
|
||||||
|
return {
|
||||||
|
...queryBase,
|
||||||
|
qryType: QueryType.MetricNames,
|
||||||
|
metric: metricNames[1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryResult = rawQuery.match(queryResultRegex);
|
||||||
|
if (queryResult) {
|
||||||
|
return {
|
||||||
|
...queryBase,
|
||||||
|
qryType: QueryType.VarQueryResult,
|
||||||
|
varQuery: queryResult[1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// seriesQuery does not have a function and no regex above
|
||||||
|
if (!labelNames && !labelValues && !metricNames && !queryResult) {
|
||||||
|
return {
|
||||||
|
...queryBase,
|
||||||
|
qryType: QueryType.SeriesQuery,
|
||||||
|
seriesQuery: rawQuery,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
// migrate it back to a string with the correct varialbes in place
|
||||||
|
export function migrateVariableEditorBackToVariableSupport(QueryVariable: PromVariableQuery): string {
|
||||||
|
switch (QueryVariable.qryType) {
|
||||||
|
case QueryType.LabelNames:
|
||||||
|
return 'label_names()';
|
||||||
|
case QueryType.LabelValues:
|
||||||
|
if (QueryVariable.metric) {
|
||||||
|
return `label_values(${QueryVariable.metric},${QueryVariable.label})`;
|
||||||
|
} else {
|
||||||
|
return `label_values(${QueryVariable.label})`;
|
||||||
|
}
|
||||||
|
case QueryType.MetricNames:
|
||||||
|
return `metrics(${QueryVariable.metric})`;
|
||||||
|
case QueryType.VarQueryResult:
|
||||||
|
const varQuery = removeLineBreaks(QueryVariable.varQuery);
|
||||||
|
return `query_result(${varQuery})`;
|
||||||
|
case QueryType.SeriesQuery:
|
||||||
|
return '' + QueryVariable.seriesQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow line breaks in query result textarea
|
||||||
|
function removeLineBreaks(input?: string) {
|
||||||
|
return input ? input.replace(/[\r\n]+/gm, '') : '';
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ export interface PromQuery extends DataQuery {
|
|||||||
showingTable?: boolean;
|
showingTable?: boolean;
|
||||||
/** Code, Builder or Explain */
|
/** Code, Builder or Explain */
|
||||||
editorMode?: QueryEditorMode;
|
editorMode?: QueryEditorMode;
|
||||||
|
query?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PromOptions extends DataSourceJsonData {
|
export interface PromOptions extends DataSourceJsonData {
|
||||||
@@ -165,3 +166,21 @@ export enum LegendFormatMode {
|
|||||||
Verbose = '__verbose',
|
Verbose = '__verbose',
|
||||||
Custom = '__custom',
|
Custom = '__custom',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum PromVariableQueryType {
|
||||||
|
LabelNames,
|
||||||
|
LabelValues,
|
||||||
|
MetricNames,
|
||||||
|
VarQueryResult,
|
||||||
|
SeriesQuery,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PromVariableQuery extends DataQuery {
|
||||||
|
query?: string;
|
||||||
|
expr?: string;
|
||||||
|
qryType?: PromVariableQueryType;
|
||||||
|
label?: string;
|
||||||
|
metric?: string;
|
||||||
|
varQuery?: string;
|
||||||
|
seriesQuery?: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
import { from, Observable, of } from 'rxjs';
|
import { from, Observable, of } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
import {
|
import { CustomVariableSupport, DataQueryRequest, DataQueryResponse, rangeUtil } from '@grafana/data';
|
||||||
DataQueryRequest,
|
|
||||||
DataQueryResponse,
|
|
||||||
rangeUtil,
|
|
||||||
StandardVariableQuery,
|
|
||||||
StandardVariableSupport,
|
|
||||||
} from '@grafana/data';
|
|
||||||
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
|
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
import { getTimeSrv, TimeSrv } from '../../../features/dashboard/services/TimeSrv';
|
import { getTimeSrv, TimeSrv } from '../../../features/dashboard/services/TimeSrv';
|
||||||
|
|
||||||
|
import { PromVariableQueryEditor } from './components/VariableQueryEditor';
|
||||||
import { PrometheusDatasource } from './datasource';
|
import { PrometheusDatasource } from './datasource';
|
||||||
import PrometheusMetricFindQuery from './metric_find_query';
|
import PrometheusMetricFindQuery from './metric_find_query';
|
||||||
import { PromQuery } from './types';
|
import { PromQuery } from './types';
|
||||||
|
|
||||||
export class PrometheusVariableSupport extends StandardVariableSupport<PrometheusDatasource> {
|
export class PrometheusVariableSupport extends CustomVariableSupport<PrometheusDatasource> {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly datasource: PrometheusDatasource,
|
private readonly datasource: PrometheusDatasource,
|
||||||
private readonly templateSrv: TemplateSrv = getTemplateSrv(),
|
private readonly templateSrv: TemplateSrv = getTemplateSrv(),
|
||||||
@@ -26,8 +21,10 @@ export class PrometheusVariableSupport extends StandardVariableSupport<Prometheu
|
|||||||
this.query = this.query.bind(this);
|
this.query = this.query.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editor = PromVariableQueryEditor;
|
||||||
|
|
||||||
query(request: DataQueryRequest<PromQuery>): Observable<DataQueryResponse> {
|
query(request: DataQueryRequest<PromQuery>): Observable<DataQueryResponse> {
|
||||||
const query = request.targets[0].expr;
|
const query = request.targets[0].query;
|
||||||
if (!query) {
|
if (!query) {
|
||||||
return of({ data: [] });
|
return of({ data: [] });
|
||||||
}
|
}
|
||||||
@@ -48,11 +45,4 @@ export class PrometheusVariableSupport extends StandardVariableSupport<Prometheu
|
|||||||
|
|
||||||
return metricFindStream.pipe(map((results) => ({ data: results })));
|
return metricFindStream.pipe(map((results) => ({ data: results })));
|
||||||
}
|
}
|
||||||
|
|
||||||
toDataQuery(query: StandardVariableQuery): PromQuery {
|
|
||||||
return {
|
|
||||||
refId: 'PrometheusDatasource-VariableQuery',
|
|
||||||
expr: query.query,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function ServiceGraphSection({
|
|||||||
const [hasKeys, setHasKeys] = useState<boolean | undefined>(undefined);
|
const [hasKeys, setHasKeys] = useState<boolean | undefined>(undefined);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fn(ds: PrometheusDatasource) {
|
async function fn(ds: PrometheusDatasource) {
|
||||||
const keys = await ds.getTagKeys({
|
const keys = await ds.getLabelNames({
|
||||||
series: [
|
series: [
|
||||||
'traces_service_graph_request_server_seconds_sum',
|
'traces_service_graph_request_server_seconds_sum',
|
||||||
'traces_service_graph_request_total',
|
'traces_service_graph_request_total',
|
||||||
|
|||||||
Reference in New Issue
Block a user