diff --git a/.betterer.results b/.betterer.results index cbfe4b579d2..71ad4e909bc 100644 --- a/.betterer.results +++ b/.betterer.results @@ -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.", "1"], [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.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"] + [0, 0, 0, "Do not use any type assertions.", "3"], + [0, 0, 0, "Unexpected any. Specify a different type.", "4"] ], "public/app/plugins/datasource/prometheus/query_hints.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], diff --git a/docs/sources/datasources/prometheus/template-variables/index.md b/docs/sources/datasources/prometheus/template-variables/index.md index 8b69205c235..8004fd233a9 100644 --- a/docs/sources/datasources/prometheus/template-variables/index.md +++ b/docs/sources/datasources/prometheus/template-variables/index.md @@ -23,17 +23,17 @@ For an introduction to templating and template variables, refer to the [Templati ## 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 | -| ----------------------------- | ----------------------------------------------------------------------- | --------------------------------- | -| `label_names()` | Returns a list of 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(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 | -| `query_result(query)` | Returns a list of Prometheus query result for the `query`. | /api/v1/query | +| Query Type | Input(\* required) | Description | Used API endpoints | +| -------------- | ------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------- | +| `Label names` | none | Returns a list of all label names. | /api/v1/labels | +| `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 | +| `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 | +| `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). diff --git a/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.test.tsx new file mode 100644 index 00000000000..099e7709da5 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.test.tsx @@ -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(); + + 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(); + + 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(); + + 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(); + + 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(); + + 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(); + + 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, + }); + }); +}); diff --git a/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.tsx b/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.tsx new file mode 100644 index 00000000000..ebe23e6e179 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.tsx @@ -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; + +const refId = 'PrometheusVariableQueryEditor-VariableQuery'; + +export const PromVariableQueryEditor: FC = ({ onChange, query, datasource }) => { + // to select the query type, i.e. label_names, label_values, etc. + const [qryType, setQryType] = useState(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>>([]); + + 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) => { + setQryType(newType.value); + if (newType.value === QueryType.LabelNames) { + onChangeWithVariableString(newType.value); + } + }; + + const onLabelChange = (newLabel: SelectableValue) => { + setLabel(newLabel.value ?? ''); + }; + + const onMetricChange = (e: FormEvent) => { + setMetric(e.currentTarget.value); + }; + + const onVarQueryChange = (e: FormEvent) => { + setVarQuery(e.currentTarget.value); + }; + + const onSeriesQueryChange = (e: FormEvent) => { + 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 ( + + The Prometheus data source plugin provides the following query types for template variables. + } + > + + + Optional: returns a list of label values for the label name in the specified metric.} + > + + + + )} + {qryType === QueryType.MetricNames && ( + <> + Returns a list of metrics matching the specified metric regex.} + > + + + + )} + {qryType === QueryType.VarQueryResult && ( + <> + + Returns a list of Prometheus query results for the query. This can include Prometheus functions, i.e. + sum(go_goroutines). + + } + > +