opensearch-project/OpenSearch-Dashboards

Dashboard De-Angular Design

Closed this issue · 2 comments

Three main components:

  • <DashboardListing> — the dashboard listing table
  • <DashboardTopNav> — top navigation bar
  • <DashboardEditor> — main dashboard page

Dashboard Editor:

  1. useSavedDashboardInstance()
    1. if creating a new dashboard, get a default dashboard instance from saved objects
    2. if opening existing dashboard, use dashboard id from URL to get the saved dashboard instance from saved objects
  2. useDashboardAppState()
    1. instantiate a dashboard app state using the saved dashboard instance/default instance
    2. set up sync mechanisms with query managers (filter manager + query string manager)
    3. set up sync mechanisms with osdUrlStateStorage → save state info in ‘_a’ part of URL
  3. useDashboardContainer()
    1. instantiate a dashboard container embeddable from the embeddable factory
    2. handle dashboard container input and output subscriptions
      1. input subscription: handleDashboardContainerChanges() to update app state
  4. useEditorUpdates()
    1. subscribe to app state → if there are changes in app state, call getChangesFromAppStateForContainerState() to also update the dashboard container
    2. render the dashboard container

State management:

  1. app state container and dashboard embeddable container needs to be in sync; state container will also be in sync with osd url storage
    • app state container is tracking:
export interface DashboardAppState {
  panels: SavedDashboardPanel[];
  fullScreenMode: boolean;
  title: string;
  description: string;
  timeRestore: boolean;
  options: {
    hidePanelTitles: boolean;
    useMargins: boolean;
  };
  query: Query | string;
  filters: Filter[];
  viewMode: ViewMode;
  expandedPanelId?: string;
  savedQuery?: string;
}
  1. dashboard embeddable container is tracking:
  • embeddable children
  • subscription: input + output
  • inherited input: these are passed down to child from the parent container
export interface InheritedChildInput extends IndexSignature {
             filters: Filter[];
             query: Query;
             timeRange: TimeRange;
             refreshConfig?: RefreshInterval;
             viewMode: ViewMode;
             hidePanelTitles?: boolean;
             id: string;
           }
export interface DashboardContainerInput extends ContainerInput {
              viewMode: ViewMode;
              filters: Filter[];
              query: Query;
              timeRange: TimeRange;
              refreshConfig?: RefreshInterval;
              expandedPanelId?: string;
              useMargins: boolean;
              title: string;
              description?: string;
              isEmbeddedExternally?: boolean;
              isFullScreenMode: boolean;
              panels: {
                [panelId: string]: DashboardPanelState<EmbeddableInput & { [k: string]: unknown }>;
              };
              isEmptyState?: boolean;
            }
  1. Synchronization: when there is an update, either state container or dashboard container will get the updates first, and then it will prompt to update the other one to keep them in sync. From the below chart, panel specific updates will be caught by dashboard container, while all other updates will be caught by state container first
  • state container
    • if state container detected changes first, will call getChangesFromAppStateForContainerState(), and then dashboardContainer.updateInput(changes)
    • dashboardStateManager.handleDashboardContainerChanges(container) will also be triggered because the dashboard container is changed, but there is actually no difference between dashboard container and state container now
  • dashboard container
    • handleDashboardContainerChange() will be called and it will update the app state
    • getChangesFromAppStateForContainerState() will be called and it will have no differences
Screenshot 2023-06-19 at 2 13 42 PM Screenshot 2023-06-19 at 2 01 14 PM

Thanks for this @abbyhu2000! Very useful, especially the last diagram. Just one high level comments here. Can't we eliminate the embeddable state and store all state in the app state? And always reference app state. With all the syncing that's happening, having one source of truth for all state information will be really useful.

Also right now we are avoiding an infinite loop because we assume that when a change in the app state happens, the embeddable state is also going to be the same. But if we have a dynamic value, e.g. time or update count, the two states will never stop syncing.

Thanks for the comments! @ashwin-pc

  1. It is indeed better to have a single source of truth for state syncing, however, I think both state container and dashboard embeddable container are necessary here. The state container is the one interacting with URL state storage and the filter and query managers to achieve app persistence and global persistence; while the dashboard embeddable container is needed because dashboard itself needs to be a special type of embeddable to be able to interact and communicate with its child visualization embeddables. Currently only the container type embeddable(which I called it as dashboard container in the design) can achieve those needs. Therefore I think both of them are needed.

  2. It won't create an infinite loop because when the state container gets updates, it will first call getChangesFromAppStateForContainerState() to compare the two. And it will only update the dashboard container when there are actual differences between the two. Similarly in handleDashboardContainerChanges(), it will only update the state container if there are actual differences too.
    For the dynamic value, I can not think of a useful scenario where the state container and dashboard container can store different values for a variable... right now the only difference between the two is that dashboard container is also storing time filter information while state container doesn't (Because time filter is part of the global state not app state). Every other variables' values (queries, filters, panel info, view mode, dashboard info etc) should be the same and in sync between the two.