Skip to content
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

Merged

Conversation

Heenawter
Copy link
Contributor

@Heenawter Heenawter commented Apr 7, 2022

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:

    1. Create a control of that specific type
    2. Make some sort of selection with that control
    3. Edit the control and change the field that is being referenced
    4. Save your new control
    5. Notice that the old selections are gone
  • Test that only field changes clear selections:

    1. Create a control of that specific type
    2. Make some sort of selection with that control
    3. Edit the control and change the size and/or the label and/or some other setting that is not the field
    4. Save your new control
    5. Notice that the old selections remain

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

@Heenawter Heenawter added bug Fixes for quality problems that affect the customer experience Feature:Dashboard Dashboard related features release_note:fix Feature:Input Control Input controls visualization Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas loe:medium Medium Level of Effort impact:high Addressing this issue will have a high level of impact on the quality/strength of our product. v8.2.0 v8.3.0 v8.1.3 labels Apr 7, 2022
@Heenawter Heenawter self-assigned this Apr 7, 2022
@Heenawter Heenawter added the auto-backport Deprecated - use backport:version if exact versions are needed label Apr 7, 2022
@Heenawter Heenawter force-pushed the clear-selections-on-change_2022-04-06 branch 2 times, most recently from 047472d to f223cba Compare April 14, 2022 16:36
@Heenawter Heenawter force-pushed the clear-selections-on-change_2022-04-06 branch 2 times, most recently from f6c94a4 to a249f68 Compare April 18, 2022 20:32
@Heenawter Heenawter force-pushed the clear-selections-on-change_2022-04-06 branch from a249f68 to 1271968 Compare April 18, 2022 21:09
@@ -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);
Copy link
Contributor Author

@Heenawter Heenawter Apr 18, 2022

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(() => {
Copy link
Contributor Author

@Heenawter Heenawter Apr 18, 2022

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,

Copy link
Contributor

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.

Copy link
Contributor Author

@Heenawter Heenawter Apr 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh, that's totally fair. I implemented these changes for both the time and range sliders in 67d6f12 - as long as @cqliu1 and @crob611 don't see any unintended consequences with this, we'll go with it! I tested it locally and everything worked as expected, but it's good to be extra careful 👍

Copy link
Contributor

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!

@Heenawter Heenawter force-pushed the clear-selections-on-change_2022-04-06 branch from a7d91af to c18e284 Compare April 19, 2022 20:15
export type DataControlInput = ControlInput & {
fieldName: string;
dataViewId: string;
};
Copy link
Contributor Author

@Heenawter Heenawter Apr 19, 2022

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

Copy link
Contributor

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 });
Copy link
Contributor Author

@Heenawter Heenawter Apr 19, 2022

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 👍

Copy link
Contributor

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.

Copy link
Contributor Author

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 :)

@Heenawter Heenawter force-pushed the clear-selections-on-change_2022-04-06 branch from c18e284 to c3c2450 Compare April 19, 2022 21:27
@Heenawter Heenawter changed the title [Controls] Clear control selections when field and/or data view changes [Controls] Clear range/time slider selections when control' field changes Apr 19, 2022
@Heenawter Heenawter changed the title [Controls] Clear range/time slider selections when control' field changes [Controls] Clear range/time slider selections when field changes Apr 19, 2022
@Heenawter Heenawter marked this pull request as ready for review April 20, 2022 15:01
@Heenawter Heenawter requested a review from a team as a code owner April 20, 2022 15:01
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-presentation (Team:Presentation)

Copy link
Contributor

@ThomThomson ThomThomson left a 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;
};
Copy link
Contributor

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 });
Copy link
Contributor

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(() => {
Copy link
Contributor

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.

@Heenawter Heenawter force-pushed the clear-selections-on-change_2022-04-06 branch 2 times, most recently from 3839fba to 67d6f12 Compare April 20, 2022 22:38
@kibana-ci
Copy link
Collaborator

💚 Build Succeeded

Metrics [docs]

Public APIs missing comments

Total count of every public API that lacks a comment. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats comments for more detailed information.

id before after diff
controls 182 174 -8

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
controls 437.6KB 437.6KB -64.0B

Public APIs missing exports

Total count of every type that is part of your API that should be exported but is not. This will cause broken links in the API documentation system. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats exports for more detailed information.

id before after diff
controls 4 5 +1

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
controls 32.4KB 32.4KB +60.0B
Unknown metric groups

API count

id before after diff
controls 188 180 -8

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

cc @Heenawter

@Heenawter Heenawter merged commit 5a86421 into elastic:main Apr 21, 2022
@kibanamachine
Copy link
Contributor

💔 All backports failed

Status Branch Result
8.2 Backport failed because of merge conflicts
8.1 Backport failed because of merge conflicts

Manual backport

To create the backport manually run:

node scripts/backport --pr 129824

Questions ?

Please refer to the Backport tool documentation

Heenawter added a commit to Heenawter/kibana that referenced this pull request Apr 21, 2022
…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
@Heenawter Heenawter deleted the clear-selections-on-change_2022-04-06 branch April 21, 2022 20:00
Heenawter added a commit that referenced this pull request Apr 21, 2022
…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
kertal pushed a commit to kertal/kibana that referenced this pull request May 24, 2022
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto-backport Deprecated - use backport:version if exact versions are needed bug Fixes for quality problems that affect the customer experience Feature:Dashboard Dashboard related features Feature:Input Control Input controls visualization impact:high Addressing this issue will have a high level of impact on the quality/strength of our product. loe:medium Medium Level of Effort release_note:fix Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas v8.2.0 v8.2.1 v8.3.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Controls] Changing the field of an existing control will keep old selections
5 participants