Skip to content

Commit 9d5e1ed

Browse files
authored
Save query form validation on blur (#43726) (#43827)
This PR changes the save query form to only validate its inputs on blur. Previously we validated on every change. This could lead to the errors flashing in the user's face before they're done typing a valid input. For example, we allow spaces in the name field, but not at the beginning or end of the name. So if a user typed this is a long name with spaces they would see the error pop up every time they type a space, only to have it disappear when they type the next letter.
1 parent 1fbf9ac commit 9d5e1ed

File tree

2 files changed

+41
-16
lines changed

2 files changed

+41
-16
lines changed

src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
EuiText,
3535
} from '@elastic/eui';
3636
import { i18n } from '@kbn/i18n';
37-
import { sortBy } from 'lodash';
37+
import { sortBy, isEqual } from 'lodash';
3838
import { SavedQuery, SavedQueryAttributes } from '../../index';
3939
import { SavedQueryService } from '../../lib/saved_query_service';
4040

@@ -74,6 +74,7 @@ export const SaveQueryForm: FunctionComponent<Props> = ({
7474
const [shouldIncludeTimefilter, setIncludeTimefilter] = useState(
7575
savedQuery ? !!savedQuery.timefilter : false
7676
);
77+
const [formErrors, setFormErrors] = useState<string[]>([]);
7778

7879
useEffect(() => {
7980
const fetchQueries = async () => {
@@ -91,12 +92,6 @@ export const SaveQueryForm: FunctionComponent<Props> = ({
9192
}
9293
);
9394

94-
const hasTitleConflict = !!savedQueries.find(
95-
existingSavedQuery => !savedQuery && existingSavedQuery.attributes.title === title
96-
);
97-
98-
const hasWhitespaceError = title.length > title.trim().length;
99-
10095
const titleConflictErrorText = i18n.translate(
10196
'data.search.searchBar.savedQueryForm.titleConflictText',
10297
{
@@ -106,18 +101,36 @@ export const SaveQueryForm: FunctionComponent<Props> = ({
106101
const whitespaceErrorText = i18n.translate(
107102
'data.search.searchBar.savedQueryForm.whitespaceErrorText',
108103
{
109-
defaultMessage: 'Title cannot contain leading or trailing white space',
104+
defaultMessage: 'Title cannot contain leading or trailing whitespace',
110105
}
111106
);
112-
const hasErrors = hasWhitespaceError || hasTitleConflict;
113107

114-
const errors = () => {
115-
if (hasWhitespaceError) return [whitespaceErrorText];
116-
if (hasTitleConflict) return [titleConflictErrorText];
117-
return [];
108+
const validate = () => {
109+
const errors = [];
110+
if (title.length > title.trim().length) {
111+
errors.push(whitespaceErrorText);
112+
}
113+
if (
114+
!!savedQueries.find(
115+
existingSavedQuery => !savedQuery && existingSavedQuery.attributes.title === title
116+
)
117+
) {
118+
errors.push(titleConflictErrorText);
119+
}
120+
121+
if (!isEqual(errors, formErrors)) {
122+
setFormErrors(errors);
123+
}
118124
};
125+
126+
const hasErrors = formErrors.length > 0;
127+
128+
if (hasErrors) {
129+
validate();
130+
}
131+
119132
const saveQueryForm = (
120-
<EuiForm isInvalid={hasErrors} error={errors()}>
133+
<EuiForm isInvalid={hasErrors} error={formErrors}>
121134
<EuiFormRow>
122135
<EuiText color="subdued">{savedQueryDescriptionText}</EuiText>
123136
</EuiFormRow>
@@ -140,6 +153,7 @@ export const SaveQueryForm: FunctionComponent<Props> = ({
140153
}}
141154
data-test-subj="saveQueryFormTitle"
142155
isInvalid={hasErrors}
156+
onBlur={validate}
143157
/>
144158
</EuiFormRow>
145159

test/functional/services/saved_query_management_component.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,20 @@ export function SavedQueryManagementComponentProvider({ getService }) {
3838
if (name) {
3939
await testSubjects.setValue('saveQueryFormTitle', name);
4040
}
41+
42+
// Form input validation only happens onBlur. Clicking the save button should de-focus the
43+
// input element and the validation should prevent a save from actually happening if there's
44+
// an error.
45+
await testSubjects.click('savedQueryFormSaveButton');
46+
4147
const saveQueryFormSaveButtonStatus = await testSubjects.isEnabled('savedQueryFormSaveButton');
42-
expect(saveQueryFormSaveButtonStatus).to.not.eql(true);
43-
await testSubjects.click('savedQueryFormCancelButton');
48+
49+
try {
50+
expect(saveQueryFormSaveButtonStatus).to.not.eql(true);
51+
}
52+
finally {
53+
await testSubjects.click('savedQueryFormCancelButton');
54+
}
4455
}
4556

4657
async saveCurrentlyLoadedAsNewQuery(name, description, includeFilters, includeTimeFilter) {

0 commit comments

Comments
 (0)