diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.js b/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.js index 5db0e88b80..57657f48a6 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.js @@ -38,9 +38,13 @@ import './SearchForm.css'; const FormItem = Form.Item; const Option = Select.Option; -const AdaptedInput = reduxFormFieldAdapter(Input); -const AdaptedSelect = reduxFormFieldAdapter(Select); -const AdaptedVirtualSelect = reduxFormFieldAdapter(VirtSelect, option => (option ? option.value : null)); +const AdaptedInput = reduxFormFieldAdapter({ AntInputComponent: Input }); +const AdaptedSelect = reduxFormFieldAdapter({ AntInputComponent: Select }); +const AdaptedVirtualSelect = reduxFormFieldAdapter({ + AntInputComponent: VirtSelect, + onChangeAdapter: option => (option ? option.value : null), +}); +const ValidatedAdaptedInput = reduxFormFieldAdapter({ AntInputComponent: Input, isValidatedInput: true }); export function getUnixTimeStampInMSFromForm({ startDate, startDateTime, endDate, endDateTime }) { const start = `${startDate} ${startDateTime}`; @@ -74,6 +78,17 @@ export function traceIDsToQuery(traceIDs) { return traceIDs.split(','); } +export const placeholderDurationFields = 'e.g. 1.2s, 100ms, 500us'; +export function validateDurationFields(value) { + if (!value) return undefined; + return /\d[\d\\.]*(us|ms|s|m|h)$/.test(value) + ? undefined + : { + content: `Please enter a number followed by a duration unit, ${placeholderDurationFields}`, + title: 'Please match the requested format.', + }; +} + export function convertQueryParamsToFormDates({ start, end }) { let queryStartDate; let queryStartDateTime; @@ -155,6 +170,7 @@ export class SearchFormImpl extends React.PureComponent { render() { const { handleSubmit, + invalid, selectedLookback, selectedService = '-', services, @@ -322,14 +338,21 @@ export class SearchFormImpl extends React.PureComponent { - + @@ -342,7 +365,11 @@ export class SearchFormImpl extends React.PureComponent { /> - @@ -352,6 +379,7 @@ export class SearchFormImpl extends React.PureComponent { SearchFormImpl.propTypes = { handleSubmit: PropTypes.func.isRequired, + invalid: PropTypes.bool, submitting: PropTypes.bool, services: PropTypes.arrayOf( PropTypes.shape({ @@ -364,6 +392,7 @@ SearchFormImpl.propTypes = { }; SearchFormImpl.defaultProps = { + invalid: false, services: [], submitting: false, selectedService: null, diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.test.js b/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.test.js index e2f3128ca0..3f2edecc03 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.test.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.test.js @@ -29,6 +29,7 @@ import { submitForm, traceIDsToQuery, SearchFormImpl as SearchForm, + validateDurationFields, } from './SearchForm'; import * as markers from './SearchForm.markers'; @@ -275,6 +276,36 @@ describe('', () => { btn = wrapper.find(`[data-test="${markers.SUBMIT_BTN}"]`); expect(btn.prop('disabled')).toBeFalsy(); }); + + it('disables the submit button when the form has invalid data', () => { + wrapper = shallow(); + let btn = wrapper.find(`[data-test="${markers.SUBMIT_BTN}"]`); + // If this test fails on the following expect statement, this may be a false negative caused by a separate + // regression. + expect(btn.prop('disabled')).toBeFalsy(); + wrapper.setProps({ invalid: true }); + btn = wrapper.find(`[data-test="${markers.SUBMIT_BTN}"]`); + expect(btn.prop('disabled')).toBeTruthy(); + }); +}); + +describe('validation', () => { + it('should return `undefined` if the value is falsy', () => { + expect(validateDurationFields('')).toBeUndefined(); + expect(validateDurationFields(null)).toBeUndefined(); + expect(validateDurationFields(undefined)).toBeUndefined(); + }); + + it('should return Popover-compliant error object if the value is a populated string that does not adhere to expected format', () => { + expect(validateDurationFields('100')).toEqual({ + content: 'Please enter a number followed by a duration unit, e.g. 1.2s, 100ms, 500us', + title: 'Please match the requested format.', + }); + }); + + it('should return `undefined` if the value is a populated string that adheres to expected format', () => { + expect(validateDurationFields('100ms')).toBeUndefined(); + }); }); describe('mapStateToProps()', () => { diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.js b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.js index cbdb34aef2..af2aff4d65 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.js @@ -61,7 +61,7 @@ function SelectSortImpl() { return (