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