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

[Research] [Dashboard plugin De-Angular] dashboard embeddable factory, embeddable container and embeddable panel structure #3905

Closed
Tracked by #3365
abbyhu2000 opened this issue Apr 20, 2023 · 1 comment
Assignees
Labels
de-angular de-angularize work research

Comments

@abbyhu2000
Copy link
Member

abbyhu2000 commented Apr 20, 2023

Document how dashboard plugin utilizes embeddable plugin, especially how it uses the embeddable factory, embeddable container and the panel structure.

@abbyhu2000
Copy link
Member Author

Dashboard plugin embeddable workflow:

1. Create and register dashboard embeddable factory

  • Create a new dashboard factory, and register it with the embeddable plugin. The DashboardContainerFactoryDefinition class is defined in dashboard_container_factory.tsx
    • class variables: isContainer = true, type="dashboard"
    • class functions:
      • isEditable(): use capability.createNew and capability.showWriteControls to determine if the action has permission
      • getDisplayName()
      • getDefaultInput(): panels: {}, isEmbeddedExternally: false, isFullScreenMode: false, useMargins: true
      • create(): create a new dashboard container
const factory = new DashboardContainerFactoryDefinition(
      getStartServices,
      () => this.currentHistory!
    );
embeddable.registerEmbeddableFactory(factory.type, factory);

2. Register dashboard placeholder factory

  • Create a new dashboard placeholder factory, and register it with the embeddable plugin. The PlaceholderEmbeddableFactory class is defined in placeholder_embeddable_factory.tsx
    • class variable: type="placeholder"
    • class function: isEditable() --> false; canCreateNew() --> false; create() --> create a new placeholder embeddable
const placeholderFactory = new PlaceholderEmbeddableFactory();
embeddable.registerEmbeddableFactory(placeholderFactory.type, placeholderFactory);
export class PlaceholderEmbeddable extends Embeddable {
  public readonly type = PLACEHOLDER_EMBEDDABLE;
  private node?: HTMLElement;

  constructor(initialInput: EmbeddableInput, parent?: IContainer) {
    super(initialInput, {}, parent);
    this.input = initialInput;
  }
  public render(node: HTMLElement) {
    if (this.node) {
      ReactDOM.unmountComponentAtNode(this.node);
    }
    this.node = node;

    const classes = classNames('embPanel', 'embPanel-isLoading');
    ReactDOM.render(
      <div className={classes}>
        <EuiLoadingChart size="l" mono />
      </div>,
      node
    );
  }

  public reload() {}
}

3. Get the dashboard factory in dashboard app controller

  • factory is a embeddable that creates other children embeddable using factory.create() method; Containers are a special type of embeddable that can contain nested embeddables. Embeddables can be dynamically added to embeddable containers. Currently only dashboard uses this interface.
const dashboardFactory = embeddable.getEmbeddableFactory<
      DashboardContainerInput,
      ContainerOutput,
      DashboardContainer
    >(DASHBOARD_CONTAINER_TYPE);

4. Use the factory to create the dashboard container embeddable with some initial inputs.

  • Initial inputs are defined by calling getDashboardInput()
const getDashboardInput = (): DashboardContainerInput => {
     return {
        id: dashboardStateManager.savedDashboard.id || '',
        filters: filterManager.getFilters(),
        hidePanelTitles: dashboardStateManager.getHidePanelTitles(),
        query: $scope.model.query,
        timeRange: {
          ..._.cloneDeep(timefilter.getTime()),
        },
        refreshConfig: timefilter.getRefreshInterval(),
        viewMode: dashboardStateManager.getViewMode(),
        panels: embeddablesMap,
        isFullScreenMode: dashboardStateManager.getFullScreenMode(),
        isEmbeddedExternally,
        isEmptyState: shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadonlyMode,
        useMargins: dashboardStateManager.getUseMargins(),
        lastReloadRequestTime,
        title: dashboardStateManager.getTitle(),
        description: dashboardStateManager.getDescription(),
        expandedPanelId: dashboardStateManager.getExpandedPanelId(),
      };
  • dashboard container embeddable class contains useful functions to manage child embeddable
    • class variable: type="dashboard"
    • class functions:
      • renderEmpty()
      • showPlaceholderUntil()
      • replacePanel()
      • addOrUpdateEmbeddable() --> used to add child embeddables
      • render()--> render the
      • getInheritedInput(): filters, hidePanelTitles, query, timerange, refreshConfig, viewMode, id
dashboardFactory.create(getDashboardInput())

5. In Dashboard container embeddable

  • define renderEmpty() to render <DashboardEmptyScreen> component
  • define output subsciption
    • subscribe to the merge of the container output and the child embeddable output
outputSubscription = merge(
              // output of dashboard container itself
              dashboardContainer.getOutput$(),
              // plus output of dashboard container children,
              // children may change, so make sure we subscribe/unsubscribe with switchMap
              dashboardContainer.getOutput$().pipe(
                map(() => dashboardContainer!.getChildIds()),
                distinctUntilChanged(deepEqual),
                switchMap((newChildIds: string[]) =>
                  merge(
                    ...newChildIds.map((childId) =>
                      dashboardContainer!
                        .getChild(childId)
                        .getOutput$()
                        .pipe(catchError(() => EMPTY))
                    )
                  )
                )
              )
               .pipe(
                mapTo(dashboardContainer),
                startWith(dashboardContainer), // to trigger initial index pattern update
                updateIndexPatternsOperator
              )
              .subscribe();
  • define input subscription: listen to changes from the embeddable container, and then update the state container for state management

    • listen to filter related changes:
      • if change the filter, the container input subscription will catch it
        • apply the change to filter manager
        • apply the change to dashboard state manager
        • change dirty to true
    • call handleDashboardContainerChanges(container) to apply changes from container to state manager. dashboardStateManager.handleDashboardContainerChanges(container);
    • call updateState() here to update the $scope in the app controller
  • registerChangeListener() on dashboard state manager: if there are changes, call refreshDashboardContainer(): we need this because there are changes that the container needs to know about that wont make the dashboard dirty, ex. view mode change

    • get the differences between app state from state manager and embeddable container
    • update the embeddable container
const refreshDashboardContainer = () => {
      const changes = getChangesFromAppStateForContainerState();
      if (changes && dashboardContainer) {
        dashboardContainer.updateInput(changes);
      }
    };
  • use container.render method to render the dashboard editor page
const dashboardDom = document.getElementById('dashboardViewport');
public render(dom: HTMLElement) {
    ReactDOM.render(
      <I18nProvider>
        <OpenSearchDashboardsContextProvider services={this.options}>
          <DashboardViewport
            renderEmpty={this.renderEmpty}
            container={this}
            PanelComponent={this.embeddablePanel}
          />
        </OpenSearchDashboardsContextProvider>
      </I18nProvider>,
      dom
    );
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
de-angular de-angularize work research
Projects
Development

No branches or pull requests

1 participant