diff --git a/packages/grafana-ui/src/components/Input/Input.tsx b/packages/grafana-ui/src/components/Input/Input.tsx index 4f2eb4664b2..e24d961514c 100644 --- a/packages/grafana-ui/src/components/Input/Input.tsx +++ b/packages/grafana-ui/src/components/Input/Input.tsx @@ -43,7 +43,7 @@ export const Input = React.forwardRef((props, ref) => { const styles = getInputStyles({ theme, invalid: !!invalid, width }); return ( -
+
{!!addonBefore &&
{addonBefore}
}
diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/NestedQuery.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/NestedQuery.tsx index 46f0e3cd5d0..5a7b92dcfa1 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/NestedQuery.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/NestedQuery.tsx @@ -1,9 +1,10 @@ import { css } from '@emotion/css'; import { GrafanaTheme2, toOption } from '@grafana/data'; import { EditorRows, FlexItem } from '@grafana/experimental'; -import { IconButton, Input, Select, useStyles2 } from '@grafana/ui'; +import { IconButton, Select, useStyles2 } from '@grafana/ui'; import React from 'react'; import { PrometheusDatasource } from '../../datasource'; +import { AutoSizeInput } from '../shared/AutoSizeInput'; import { PromVisualQueryBinary } from '../types'; import { PromQueryBuilder } from './PromQueryBuilder'; @@ -36,10 +37,10 @@ export const NestedQuery = React.memo(({ nestedQuery, index, datasource, />
Vector matches
- { + onCommitChange={(evt) => { onChange(index, { ...nestedQuery, vectorMatches: evt.currentTarget.value, diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index 33bcc9ae8e4..a9f63a62f57 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -1,12 +1,13 @@ import React, { SyntheticEvent } from 'react'; import { EditorRow, EditorField } from '@grafana/experimental'; import { CoreApp, SelectableValue } from '@grafana/data'; -import { Input, RadioButtonGroup, Select, Switch } from '@grafana/ui'; +import { RadioButtonGroup, Select, Switch } from '@grafana/ui'; 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'; +import { AutoSizeInput } from '../shared/AutoSizeInput'; export interface Props { query: PromQuery; @@ -21,7 +22,7 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange onRunQuery(); }; - const onChangeStep = (evt: React.FocusEvent) => { + const onChangeStep = (evt: React.FormEvent) => { onChange({ ...query, interval: evt.currentTarget.value }); onRunQuery(); }; @@ -57,12 +58,12 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange } > - diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendEditor.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendEditor.tsx index 3a9ba54a9a4..f5c920a0a09 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendEditor.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendEditor.tsx @@ -1,8 +1,9 @@ import React, { useRef } from 'react'; import { EditorField } from '@grafana/experimental'; import { SelectableValue } from '@grafana/data'; -import { Input, Select } from '@grafana/ui'; +import { Select } from '@grafana/ui'; import { LegendFormatMode, PromQuery } from '../../types'; +import { AutoSizeInput } from '../shared/AutoSizeInput'; export interface Props { query: PromQuery; @@ -27,7 +28,7 @@ export const PromQueryLegendEditor = React.memo(({ query, onChange, onRun const mode = getLegendMode(query.legendFormat); const inputRef = useRef(null); - const onLegendFormatChanged = (evt: React.FocusEvent) => { + const onLegendFormatChanged = (evt: React.FormEvent) => { let legendFormat = evt.currentTarget.value; if (legendFormat.length === 0) { legendFormat = LegendFormatMode.Auto; @@ -62,12 +63,12 @@ export const PromQueryLegendEditor = React.memo(({ query, onChange, onRun > <> {mode === LegendFormatMode.Custom && ( - )} diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/AutoSizeInput.test.tsx b/public/app/plugins/datasource/prometheus/querybuilder/shared/AutoSizeInput.test.tsx new file mode 100644 index 00000000000..0e0ef42b541 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/AutoSizeInput.test.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { screen, render, fireEvent } from '@testing-library/react'; +import { AutoSizeInput } from './AutoSizeInput'; + +jest.mock('@grafana/ui', () => { + const original = jest.requireActual('@grafana/ui'); + const mockedUi = { ...original }; + + // Mocking measureText + mockedUi.measureText = (text: string, fontSize: number) => { + return { width: text.length * fontSize }; + }; + + return mockedUi; +}); + +describe('AutoSizeInput', () => { + it('should have default minWidth when empty', () => { + render(); + + const input: HTMLInputElement = screen.getByTestId('autosize-input'); + const inputWrapper: HTMLDivElement = screen.getByTestId('input-wrapper'); + + fireEvent.change(input, { target: { value: '' } }); + + expect(input.value).toBe(''); + expect(getComputedStyle(inputWrapper).width).toBe('80px'); + }); + + it('should have default minWidth for short content', () => { + render(); + + const input: HTMLInputElement = screen.getByTestId('autosize-input'); + const inputWrapper: HTMLDivElement = screen.getByTestId('input-wrapper'); + + fireEvent.change(input, { target: { value: 'foo' } }); + + expect(input.value).toBe('foo'); + expect(getComputedStyle(inputWrapper).width).toBe('80px'); + }); + + it('should change width for long content', () => { + render(); + + const input: HTMLInputElement = screen.getByTestId('autosize-input'); + const inputWrapper: HTMLDivElement = screen.getByTestId('input-wrapper'); + + fireEvent.change(input, { target: { value: 'very very long value' } }); + expect(getComputedStyle(inputWrapper).width).toBe('304px'); + }); +}); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/AutoSizeInput.tsx b/public/app/plugins/datasource/prometheus/querybuilder/shared/AutoSizeInput.tsx new file mode 100644 index 00000000000..a0223f596c7 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/AutoSizeInput.tsx @@ -0,0 +1,71 @@ +import { Input, measureText } from '@grafana/ui'; +import { Props as InputProps } from '@grafana/ui/src/components/Input/Input'; +import React, { useEffect } from 'react'; +export interface Props extends InputProps { + /** Sets the min-width to a multiple of 8px. Default value is 10*/ + minWidth?: number; + /** Sets the max-width to a multiple of 8px.*/ + maxWidth?: number; + /** onChange function that will be run on onBlur and onKeyPress with enter*/ + onCommitChange?: (event: React.FormEvent) => void; +} + +export const AutoSizeInput = React.forwardRef((props, ref) => { + const { defaultValue = '', minWidth = 10, maxWidth, onCommitChange, onKeyDown, onBlur, ...restProps } = props; + const [value, setValue] = React.useState(defaultValue); + const [inputWidth, setInputWidth] = React.useState(minWidth); + + useEffect(() => { + setInputWidth(getWidthFor(value.toString(), minWidth, maxWidth)); + }, [value, maxWidth, minWidth]); + + return ( + { + setValue(event.currentTarget.value); + }} + width={inputWidth} + onBlur={(event) => { + if (onCommitChange) { + onCommitChange(event); + } + if (onBlur) { + onBlur(event); + } + }} + onKeyDown={(event) => { + if (event.key === 'Enter' && onCommitChange) { + onCommitChange(event); + } + if (onKeyDown) { + onKeyDown(event); + } + }} + data-testid={'autosize-input'} + /> + ); +}); + +function getWidthFor(value: string, minWidth: number, maxWidth: number | undefined): number { + if (!value) { + return minWidth; + } + + const extraSpace = 3; + const realWidth = measureText(value.toString(), 14).width / 8 + extraSpace; + + if (minWidth && realWidth < minWidth) { + return minWidth; + } + + if (maxWidth && realWidth > maxWidth) { + return realWidth; + } + + return realWidth; +} + +AutoSizeInput.displayName = 'AutoSizeInput'; diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationParamEditor.tsx b/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationParamEditor.tsx index 3549b82a2ac..0befd0b5760 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationParamEditor.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationParamEditor.tsx @@ -1,7 +1,8 @@ import { SelectableValue, toOption } from '@grafana/data'; -import { Input, Select } from '@grafana/ui'; +import { Select } from '@grafana/ui'; import React, { ComponentType } from 'react'; import { QueryBuilderOperationParamDef, QueryBuilderOperationParamEditorProps } from '../shared/types'; +import { AutoSizeInput } from './AutoSizeInput'; import { getOperationParamId } from './operationUtils'; export function getOperationParamEditor( @@ -20,18 +21,10 @@ export function getOperationParamEditor( function SimpleInputParamEditor(props: QueryBuilderOperationParamEditorProps) { return ( - { - if (evt.key === 'Enter') { - if (evt.currentTarget.value !== props.value) { - props.onChange(props.index, evt.currentTarget.value); - } - props.onRunQuery(); - } - }} - onBlur={(evt) => { + defaultValue={props.value} + onCommitChange={(evt) => { props.onChange(props.index, evt.currentTarget.value); }} />