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

[Analyst Experience Components] Dashboard & Control Group APIs #150121

Conversation

ThomThomson
Copy link
Contributor

@ThomThomson ThomThomson commented Feb 1, 2023

Summary

✅ Pre-Merge Flaky Test Check here

This PR includes a number of tightly coupled enhancements and cleanups. It:

Creates a Standard Analyst Experience Component API

This PR aligns the Portable Dashboard renderer and the Control Group renderer to a new API structure using useImperativeHandle rather than the overcomplicated and mostly unused wrapper provider system.

Before

Architecture
The previous design had the embeddable instance return a wrapper component which provided access to redux tools like dispatch, actions & select.

Screen Shot 2023-02-01 at 6 31 16 PM

Code Example

export const BasicReduxExample = ({ dataViewId }: { dataViewId: string }) => {
  const [controlGroup, setControlGroup] = useState<ControlGroupContainer>();

  const ControlGroupReduxWrapper = useMemo(() => {
    if (controlGroup) return controlGroup.getReduxEmbeddableTools().Wrapper;
  }, [controlGroup]);

  const ButtonControls = () => {
    const {
      useEmbeddableDispatch,
      useEmbeddableSelector: select,
      actions: { setControlStyle },
    } = useControlGroupContainerContext();
    const dispatch = useEmbeddableDispatch();
    const controlStyle = select((state) => state.explicitInput.controlStyle);

    return (
      <EuiButtonGroup
        legend="Text style"
        options={[
          {
            id: `oneLine`,
            label: 'One line',
            value: 'oneLine' as ControlStyle,
          },
          {
            id: `twoLine`,
            label: 'Two lines',
            value: 'twoLine' as ControlStyle,
          },
        ]}
        idSelected={controlStyle}
        onChange={(id, value) => {
          dispatch(setControlStyle(value));
        }}
        type="single"
      />
    );
  };

  return (
    <>
      <EuiTitle>
        <h2>Redux example</h2>
      </EuiTitle>
      <EuiText>
        <p>Use the redux context from the control group to set layout style.</p>
      </EuiText>
      <EuiSpacer size="m" />
      <EuiPanel hasBorder={true}>
        {ControlGroupReduxWrapper && (
          <ControlGroupReduxWrapper>
            <ButtonControls />
          </ControlGroupReduxWrapper>
        )}

        <ControlGroupRenderer
          onLoadComplete={async (newControlGroup) => {
            setControlGroup(newControlGroup);
          }}
          getInitialInput={async (initialInput, builder) => {
            await builder.addDataControlFromField(initialInput, {
              dataViewId,
              title: 'Destintion country',
              fieldName: 'geo.dest',
              width: 'medium',
              grow: false,
            });
            await builder.addDataControlFromField(initialInput, {
              dataViewId,
              fieldName: 'bytes',
              width: 'medium',
              grow: true,
              title: 'Bytes',
            });
            return {
              ...initialInput,
              viewMode: ViewMode.VIEW,
            };
          }}
        />
      </EuiPanel>
    </>
  );
};
After

Architecture
The new design centralizes all of the redux tools directly onto the embeddable object, which is then transformed into an API. Instead of using a wrapper, any components are free to interact with the internal component state via the api object.

Screen Shot 2023-02-01 at 6 31 49 PM

Code Example

export const BasicReduxExample = ({ dataViewId }: { dataViewId: string }) => {
 const [controlGroupAPI, setControlGroupApi] = useState<AwaitingControlGroupAPI>();

  const Buttons = ({ api }: { api: ControlGroupAPI }) => {
    const controlStyle = api.select((state) => state.explicitInput.controlStyle);
    return (
      <EuiButtonGroup
        legend="Text style"
        options={[
          {
            id: `oneLine`,
            label: 'One line',
            value: 'oneLine' as ControlStyle,
          },
          {
            id: `twoLine`,
            label: 'Two lines',
            value: 'twoLine' as ControlStyle,
          },
        ]}
        idSelected={controlStyle}
        onChange={(id, value) => api.dispatch.setControlStyle(value)}
        type="single"
      />
    );
  };

  return (
    <>
      <EuiTitle>
        <h2>Redux example</h2>
      </EuiTitle>
      <EuiText>
        <p>Use the redux context from the control group to set layout style.</p>
      </EuiText>
      <EuiSpacer size="m" />
      <EuiPanel hasBorder={true}>
        {controlGroupAPI && <Buttons api={controlGroupAPI} />}

        <ControlGroupRenderer
          ref={setControlGroupApi}
          getCreationOptions={async (initialInput, builder) => {
            await builder.addDataControlFromField(initialInput, {
              dataViewId,
              title: 'Destintion country',
              fieldName: 'geo.dest',
              width: 'medium',
              grow: false,
            });
            await builder.addDataControlFromField(initialInput, {
              dataViewId,
              fieldName: 'bytes',
              width: 'medium',
              grow: true,
              title: 'Bytes',
            });
            return {
              initialInput: {
                ...initialInput,
                viewMode: ViewMode.VIEW,
              },
            };
          }}
        />
      </EuiPanel>
    </>
  );
};

Changes how Presentation team component renderers are consumed

This PR removes all instances of withSuspense used to load the dashboard renderer or the control group renderer. Instead, these will be exported from their plugins directly, with special care taken to ensure they don't balloon the bundle size. The slight increase to dashboard's page load bundle is due to the dashboard renderer being exported.

Changes how Portable Dashboard Containers are initialized

This PR shifts async dashboard initialization logic from the DashboardContainer into the DashboardContainerFactory. This simplifies a lot of code and state types:

  • Because the Dashboard Factory is now in charge of loading the saved object, there is no need for a union By reference / by value input type.
  • The dashboard can start loading its embeddable children immediately on load, rather than needing a mechanism to wait for the saved object before loading them. That mechanism has been removed from the embeddable container.

Redux Tools

This PR creates a generic implementation of redux tools that isn't coupled to the embeddable class. This will allow analyst experience components that match this new pattern to be created entirely without embeddables attached.

Test coverage

This PR restores jest unit tests that cover the rendering, lifecycles and state management of the dashboard creation process. These used to be defined here, but were marked flaky, skipped and subsequently removed. They have been reinstated in src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts

This PR also introduces tests for the portable dashboard renderer and for the control group renderer.

Follow-ups

A few follow-up PRs should:

  • Actually lock down the API types introduced in this PR, to ensure only public facing methods are exposed from the embeddable instances.
  • Shift the unified search integration of Dashboards to be declarative / prop based instead of being initialized in the portable container. This will make it easier to configure the unified search state accepted by the dashboard, and will make the dashboard renderer api match the control group API
  • Build some basic examples of how simple components built in this way can interact with each other.

Checklist

Delete any items that are not applicable to this PR.

ThomThomson and others added 30 commits January 23, 2023 13:01
…embeddable tools to centralize on embeddable, used this for dashboard
Copy link
Member

@jennypavlova jennypavlova left a comment

Choose a reason for hiding this comment

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

LGTM on hosts view

@@ -6,12 +6,15 @@
*/

import React, { useCallback, useEffect, useRef } from 'react';
import { ControlGroupContainer, type ControlGroupInput } from '@kbn/controls-plugin/public';
import {
ControlGroupAPI,
Copy link
Member

Choose a reason for hiding this comment

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

nit:

Suggested change
ControlGroupAPI,
type ControlGroupAPI,

Copy link
Contributor

@darnautov darnautov left a comment

Choose a reason for hiding this comment

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

ML changes LGTM

Copy link
Contributor

@logeekal logeekal left a comment

Choose a reason for hiding this comment

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

LGTM from Security solution perspective.

Co-authored-by: Angela Chuang <6295984+angorayc@users.noreply.github.com>
@ThomThomson
Copy link
Contributor Author

@elasticmachine merge upstream

@kibana-ci
Copy link
Collaborator

💚 Build Succeeded

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
dashboard 500 498 -2
infra 1340 1339 -1
total -3

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 274 294 +20
dashboard 181 125 -56
presentationUtil 151 164 +13
total -23

Async chunks

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

id before after diff
controls 190.6KB 187.7KB -2.8KB
dashboard 465.4KB 433.3KB -32.1KB
infra 2.0MB 2.0MB -170.0B
maps 2.8MB 2.8MB -58.0B
presentationUtil 129.6KB 130.0KB +497.0B
securitySolution 9.1MB 9.0MB -1.4KB
total -36.1KB

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 10 13 +3
dashboard 14 7 -7
presentationUtil 12 11 -1
total -5

Page load bundle

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

id before after diff
controls 33.1KB 32.8KB -362.0B
dashboard 26.9KB 38.9KB +11.9KB
embeddable 75.8KB 75.7KB -84.0B
presentationUtil 36.2KB 35.9KB -243.0B
total +11.3KB
Unknown metric groups

API count

id before after diff
controls 278 301 +23
dashboard 190 130 -60
embeddable 545 544 -1
presentationUtil 207 218 +11
total -27

async chunk count

id before after diff
dashboard 10 9 -1
presentationUtil 11 12 +1
total -0

ESLint disabled line counts

id before after diff
dashboard 8 7 -1
securitySolution 394 397 +3
total +2

Total ESLint disabled count

id before after diff
dashboard 8 7 -1
securitySolution 474 477 +3
total +2

History

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

cc @ThomThomson

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 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:x-large Extra 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.8.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants