diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx index dacd001a8a1..17134a109e7 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx @@ -9,6 +9,7 @@ import { useStyles2 } from '../../../themes'; export interface RadioButtonGroupProps { value?: T; + id?: string; disabled?: boolean; disabledOptions?: T[]; options: Array>; @@ -28,6 +29,7 @@ export function RadioButtonGroup({ disabled, disabledOptions, size = 'md', + id, className, fullWidth = false, autoFocus = false, @@ -52,8 +54,9 @@ export function RadioButtonGroup({ }, [onClick] ); - const id = uniqueId('radiogroup-'); - const groupName = useRef(id); + + const internalId = id ?? uniqueId('radiogroup-'); + const groupName = useRef(internalId); const styles = useStyles2(getStyles); const activeButtonRef = useRef(null); @@ -76,7 +79,7 @@ export function RadioButtonGroup({ aria-label={o.ariaLabel} onChange={handleOnChange(o)} onClick={handleOnClick(o)} - id={`option-${o.value}-${id}`} + id={`option-${o.value}-${internalId}`} name={groupName.current} description={o.description} fullWidth={fullWidth} diff --git a/pkg/tsdb/prometheus/time_series_query.go b/pkg/tsdb/prometheus/time_series_query.go index b38ffd33d8b..a4d1b57e168 100644 --- a/pkg/tsdb/prometheus/time_series_query.go +++ b/pkg/tsdb/prometheus/time_series_query.go @@ -39,6 +39,8 @@ const ( varRateIntervalAlt = "${__rate_interval}" ) +const legendFormatAuto = "__auto" + type TimeSeriesQueryType string const ( @@ -137,11 +139,14 @@ func (s *Service) executeTimeSeriesQuery(ctx context.Context, req *backend.Query } func formatLegend(metric model.Metric, query *PrometheusQuery) string { - var legend string + var legend = metric.String() - if query.LegendFormat == "" { - legend = metric.String() - } else { + if query.LegendFormat == legendFormatAuto { + // If we have labels set legend to empty string to utilize the auto naming system + if len(metric) > 0 { + legend = "" + } + } else if query.LegendFormat != "" { result := legendFormat.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte { labelName := strings.Replace(string(in), "{{", "", 1) labelName = strings.Replace(labelName, "}}", "", 1) @@ -335,8 +340,12 @@ func matrixToDataFrames(matrix model.Matrix, query *PrometheusQuery, frames data timeField.Name = data.TimeSeriesTimeFieldName timeField.Config = &data.FieldConfig{Interval: float64(query.Step.Milliseconds())} valueField.Name = data.TimeSeriesValueFieldName - valueField.Config = &data.FieldConfig{DisplayNameFromDS: name} valueField.Labels = tags + + if name != "" { + valueField.Config = &data.FieldConfig{DisplayNameFromDS: name} + } + frames = append(frames, newDataFrame(name, "matrix", timeField, valueField)) } diff --git a/pkg/tsdb/prometheus/time_series_query_test.go b/pkg/tsdb/prometheus/time_series_query_test.go index fc50c3fb78f..f98d7484a8f 100644 --- a/pkg/tsdb/prometheus/time_series_query_test.go +++ b/pkg/tsdb/prometheus/time_series_query_test.go @@ -52,6 +52,30 @@ func TestPrometheus_timeSeriesQuery_formatLeged(t *testing.T) { require.Equal(t, `{job="grafana"}`, formatLegend(metric, query)) }) + + t.Run("When legendFormat = __auto and no labels", func(t *testing.T) { + metric := map[p.LabelName]p.LabelValue{} + + query := &PrometheusQuery{ + LegendFormat: legendFormatAuto, + Expr: `{job="grafana"}`, + } + + require.Equal(t, `{job="grafana"}`, formatLegend(metric, query)) + }) + + t.Run("When legendFormat = __auto with labels", func(t *testing.T) { + metric := map[p.LabelName]p.LabelValue{ + p.LabelName("app"): p.LabelValue("backend"), + } + + query := &PrometheusQuery{ + LegendFormat: legendFormatAuto, + Expr: `{job="grafana"}`, + } + + require.Equal(t, "", formatLegend(metric, query)) + }) } func TestPrometheus_timeSeriesQuery_parseTimeSeriesQuery(t *testing.T) { diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx new file mode 100644 index 00000000000..ac6b5132951 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { PromQuery } from '../../types'; +import { getQueryWithDefaults } from '../types'; +import { CoreApp } from '@grafana/data'; +import { PromQueryBuilderOptions } from './PromQueryBuilderOptions'; +import { selectOptionInTest } from '@grafana/ui'; + +describe('PromQueryBuilderOptions', () => { + it('Can change query type', async () => { + const { props } = setup(); + + screen.getByTitle('Click to edit options').click(); + expect(screen.getByLabelText('Range')).toBeChecked(); + + screen.getByLabelText('Instant').click(); + + expect(props.onChange).toHaveBeenCalledWith({ + ...props.query, + instant: true, + range: false, + exemplar: false, + }); + }); + + it('Legend format default to Auto', async () => { + setup(); + expect(screen.getByText('Legend: Auto')).toBeInTheDocument(); + }); + + it('Can change legend format to verbose', async () => { + const { props } = setup(); + + screen.getByTitle('Click to edit options').click(); + + let legendModeSelect = screen.getByText('Auto').parentElement!; + legendModeSelect.click(); + + await selectOptionInTest(legendModeSelect as HTMLElement, 'Verbose'); + + expect(props.onChange).toHaveBeenCalledWith({ + ...props.query, + legendFormat: '', + }); + }); + + it('Can change legend format to custom', async () => { + const { props } = setup(); + + screen.getByTitle('Click to edit options').click(); + + let legendModeSelect = screen.getByText('Auto').parentElement!; + legendModeSelect.click(); + + await selectOptionInTest(legendModeSelect as HTMLElement, 'Custom'); + + expect(props.onChange).toHaveBeenCalledWith({ + ...props.query, + legendFormat: '{{label_name}}', + }); + }); +}); + +function setup(queryOverrides: Partial = {}) { + const props = { + query: { + ...getQueryWithDefaults({ refId: 'A' } as PromQuery, CoreApp.PanelEditor), + queryOverrides, + }, + onRunQuery: jest.fn(), + onChange: jest.fn(), + }; + + const { container } = render(); + return { container, props }; +} diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index 64f1a5ea653..33bcc9ae8e4 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -6,6 +6,7 @@ import { QueryOptionGroup } from '../shared/QueryOptionGroup'; import { PromQuery } from '../../types'; import { FORMAT_OPTIONS, INTERVAL_FACTOR_OPTIONS } from '../../components/PromQueryEditor'; import { getQueryTypeChangeHandler, getQueryTypeOptions } from '../../components/PromExploreExtraField'; +import { getLegendModeLabel, PromQueryLegendEditor } from './PromQueryLegendEditor'; export interface Props { query: PromQuery; @@ -15,18 +16,11 @@ export interface Props { } export const PromQueryBuilderOptions = React.memo(({ query, app, onChange, onRunQuery }) => { - const formatOption = FORMAT_OPTIONS.find((option) => option.value === query.format) || FORMAT_OPTIONS[0]; - const onChangeFormat = (value: SelectableValue) => { onChange({ ...query, format: value.value }); onRunQuery(); }; - const onLegendFormatChanged = (evt: React.FocusEvent) => { - onChange({ ...query, legendFormat: evt.currentTarget.value }); - onRunQuery(); - }; - const onChangeStep = (evt: React.FocusEvent) => { onChange({ ...query, interval: evt.currentTarget.value }); onRunQuery(); @@ -46,15 +40,14 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange onRunQuery(); }; + const formatOption = FORMAT_OPTIONS.find((option) => option.value === query.format) || FORMAT_OPTIONS[0]; + const queryTypeValue = getQueryTypeValue(query); + const queryTypeLabel = queryTypeOptions.find((x) => x.value === queryTypeValue)!.label; + return ( - - - - + + (({ query, app, onChange defaultValue={query.interval} /> - + )} + {mode !== LegendFormatMode.Custom && ( +