mirror of https://github.com/grafana/grafana
Tracing: Add inline validation to time shift configuration fields (#70879)
* Inline validation * Update invalidTimeShiftError after self review * Renames and moved err msg * Update validation * Remove local statepull/71031/head
parent
20b6ae96a3
commit
f1338cee60
@ -0,0 +1,76 @@ |
||||
import { render, screen, waitFor } from '@testing-library/react'; |
||||
import userEvent from '@testing-library/user-event'; |
||||
import React, { useState } from 'react'; |
||||
|
||||
import { invalidTimeShiftError } from '../TraceToLogs/TraceToLogsSettings'; |
||||
|
||||
import { IntervalInput } from './IntervalInput'; |
||||
|
||||
describe('IntervalInput', () => { |
||||
const IntervalInputtWithProps = ({ val }: { val: string }) => { |
||||
const [value, setValue] = useState(val); |
||||
|
||||
return ( |
||||
<IntervalInput |
||||
label="" |
||||
tooltip="" |
||||
value={value} |
||||
disabled={false} |
||||
onChange={(v) => { |
||||
setValue(v); |
||||
}} |
||||
isInvalidError={invalidTimeShiftError} |
||||
/> |
||||
); |
||||
}; |
||||
|
||||
describe('validates time shift correctly', () => { |
||||
it('for previosuly saved invalid value', async () => { |
||||
render(<IntervalInputtWithProps val="77" />); |
||||
expect(screen.getByDisplayValue('77')).toBeInTheDocument(); |
||||
expect(screen.getByText(invalidTimeShiftError)).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('for previously saved empty value', async () => { |
||||
render(<IntervalInputtWithProps val="" />); |
||||
expect(screen.getByPlaceholderText('0')).toBeInTheDocument(); |
||||
expect(screen.queryByText(invalidTimeShiftError)).not.toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('for empty (valid) value', async () => { |
||||
render(<IntervalInputtWithProps val="1ms" />); |
||||
await userEvent.clear(screen.getByDisplayValue('1ms')); |
||||
await waitFor(() => { |
||||
expect(screen.queryByText(invalidTimeShiftError)).not.toBeInTheDocument(); |
||||
}); |
||||
}); |
||||
|
||||
it('for valid value', async () => { |
||||
render(<IntervalInputtWithProps val="10ms" />); |
||||
expect(screen.queryByText(invalidTimeShiftError)).not.toBeInTheDocument(); |
||||
|
||||
const input = screen.getByDisplayValue('10ms'); |
||||
await userEvent.clear(input); |
||||
await userEvent.type(input, '100s'); |
||||
await waitFor(() => { |
||||
expect(screen.queryByText(invalidTimeShiftError)).not.toBeInTheDocument(); |
||||
}); |
||||
|
||||
await userEvent.clear(input); |
||||
await userEvent.type(input, '-77ms'); |
||||
await waitFor(() => { |
||||
expect(screen.queryByText(invalidTimeShiftError)).not.toBeInTheDocument(); |
||||
}); |
||||
}); |
||||
|
||||
it('for invalid value', async () => { |
||||
render(<IntervalInputtWithProps val="10ms" />); |
||||
const input = screen.getByDisplayValue('10ms'); |
||||
await userEvent.clear(input); |
||||
await userEvent.type(input, 'abc'); |
||||
await waitFor(() => { |
||||
expect(screen.queryByText(invalidTimeShiftError)).toBeInTheDocument(); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,53 @@ |
||||
import React, { useState } from 'react'; |
||||
import { useDebounce } from 'react-use'; |
||||
|
||||
import { InlineField, InlineFieldRow, Input } from '@grafana/ui'; |
||||
|
||||
import { validateInterval } from './validation'; |
||||
|
||||
interface Props { |
||||
label: string; |
||||
tooltip: string; |
||||
value: string; |
||||
onChange: (val: string) => void; |
||||
isInvalidError: string; |
||||
disabled?: boolean; |
||||
} |
||||
|
||||
export function IntervalInput(props: Props) { |
||||
const [intervalIsInvalid, setIntervalIsInvalid] = useState(() => { |
||||
return props.value ? validateInterval(props.value) : false; |
||||
}); |
||||
|
||||
useDebounce( |
||||
() => { |
||||
setIntervalIsInvalid(validateInterval(props.value)); |
||||
}, |
||||
500, |
||||
[props.value] |
||||
); |
||||
|
||||
return ( |
||||
<InlineFieldRow> |
||||
<InlineField |
||||
label={props.label} |
||||
labelWidth={26} |
||||
disabled={props.disabled ?? false} |
||||
grow |
||||
tooltip={props.tooltip} |
||||
invalid={intervalIsInvalid} |
||||
error={props.isInvalidError} |
||||
> |
||||
<Input |
||||
type="text" |
||||
placeholder="0" |
||||
width={40} |
||||
onChange={(e) => { |
||||
props.onChange(e.currentTarget.value); |
||||
}} |
||||
value={props.value} |
||||
/> |
||||
</InlineField> |
||||
</InlineFieldRow> |
||||
); |
||||
} |
@ -0,0 +1,28 @@ |
||||
import { validateInterval } from './validation'; |
||||
|
||||
describe('Validation', () => { |
||||
it('should validate incorrect values correctly', () => { |
||||
expect(validateInterval('-')).toBeTruthy(); |
||||
expect(validateInterval('1')).toBeTruthy(); |
||||
expect(validateInterval('test')).toBeTruthy(); |
||||
expect(validateInterval('1ds')).toBeTruthy(); |
||||
expect(validateInterval('10Ms')).toBeTruthy(); |
||||
expect(validateInterval('-9999999')).toBeTruthy(); |
||||
}); |
||||
|
||||
it('should validate correct values correctly', () => { |
||||
expect(validateInterval('1y')).toBeFalsy(); |
||||
expect(validateInterval('1M')).toBeFalsy(); |
||||
expect(validateInterval('1w')).toBeFalsy(); |
||||
expect(validateInterval('1d')).toBeFalsy(); |
||||
expect(validateInterval('2h')).toBeFalsy(); |
||||
expect(validateInterval('4m')).toBeFalsy(); |
||||
expect(validateInterval('8s')).toBeFalsy(); |
||||
expect(validateInterval('80ms')).toBeFalsy(); |
||||
expect(validateInterval('-80ms')).toBeFalsy(); |
||||
}); |
||||
|
||||
it('should not return error if no value provided', () => { |
||||
expect(validateInterval('')).toBeFalsy(); |
||||
}); |
||||
}); |
@ -0,0 +1,5 @@ |
||||
export const validateInterval = (val: string) => { |
||||
const intervalRegex = /^(-?\d+(?:\.\d+)?)(ms|[Mwdhmsy])$/; |
||||
const matches = val.match(intervalRegex); |
||||
return matches || !val ? false : true; |
||||
}; |
Loading…
Reference in new issue