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

[Discover] Deangularization of search embeddable #100552

Merged
merged 29 commits into from
Jun 14, 2021

Conversation

majagrubic
Copy link
Contributor

@majagrubic majagrubic commented May 25, 2021

Summary

This is an attempt to deangularize search embeddable. I've created a new component - SavedSearchEmbeddable that is instantiated by the factory. Much of the logic inside it remains the same, with a few caveats around lifecycle and updates.

I've tested it for doc table extensively and a bit for DiscoverGrid.

Checklist

Delete any items that are not applicable to this PR.

For maintainers

@majagrubic majagrubic changed the title Discover ss embeddable [Discover] Deangularization of search embeddable May 27, 2021
Copy link
Member

@kertal kertal left a comment

Choose a reason for hiding this comment

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

Tested in Chrome, Firefox, Safari, works as expected, LLGW! will have a closer look when you flag ready for review

@majagrubic
Copy link
Contributor Author

@elasticmachine merge upstream

@majagrubic majagrubic added Team:Visualizations Visualization editors, elastic-charts and infrastructure Feature:Discover Discover Application release_note:skip Skip the PR/issue when compiling release notes v7.14.0 v8.0.0 labels Jun 1, 2021
@majagrubic majagrubic marked this pull request as ready for review June 1, 2021 07:18
@majagrubic majagrubic requested a review from a team June 1, 2021 07:18
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-app (Team:KibanaApp)

@majagrubic majagrubic requested a review from kertal June 1, 2021 07:18
Copy link
Member

@kertal kertal left a comment

Choose a reason for hiding this comment

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

Review in progress, one more file, looks pretty nice, great work!

Copy link
Member

@kertal kertal left a comment

Choose a reason for hiding this comment

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

Code LGTM 👍 , found 2 more files to cleanup: search_template.html, search_template_datagrid.html, will continue with checking performance

@botelastic botelastic bot added the Team:APM All issues that need APM UI Team support label Jun 4, 2021
@majagrubic majagrubic requested review from a team as code owners June 4, 2021 08:17
Comment on lines 65 to 70
if (renderProps.refs) {
const fn = getRenderFn(renderProps.refs, renderProps);
fn().then((newScope) => {
scope.current = newScope;
});
}
Copy link
Member

@kertal kertal Jun 8, 2021

Choose a reason for hiding this comment

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

I think I've found the reason why the domNode count is much higher in this PR compared to the one in master. So currently, with the current code, the classic table is re-rendered completely with every change. with the following change, it would just render once, and then Angular would take care of the additional renderings:

if (renderProps.refs && !scope.current) {
      const fn = getRenderFn(renderProps.refs, renderProps);
      fn().then((newScope) => {
        scope.current = newScope;
      });
} else if (scope && scope.current) {
      scope.current.renderProps = { ...renderProps };
      scope.current.$applyAsync();
}

Some additional typings is necessary to satisfy TypeScript, best to look here how it was solved:

useEffect(() => {
if (ref && ref.current && !scope.current) {
const fn = getRenderFn(ref.current, { ...renderProps, rows, minimumVisibleRows });
fn().then((newScope) => {
scope.current = newScope;
});
} else if (scope && scope.current) {
scope.current.renderProps = { ...renderProps, rows, minimumVisibleRows };
scope.current.$apply();
}
}, [renderProps, minimumVisibleRows, rows]);
useEffect(() => {

Copy link
Contributor

Choose a reason for hiding this comment

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

🎨 Optional chaining :)

ref?.current
scope?.current

Copy link
Member

@kertal kertal left a comment

Choose a reason for hiding this comment

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

I think search_embeddable.ts can be cleaned up?

@majagrubic
Copy link
Contributor Author

@kertal I intentionally left search_embeddable in here, to make reviewing easier (as a lot of logic is the same). Happy to remove it though ⚡

@majagrubic
Copy link
Contributor Author

@elasticmachine merge upstream

const docTableProps = props as DocTableEmbeddableProps;
return <DiscoverDocTableEmbeddable {...docTableProps} />;
}
const discoverGridProps = props as DiscoverGridProps;
Copy link
Contributor

Choose a reason for hiding this comment

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

This cast should be unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

SearchProps differ in more than just refs from DiscoverGridProps

if (!this.searchProps) {
return;
}
this.searchProps.refs = domNode;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we could improve readability and type saftyness if we'd not store the domNode in the searchProps. We have the domNode available in the renderReactComponent method, so we could explicitly pass it into the SaveSearchEmbeddableComponent there besides the searchProps, thus making us able to type the SavedSearchEmbeddableComponent props in a way, that we know ref is always there (instead of - as currently - type un-safe case the property type to something that has ref explcitily non null on it.

tl;dr: Removing refs from searchProps, pass domNode in renderReactComponent explicitly to SavedSearchEmbeddableComponent and fix types to match that.

export function SavedSearchEmbeddableComponent(props: SearchProps) {
const { useLegacyTable } = props;
if (useLegacyTable) {
const docTableProps = props as DocTableEmbeddableProps;
Copy link
Contributor

Choose a reason for hiding this comment

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

See my comment above, on how we can get rid of this type un-safe cast.


import React from 'react';

import { DiscoverGridEmbeddable } from 'src/plugins/discover/public/application/angular/create_discover_grid_directive';
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's try to keep this import relative since it's within the same plugin, so we avoid later problems in case the overall plugin folder structure from the Kibana platform changes (once again :D)

Comment on lines 80 to 82
if (scope.current) {
scope.current.$destroy();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (scope.current) {
scope.current.$destroy();
}
scope.current?.$destroy();

const injector = await getServices().getEmbeddableInjector();
return await injectAngularElement(domNode, directive.template, props, injector);
} catch (e) {
render(<div>error</div>, domNode);
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume we expect this to never happen. Since we still catch it, I think we should render a more meaningful message, that will help us trace this in case it would ever happen. If we expect it to "NEVER" happen, I think alternatively we might just not catch the error at all, and instead let it be handled by the overall embeddable error infrastructure.

}

function getRenderFn(domNode: Element, props: DocTableEmbeddableProps) {
const directive = {
Copy link
Contributor

Choose a reason for hiding this comment

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

🎨 Since this object has only one property, and doesn't seem to be used anywhere except for accessing that single property, maybe we could just make this a const template instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For consistency with other angular directives and overall readability, I'd like to keep it as is

}

public reload() {
if (this.searchProps)
Copy link
Contributor

Choose a reason for hiding this comment

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

🎨 I think we try not to skip the curly braces whenever possible, so I believe it might be a bit more aligned with the rest of the code if we'd also put that block into curly braces.

Comment on lines 410 to 412
if (this.subscription) {
this.subscription.unsubscribe();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🎨

Suggested change
if (this.subscription) {
this.subscription.unsubscribe();
}
this.subscription?.unsubscribe();

Comment on lines 386 to 388
return new Promise(() => {
ReactDOM.render(<SavedSearchEmbeddableComponentMemoized {...searchProps} />, domNode);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ Could you clarify for me, why we create a new promise here? Are we trying to "delay" the rendering to the next execution slot for some reason? (Which would not work via that code, since the executor of a Promise is executed synchronously.) Because the way this is currently written we return a promise that will never resolve, and thus potentially creating a memory leak, since we're waiting on it in other places with await (which will never pass).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Matthias says it makes debugging easier :)

Copy link
Member

Choose a reason for hiding this comment

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

but there's something missing in the code? one moment

Copy link
Member

Choose a reason for hiding this comment

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

The 3rd param of ReactDom.render will be executed once rendering has finished, it should resolve the Promise

Copy link
Member

Choose a reason for hiding this comment

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

However fine with doing it simpler, main purpose was just to use 1 ReactDOM.render instead of 2

Copy link
Contributor

@timroes timroes Jun 9, 2021

Choose a reason for hiding this comment

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

Ah okay, I see, you planned to do that as:

    return new Promise((resolve) => {
      ReactDOM.render(<SavedSearchEmbeddableComponentMemoized {...searchProps} />, domNode, resolve);
    });

Copy link
Contributor

Choose a reason for hiding this comment

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

If that actually helps lowering the amount of rendering, it might be a reasonable change. Though we should keep in mind, that the 3rd parameter or render will be called on every Render or update, meaning React will also keep a reference around to that to call it again when updated, so I think we should make sure we're not accidentally leaking promises around between re-renderings (that anyway can only resolve once).

Copy link
Member

@kertal kertal Jun 9, 2021

Choose a reason for hiding this comment

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

used that to debug the issue about the dom node count increase, it wasn't the cause. fine with keeping it the way it was used before .. for now

@kertal
Copy link
Member

kertal commented Jun 14, 2021

@elasticmachine merge upstream

Copy link
Member

@kertal kertal left a comment

Choose a reason for hiding this comment

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

Code LGTM 👍 , with the latest changes performance issues were resolved 🥳 , no more detached DOM nodes, and, now the performance is comparable with the old embeddable, in the attached example, it's even faster.

Bildschirmfoto 2021-06-14 um 09 45 34

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
discover 377 368 -9

Async chunks

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

id before after diff
discover 426.8KB 416.9KB -9.8KB

Page load bundle

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

id before after diff
discover 74.5KB 74.7KB +227.0B
Unknown metric groups

async chunk count

id before after diff
discover 7 8 +1

History

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

@majagrubic majagrubic merged commit 80b109f into elastic:master Jun 14, 2021
@majagrubic majagrubic deleted the discover-ss-embeddable branch June 14, 2021 08:34
majagrubic pushed a commit to majagrubic/kibana that referenced this pull request Jun 14, 2021
* [Discover] Render empty embeddable

* First version of grid embeddable

* More search embeddable

* Almost stable version

* Fixing typescript errors

* Fixing filtering and sorting

* Add data-shared-item to DiscoverGridEmbeddable

* Trigger rerender when title changes

* Fixing incorrectly touched files

* Remove search_embeddable

* Remove lodash

* Fixing imports

* Minor fixes

* Removing unnecessary files

* Minor fix

* Remove unused import

* Applying PR comments

* Applying PR comments

* Removing search embeddable

* Fix missing import

* Addressing PR comments

* Do not memoize saved search component

* Applying Matthias's suggestion

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
# Conflicts:
#	api_docs/deprecations.mdx
gmmorris added a commit to gmmorris/kibana that referenced this pull request Jun 14, 2021
* master:
  [Security solution][Endpoint] Removes zip compression when creating artifacts (elastic#101379)
  [Dashboard]: Fixes disabled viz filter is applied (elastic#101859)
  [Discover] Deangularization of search embeddable (elastic#100552)
majagrubic pushed a commit that referenced this pull request Jun 14, 2021
* [Discover] Render empty embeddable

* First version of grid embeddable

* More search embeddable

* Almost stable version

* Fixing typescript errors

* Fixing filtering and sorting

* Add data-shared-item to DiscoverGridEmbeddable

* Trigger rerender when title changes

* Fixing incorrectly touched files

* Remove search_embeddable

* Remove lodash

* Fixing imports

* Minor fixes

* Removing unnecessary files

* Minor fix

* Remove unused import

* Applying PR comments

* Applying PR comments

* Removing search embeddable

* Fix missing import

* Addressing PR comments

* Do not memoize saved search component

* Applying Matthias's suggestion

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
# Conflicts:
#	api_docs/deprecations.mdx

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
cuff-links pushed a commit to cuff-links/kibana that referenced this pull request Jun 15, 2021
* [Discover] Render empty embeddable

* First version of grid embeddable

* More search embeddable

* Almost stable version

* Fixing typescript errors

* Fixing filtering and sorting

* Add data-shared-item to DiscoverGridEmbeddable

* Trigger rerender when title changes

* Fixing incorrectly touched files

* Remove search_embeddable

* Remove lodash

* Fixing imports

* Minor fixes

* Removing unnecessary files

* Minor fix

* Remove unused import

* Applying PR comments

* Applying PR comments

* Removing search embeddable

* Fix missing import

* Addressing PR comments

* Do not memoize saved search component

* Applying Matthias's suggestion

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Discover Discover Application release_note:skip Skip the PR/issue when compiling release notes Team:Visualizations Visualization editors, elastic-charts and infrastructure v7.14.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants