-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Controls] Clear range/time slider selections when field changes #129824
[Controls] Clear range/time slider selections when field changes #129824
Conversation
047472d
to
f223cba
Compare
f6c94a4
to
a249f68
Compare
a249f68
to
1271968
Compare
@@ -45,16 +45,19 @@ export const RangeSliderComponent: FC<Props> = ({ componentStateSubject }) => { | |||
componentStateSubject.getValue() | |||
); | |||
|
|||
const { value = ['', ''], id, title } = useEmbeddableSelector((state) => state); | |||
const { value, id, title } = useEmbeddableSelector((state) => state); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When it was previously const { value = ['', ''], id, title }
, this would cause infinite recursion because of the new useEffect
that depends on the value changing - therefore, to avoid this, I simply do this check as part of the new useEffect
where I set the selected value (line 53)
|
||
const [selectedValue, setSelectedValue] = useState<RangeValue>(value || ['', '']); | ||
|
||
useEffect(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to updated the selectedValue
when the state value changes via the resaveTransformFunction
, I had to split the previous useCallback
in to two steps rather than one - the onChangeComplete
callback now simply dispatches the new value via selectRange
, and the useEffect
listens for changes to value
in order to force selectedValue
to update alongside value
.
Note that, for the options list, this was already handled by the useEffect
on line 75 of options_list_component.tsx
because we don't have a separate useState
for which values are selected - instead, we handle changes directly from when the state changes,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a UseEffect
and a useState
here seems a bit overcomplicated for the use case. Maybe we should double check with @cqliu1 when she's back, but I was able to get the same behaviour by removing the useState
and useEffect
here entirely and just using the value directly from the embeddable input instead of mirroring it:
<RangeSliderPopover
... other stuff
value={value ?? ['', '']}
/>
Because the slider itself can't handle null / undefined, notice how I've initialized the default value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thanks for implementing this. And yeah it'll be good to check with them because I could've potentially not covered an edge case with my testing of it. It seems fine, but they might know better!
a7d91af
to
c18e284
Compare
export type DataControlInput = ControlInput & { | ||
fieldName: string; | ||
dataViewId: string; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, all of the controls we have share fieldName
and dataViewId
.
While this is true for now, in the future we may have different types of controls - for example, those that set dashboard variables or ones that change configuration options - and these new controls may not need all three of these properties. Therefore, rather than simply making these two properties part of the generic ControlInput
, I defined a separate type for data-specific controls DataControlInput
that all of our current controls should use instead of the generic ControlInput
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again for tackling this!
@@ -60,7 +60,7 @@ export const OptionsListEditor = ({ | |||
initialInput?.dataViewId ?? getRelevantDataViewId?.() ?? (await getDefaultId()); | |||
let dataView: DataView | undefined; | |||
if (initialId) { | |||
onChange({ dataViewId: initialId }); | |||
onChange({ dataViewId: initialId, fieldName: initialInput?.fieldName }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we weren't making the fieldName
part of the initial input on mount for editing existing controls, this meant it was originally undefined
- so if you changed, for example, just the control label (and did not specifically re-click the exact same field), then the selections would be unnecessarily cleared because the original field would not equal undefined
and therefore diffDataFetchProps
would be called. This happened for all control types.
By making fieldName
part of the initial input (when editing an existing control), this is prevented 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I undid this change, and was unable to reproduce the issue where changing the label and not the fieldName would clear the selections on an options list control. I believe this is because the Options List Presave transform does a null check as well as the deepEqual
.
For an options list, the logic looks like:
newInput.fieldName && !deepEqual(newInput.fieldName, embeddable.getInput().fieldName)
,
Whereas in the Range slider and Time Slider the logic looks like
!deepEqual(newInput.fieldName, embeddable.getInput().fieldName)
I think it's a bit simpler, and more easily maintainable if this is fixed from within the presaveTransformFunction
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great point! I implemented the change in presaveTransformFunction
for each control instead, and it works great :)
c18e284
to
c3c2450
Compare
Pinging @elastic/kibana-presentation (Team:Presentation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I very much liked the test additions, and the new interface and it's description / explanation are spot on, so great job!
I left a couple comments about code architecture / DX, but this LGTM!
export type DataControlInput = ControlInput & { | ||
fieldName: string; | ||
dataViewId: string; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again for tackling this!
@@ -60,7 +60,7 @@ export const OptionsListEditor = ({ | |||
initialInput?.dataViewId ?? getRelevantDataViewId?.() ?? (await getDefaultId()); | |||
let dataView: DataView | undefined; | |||
if (initialId) { | |||
onChange({ dataViewId: initialId }); | |||
onChange({ dataViewId: initialId, fieldName: initialInput?.fieldName }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I undid this change, and was unable to reproduce the issue where changing the label and not the fieldName would clear the selections on an options list control. I believe this is because the Options List Presave transform does a null check as well as the deepEqual
.
For an options list, the logic looks like:
newInput.fieldName && !deepEqual(newInput.fieldName, embeddable.getInput().fieldName)
,
Whereas in the Range slider and Time Slider the logic looks like
!deepEqual(newInput.fieldName, embeddable.getInput().fieldName)
I think it's a bit simpler, and more easily maintainable if this is fixed from within the presaveTransformFunction
.
|
||
const [selectedValue, setSelectedValue] = useState<RangeValue>(value || ['', '']); | ||
|
||
useEffect(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a UseEffect
and a useState
here seems a bit overcomplicated for the use case. Maybe we should double check with @cqliu1 when she's back, but I was able to get the same behaviour by removing the useState
and useEffect
here entirely and just using the value directly from the embeddable input instead of mirroring it:
<RangeSliderPopover
... other stuff
value={value ?? ['', '']}
/>
Because the slider itself can't handle null / undefined, notice how I've initialized the default value.
3839fba
to
67d6f12
Compare
💚 Build SucceededMetrics [docs]Public APIs missing comments
Async chunks
Public APIs missing exports
Page load bundle
History
To update your PR or re-run it, just comment with: cc @Heenawter |
💔 All backports failed
Manual backportTo create the backport manually run:
Questions ?Please refer to the Backport tool documentation |
…stic#129824) * Reset selections on save of existing control * Allow reset to force render for range/time sliders * Reset selections only when field name or data view changes * Make generic DataControlInput interface * Fix infinite useEffect + imports + types * Simpler solution without resetSelections() * Add functional tests * Apply Devon's changes (cherry picked from commit 5a86421) # Conflicts: # src/plugins/controls/common/control_types/options_list/types.ts
…9824) (#130827) * Reset selections on save of existing control * Allow reset to force render for range/time sliders * Reset selections only when field name or data view changes * Make generic DataControlInput interface * Fix infinite useEffect + imports + types * Simpler solution without resetSelections() * Add functional tests * Apply Devon's changes (cherry picked from commit 5a86421) # Conflicts: # src/plugins/controls/common/control_types/options_list/types.ts
…stic#129824) * Reset selections on save of existing control * Allow reset to force render for range/time sliders * Reset selections only when field name or data view changes * Make generic DataControlInput interface * Fix infinite useEffect + imports + types * Simpler solution without resetSelections() * Add functional tests * Apply Devon's changes
Closes #129638
Summary
Before this change, old selections would remain when a control's field was edited for both range and time slider controls - this would, more often than not, result in an invalid selection for the new control. Now, if the field is different when the control editor is saved, the previous selections are cleared for all three control types.
How to test
For both range and time slider controls, you should:
Test that changing the field clears selections:
Test that only field changes clear selections:
Videos
Changing field clears selection:
2022-04-19_RangeFieldClearOldSelctions.mp4
Changing anything else does not clear selection:
2022-04-19_RangeKeepOldSelctions.mp4.mp4
Checklist
Delete any items that are not applicable to this PR.
For maintainers