Skip to content

Commit 58b4ec5

Browse files
committed
Fix Advanced Settings Panel number editing in Graph (#69672)
1 parent 30b997f commit 58b4ec5

2 files changed

Lines changed: 52 additions & 13 deletions

File tree

x-pack/plugins/graph/public/components/settings/advanced_settings_form.tsx

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import React from 'react';
7+
import React, { useState, useEffect } from 'react';
88
import { EuiFormRow, EuiFieldNumber, EuiComboBox, EuiSwitch, EuiText } from '@elastic/eui';
99
import { i18n } from '@kbn/i18n';
1010
import { SettingsProps } from './settings';
@@ -26,13 +26,30 @@ export function AdvancedSettingsForm({
2626
updateSettings,
2727
allFields,
2828
}: Pick<SettingsProps, 'advancedSettings' | 'updateSettings' | 'allFields'>) {
29+
// keep a local state during changes
30+
const [formState, updateFormState] = useState({ ...advancedSettings });
31+
// useEffect update localState only based on the main store
32+
useEffect(() => {
33+
updateFormState(advancedSettings);
34+
}, [updateFormState, advancedSettings]);
35+
2936
function updateSetting<K extends keyof AdvancedSettings>(key: K, value: AdvancedSettings[K]) {
3037
updateSettings({ ...advancedSettings, [key]: value });
3138
}
3239

3340
function getNumberUpdater<K extends NumberKeys<AdvancedSettings>>(key: K) {
34-
return function ({ target: { valueAsNumber } }: { target: { valueAsNumber: number } }) {
35-
updateSetting(key, Number.isNaN(valueAsNumber) ? 1 : valueAsNumber);
41+
return function ({
42+
target: { valueAsNumber, value },
43+
}: {
44+
target: { valueAsNumber: number; value: string };
45+
}) {
46+
// if the value is valid, then update the central store, otherwise only the local store
47+
if (Number.isNaN(valueAsNumber)) {
48+
// localstate update
49+
return updateFormState({ ...formState, [key]: value });
50+
}
51+
// do not worry about local store here, the useEffect will pick that up and sync it
52+
updateSetting(key, valueAsNumber);
3653
};
3754
}
3855

@@ -52,7 +69,7 @@ export function AdvancedSettingsForm({
5269
fullWidth
5370
min={1}
5471
step={1}
55-
value={advancedSettings.sampleSize}
72+
value={formState.sampleSize}
5673
onChange={getNumberUpdater('sampleSize')}
5774
/>
5875
</EuiFormRow>
@@ -73,7 +90,7 @@ export function AdvancedSettingsForm({
7390
{ defaultMessage: 'Significant links' }
7491
)}
7592
id="graphSignificance"
76-
checked={advancedSettings.useSignificance}
93+
checked={formState.useSignificance}
7794
onChange={({ target: { checked } }) => updateSetting('useSignificance', checked)}
7895
/>
7996
</EuiFormRow>
@@ -91,7 +108,7 @@ export function AdvancedSettingsForm({
91108
fullWidth
92109
min={1}
93110
step={1}
94-
value={advancedSettings.minDocCount}
111+
value={formState.minDocCount}
95112
onChange={getNumberUpdater('minDocCount')}
96113
/>
97114
</EuiFormRow>
@@ -127,11 +144,11 @@ export function AdvancedSettingsForm({
127144
singleSelection={{ asPlainText: true }}
128145
options={allFields.map((field) => ({ label: field.name, value: field }))}
129146
selectedOptions={
130-
advancedSettings.sampleDiversityField
147+
formState.sampleDiversityField
131148
? [
132149
{
133-
label: advancedSettings.sampleDiversityField.name,
134-
value: advancedSettings.sampleDiversityField,
150+
label: formState.sampleDiversityField.name,
151+
value: formState.sampleDiversityField,
135152
},
136153
]
137154
: []
@@ -145,7 +162,7 @@ export function AdvancedSettingsForm({
145162
/>
146163
</EuiFormRow>
147164

148-
{advancedSettings.sampleDiversityField && (
165+
{formState.sampleDiversityField && (
149166
<EuiFormRow
150167
fullWidth
151168
helpText={
@@ -154,7 +171,7 @@ export function AdvancedSettingsForm({
154171
defaultMessage:
155172
'Max number of documents in a sample that can contain the same value for the',
156173
})}{' '}
157-
<em>{advancedSettings.sampleDiversityField.name}</em>{' '}
174+
<em>{formState.sampleDiversityField.name}</em>{' '}
158175
{i18n.translate(
159176
'xpack.graph.settings.advancedSettings.maxValuesInputHelpText.fieldText',
160177
{
@@ -171,7 +188,7 @@ export function AdvancedSettingsForm({
171188
fullWidth
172189
min={1}
173190
step={1}
174-
value={advancedSettings.maxValuesPerDoc}
191+
value={formState.maxValuesPerDoc}
175192
onChange={getNumberUpdater('maxValuesPerDoc')}
176193
/>
177194
</EuiFormRow>
@@ -190,7 +207,7 @@ export function AdvancedSettingsForm({
190207
fullWidth
191208
min={1}
192209
step={1}
193-
value={advancedSettings.timeoutMillis}
210+
value={formState.timeoutMillis}
194211
onChange={getNumberUpdater('timeoutMillis')}
195212
append={
196213
<EuiText size="xs">

x-pack/plugins/graph/public/components/settings/settings.test.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,28 @@ describe('settings', () => {
177177
)
178178
);
179179
});
180+
181+
it('should let the user edit and empty the field to input a new number', () => {
182+
act(() => {
183+
input('Sample size').prop('onChange')!({
184+
target: { value: '', valueAsNumber: NaN },
185+
} as React.ChangeEvent<HTMLInputElement>);
186+
});
187+
// Central state should not be called
188+
expect(dispatchSpy).not.toHaveBeenCalledWith(
189+
updateSettings(
190+
expect.objectContaining({
191+
timeoutMillis: 10000,
192+
sampleSize: NaN,
193+
})
194+
)
195+
);
196+
197+
// Update the local state
198+
instance.update();
199+
// Now check that local state should reflect what the user sent
200+
expect(input('Sample size').prop('value')).toEqual('');
201+
});
180202
});
181203

182204
describe('blacklist', () => {

0 commit comments

Comments
 (0)