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

[Portable Dashboards] Prep Redux Tools #136572

Merged

Conversation

ThomThomson
Copy link
Contributor

@ThomThomson ThomThomson commented Jul 18, 2022

Summary

Fixes #133681.

This PR introduces a new Redux Embeddable Tools system to enhance embeddable DX for the Portable Dashboards initiative.

This PR:

  1. Provides a simple, opt-in state management system for all kinds of state that an Embeddable needs.
    • Explicit Input which is state that is passed in to build this embeddable - not including state inherited from the parent
    • Output which is derived state that this embeddable outputs to communicate externally
    • Component State which is ephemeral, derived state which should not be tracked or persisted.
  2. Unifies all embeddable-based state interactions: Whether they are used from inside the embeddable class, or from inside react components underneath the embeddable class, it is possible to use reducers, and access state from the redux store.
  3. Provides automatic two-way syncing between embeddable inputs and outputs and the redux store.

This PR also migrates the Controls plugin to use the new Redux Embeddable Tools, and removes all other state management workarounds from them.

In addition, this PR reduces the number of re-renders that the Controls are subjected to by using useSelector hooks individually for each piece of state that the component needs rather than using one selector for the whole state object.

Architecture

Screen Shot 2022-07-21 at 5 10 26 PM

Checklist

Delete any items that are not applicable to this PR.

@cla-checker-service
Copy link

cla-checker-service bot commented Jul 18, 2022

💚 CLA has been signed

@ThomThomson ThomThomson force-pushed the portableDashboard/prepReduxWrapper branch from 2f5e805 to ffa8253 Compare July 18, 2022 21:22
@ThomThomson ThomThomson changed the title Portable dashboard/prep redux wrapper [Portable Dashboards] Prep Redux Tools Jul 21, 2022
@ThomThomson ThomThomson added v8.5.0 Feature:Dashboard Dashboard related features Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas loe:large Large Level of Effort release_note:skip Skip the PR/issue when compiling release notes impact:high Addressing this issue will have a high level of impact on the quality/strength of our product. v8.4.0 Project:Portable Dashboard Related to the Portable Dashboards initiative and removed v8.5.0 labels Jul 21, 2022
@ThomThomson ThomThomson marked this pull request as ready for review July 22, 2022 16:00
@ThomThomson ThomThomson requested review from a team as code owners July 22, 2022 16:00
@elasticmachine
Copy link
Contributor

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

@ThomThomson
Copy link
Contributor Author

@elasticmachine merge upstream

@botelastic botelastic bot added the Feature:Embedding Embedding content via iFrame label Jul 25, 2022
@Heenawter Heenawter self-requested a review July 26, 2022 16:54
@ThomThomson ThomThomson removed the request for review from a team July 27, 2022 16:32
Copy link
Contributor

@Heenawter Heenawter left a comment

Choose a reason for hiding this comment

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

Wooo, so happy this is going in 🎉

Mostly a code review only... I did pull and run Kibana, added some controls to Dashboard and ensured everything still... worked? Honestly not sure what else to do, since there's no user-facing changes, heh :) But nothing exploded, so woohoo!

Left a few questions/comments but nothing major. I think it all makes sense to me, and I'm excited for when this can finally be used in Dashboard - seeing it used for Controls really shows the potential!

super(
initialInput,
{ embeddableLoaded: {} },
{ dataViewIds: [], loading: false, embeddableLoaded: {}, filters: [] },
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm... Why is loading false here? Are you just letting the parent constructor take care of it? If so, why set it explicitly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not actually sure why I added that - I think at one point I might have modified the output type more significantly, but the Control group doesn't even use loading. It actually has it's own untilReady method that does the same thing. Will remove this.

Comment on lines +41 to +49
const { isInputEqual: inputEqualityCheck, isOutputEqual: outputEqualityCheck } = settings ?? {};
const inputEqual = (
inputA: Partial<ReduxEmbeddableStateType['explicitInput']>,
inputB: Partial<ReduxEmbeddableStateType['explicitInput']>
) => (inputEqualityCheck ? inputEqualityCheck(inputA, inputB) : deepEqual(inputA, inputB));
const outputEqual = (
outputA: ReduxEmbeddableStateType['output'],
outputB: ReduxEmbeddableStateType['output']
) => (outputEqualityCheck ? outputEqualityCheck(outputA, outputB) : deepEqual(outputA, outputB));
Copy link
Contributor

Choose a reason for hiding this comment

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

Ohhhh man, this logic is so much nicer for when we switch Dashboard over to use this for state management 🔥

const { explicitInput: reduxExplicitInput } = store.getState();

// store only explicit input in the store
const embeddableExplictiInput = embeddable.getExplicitInput() as Writeable<
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo:

Suggested change
const embeddableExplictiInput = embeddable.getExplicitInput() as Writeable<
const embeddableExplicitInput = embeddable.getExplicitInput() as Writeable<

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Whoops, will fix! Nice catch

import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import { ControlInput } from '../common/types';
import { ControlsService } from './services/controls';

export interface CommonControlOutput {
filters?: Filter[];
dataViews?: DataView[];
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.

Why the switch to dataViewId rather than just dataView directly? Just for... initial speed perhaps, since it's only needed when changes are actually made to the control's selections?

As far as why it's no longer an array - I assume that's because a control can only have a single data view? Just wanted to clarify :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is actually a little bit of an awkward change introduced by this PR. Basically embeddables have this pattern where they communicate the data views they use to the Dashboard so that the dashboard can pick appropriate data views when creating a filter. The problem with this is that the data view they were communicating was a whole data view object instance which is non-serializable and cannot be stored in Redux. For this reason I made the switch for Controls only to communicate the data view Id instead.

See this issue for a description of how we could better extend this pattern to other embeddables.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahhh that makes perfect sense - thank you! I definitely think reducing the amount of times we have to fetch the data view objects would be a huge win for performance, so communicating IDs and only fetching when absolutely necessary is a great pattern 💪

invalidSelections={invalidSelections}
updateSearchString={updateSearchString}
/>
<OptionsListPopover width={dimensions.width} updateSearchString={updateSearchString} />
Copy link
Contributor

Choose a reason for hiding this comment

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

So much cleaner, wow 🎉

Comment on lines +15 to +16
export const RangeSliderComponent = () => {
return <RangeSliderPopover />;
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm... Is it worth even keeping RangeSliderComponent at this point now that a bunch of logic has been removed? Could we not just use RangeSliderPopover component directly for the embeddable render?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was actually going to work through this with @cqliu1 yeah! The it seems like the whole Component was just a wrapper for passing some props down (which is no longer needed!). Other than that, I'd really like to be able to split the Popover up into two components - one with the popover contents only, and then the component can handle everything else, kinda like how Options List does it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Created this issue

@kibana-ci
Copy link
Collaborator

💚 Build Succeeded

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
controls 173 196 +23
presentationUtil 181 183 +2
total +25

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 198 199 +1
presentationUtil 180 187 +7
total +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 428.3KB 443.4KB +15.1KB
dashboard 409.7KB 409.8KB +3.0B
presentationUtil 127.6KB 127.5KB -21.0B
total +15.0KB

Canvas Sharable Runtime

The Canvas "shareable runtime" is an bundle produced to enable running Canvas workpads outside of Kibana. This bundle is included in third-party webpages that embed canvas and therefor should be as slim as possible.

id before after diff
module count 4939 4876 -63
total size 7.7MB 7.6MB -49.5KB

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
presentationUtil 11 12 +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 29.2KB 29.3KB +183.0B
presentationUtil 43.3KB 43.2KB -141.0B
total +42.0B
Unknown metric groups

API count

id before after diff
controls 206 207 +1
presentationUtil 231 243 +12
total +13

async chunk count

id before after diff
controls 6 7 +1

ESLint disabled line counts

id before after diff
presentationUtil 10 9 -1

Total ESLint disabled count

id before after diff
presentationUtil 12 11 -1

History

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

@ThomThomson ThomThomson merged commit 01fc584 into elastic:main Jul 29, 2022
@ThomThomson ThomThomson added v8.5.0 and removed v8.4.0 labels Jul 29, 2022
@kibanamachine kibanamachine added the backport:skip This commit does not require backporting label Jul 29, 2022
kibanamachine pushed a commit to kibanamachine/kibana that referenced this pull request Jul 29, 2022
* Created new Redux system for embeddables in Presentation Util. Migrated all Controls to use it.

(cherry picked from commit 01fc584)
@kibanamachine
Copy link
Contributor

💚 All backports created successfully

Status Branch Result
8.4

Note: Successful backport PRs will be merged automatically after passing CI.

Questions ?

Please refer to the Backport tool documentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:skip This commit does not require backporting Feature:Dashboard Dashboard related features Feature:Embedding Embedding content via iFrame impact:high Addressing this issue will have a high level of impact on the quality/strength of our product. loe:large Large Level of Effort Project:Portable Dashboard Related to the Portable Dashboards initiative release_note:skip Skip the PR/issue when compiling release notes Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas v8.5.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Portable Dashboards] Prepare Redux Embeddable System for Dashboard
5 participants