mirror of https://github.com/grafana/grafana
Prometheus: Auto legend handling (#45367)
* Legend editor is working * It's working * Progress on auto legend mode * Fixes * added unit tests * Added go tests * Fixing tests * Fix issue with timing and internal state * Update public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryCodeEditor.tsx Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>pull/44642/head^2
parent
da91c93f4a
commit
cfa24a3cfb
@ -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<PromQuery> = {}) { |
||||
const props = { |
||||
query: { |
||||
...getQueryWithDefaults({ refId: 'A' } as PromQuery, CoreApp.PanelEditor), |
||||
queryOverrides, |
||||
}, |
||||
onRunQuery: jest.fn(), |
||||
onChange: jest.fn(), |
||||
}; |
||||
|
||||
const { container } = render(<PromQueryBuilderOptions {...props} />); |
||||
return { container, props }; |
||||
} |
@ -0,0 +1,112 @@ |
||||
import React, { useRef } from 'react'; |
||||
import { EditorField } from '@grafana/experimental'; |
||||
import { SelectableValue } from '@grafana/data'; |
||||
import { Input, Select } from '@grafana/ui'; |
||||
import { LegendFormatMode, PromQuery } from '../../types'; |
||||
|
||||
export interface Props { |
||||
query: PromQuery; |
||||
onChange: (update: PromQuery) => void; |
||||
onRunQuery: () => void; |
||||
} |
||||
|
||||
const legendModeOptions = [ |
||||
{ |
||||
label: 'Auto', |
||||
value: LegendFormatMode.Auto, |
||||
description: 'Only includes unique labels', |
||||
}, |
||||
{ label: 'Verbose', value: LegendFormatMode.Verbose, description: 'All label names and values' }, |
||||
{ label: 'Custom', value: LegendFormatMode.Custom, description: 'Provide a naming template' }, |
||||
]; |
||||
|
||||
/** |
||||
* Tests for this component are on the parent level (PromQueryBuilderOptions). |
||||
*/ |
||||
export const PromQueryLegendEditor = React.memo<Props>(({ query, onChange, onRunQuery }) => { |
||||
const mode = getLegendMode(query.legendFormat); |
||||
const inputRef = useRef<HTMLInputElement | null>(null); |
||||
|
||||
const onLegendFormatChanged = (evt: React.FocusEvent<HTMLInputElement>) => { |
||||
let legendFormat = evt.currentTarget.value; |
||||
if (legendFormat.length === 0) { |
||||
legendFormat = LegendFormatMode.Auto; |
||||
} |
||||
onChange({ ...query, legendFormat }); |
||||
onRunQuery(); |
||||
}; |
||||
|
||||
const onLegendModeChanged = (value: SelectableValue<LegendFormatMode>) => { |
||||
switch (value.value!) { |
||||
case LegendFormatMode.Auto: |
||||
onChange({ ...query, legendFormat: LegendFormatMode.Auto }); |
||||
break; |
||||
case LegendFormatMode.Custom: |
||||
onChange({ ...query, legendFormat: '{{label_name}}' }); |
||||
setTimeout(() => { |
||||
inputRef.current?.focus(); |
||||
inputRef.current?.setSelectionRange(2, 12, 'forward'); |
||||
}, 10); |
||||
break; |
||||
case LegendFormatMode.Verbose: |
||||
onChange({ ...query, legendFormat: '' }); |
||||
break; |
||||
} |
||||
onRunQuery(); |
||||
}; |
||||
|
||||
return ( |
||||
<EditorField |
||||
label="Legend" |
||||
tooltip="Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname." |
||||
> |
||||
<> |
||||
{mode === LegendFormatMode.Custom && ( |
||||
<Input |
||||
id="legendFormat" |
||||
width={22} |
||||
placeholder="auto" |
||||
defaultValue={query.legendFormat} |
||||
onBlur={onLegendFormatChanged} |
||||
ref={inputRef} |
||||
/> |
||||
)} |
||||
{mode !== LegendFormatMode.Custom && ( |
||||
<Select |
||||
inputId="legend.mode" |
||||
isSearchable={false} |
||||
placeholder="Select legend mode" |
||||
options={legendModeOptions} |
||||
width={22} |
||||
onChange={onLegendModeChanged} |
||||
value={legendModeOptions.find((x) => x.value === mode)} |
||||
/> |
||||
)} |
||||
</> |
||||
</EditorField> |
||||
); |
||||
}); |
||||
|
||||
PromQueryLegendEditor.displayName = 'PromQueryLegendEditor'; |
||||
|
||||
function getLegendMode(legendFormat: string | undefined) { |
||||
// This special value means the new smart minimal series naming
|
||||
if (legendFormat === LegendFormatMode.Auto) { |
||||
return LegendFormatMode.Auto; |
||||
} |
||||
|
||||
// Missing or empty legend format is the old verbose behavior
|
||||
if (legendFormat == null || legendFormat === '') { |
||||
return LegendFormatMode.Verbose; |
||||
} |
||||
|
||||
return LegendFormatMode.Custom; |
||||
} |
||||
|
||||
export function getLegendModeLabel(legendFormat: string | undefined) { |
||||
const mode = getLegendMode(legendFormat); |
||||
if (mode !== LegendFormatMode.Custom) { |
||||
return legendModeOptions.find((x) => x.value === mode)?.label; |
||||
} |
||||
return legendFormat; |
||||
} |
Loading…
Reference in new issue