Skip to content

Commit

Permalink
Render editor page with basic nav actions (opensearch-project#4213)
Browse files Browse the repository at this point in the history
Added dashboard embeddable container to render the dashboard editor page.

Signed-off-by: abbyhu2000 <abigailhu2000@gmail.com>
  • Loading branch information
abbyhu2000 committed Jun 27, 2023
1 parent 9c23f76 commit 124fb9d
Show file tree
Hide file tree
Showing 12 changed files with 945 additions and 286 deletions.
5 changes: 4 additions & 1 deletion src/plugins/dashboard/public/application/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ export const DashboardApp = ({ onAppLeave }: DashboardAppProps) => {
exact
path={[DashboardConstants.CREATE_NEW_DASHBOARD_URL, createDashboardEditUrl(':id')]}
>
<DashboardEditor />
<div className="app-container dshAppContainer">
<DashboardEditor />
<div id="dashboardViewport" />
</div>
</Route>
<DashboardNoMatch />
</Switch>
Expand Down
216 changes: 40 additions & 176 deletions src/plugins/dashboard/public/application/components/dashboard_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,22 @@
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { EventEmitter } from 'events';
import { EMPTY, Subscription, merge } from 'rxjs';
import { catchError, distinctUntilChanged, map, mapTo, startWith, switchMap } from 'rxjs/operators';
import deepEqual from 'fast-deep-equal';
import { DashboardTopNav } from '../components/dashboard_top_nav';
import { useChromeVisibility } from '../utils/use/use_chrome_visibility';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
import { useSavedDashboardInstance } from '../utils/use/use_saved_dashboard_instance';

import { DashboardServices, SavedDashboardPanel } from '../../types';
import {
DASHBOARD_CONTAINER_TYPE,
DashboardContainer,
DashboardContainerInput,
DashboardPanelState,
} from '../embeddable';
import {
ContainerOutput,
ErrorEmbeddable,
ViewMode,
isErrorEmbeddable,
} from '../../embeddable_plugin';
import { DashboardEmptyScreen, DashboardEmptyScreenProps } from '../dashboard_empty_screen';
import { convertSavedDashboardPanelToPanelState } from '../lib/embeddable_saved_object_converters';
import { DashboardServices } from '../../types';
import { useDashboardAppState } from '../utils/use/use_dashboard_app_state';
import { useDashboardContainer } from '../utils/use/use_dashboard_container';
import { useEditorUpdates } from '../utils/use/use_editor_updates';

export const DashboardEditor = () => {
const { id: dashboardIdFromUrl } = useParams<{ id: string }>();
const { services } = useOpenSearchDashboards<DashboardServices>();
const { embeddable, data, dashboardConfig, embeddableCapabilities, uiSettings, http } = services;
const { query: queryService } = data;
const { visualizeCapabilities, mapsCapabilities } = embeddableCapabilities;
const timefilter = queryService.timefilter.timefilter;
const isChromeVisible = useChromeVisibility(services.chrome);
const [eventEmitter] = useState(new EventEmitter());

const { savedDashboardInstance } = useSavedDashboardInstance(
const savedDashboardInstance = useSavedDashboardInstance(
services,
eventEmitter,
isChromeVisible,
Expand All @@ -50,165 +30,49 @@ export const DashboardEditor = () => {

const { appState } = useDashboardAppState(services, eventEmitter, savedDashboardInstance);

const appStateData = appState?.get();
if (!appStateData) {
return null;
}
let dashboardContainer: DashboardContainer | undefined;
let inputSubscription: Subscription | undefined;
let outputSubscription: Subscription | undefined;

const dashboardDom = document.getElementById('dashboardViewport');
const dashboardFactory = embeddable.getEmbeddableFactory<
DashboardContainerInput,
ContainerOutput,
DashboardContainer
>(DASHBOARD_CONTAINER_TYPE);

const getShouldShowEditHelp = () => {
return (
!appStateData.panels.length &&
appStateData.viewMode === ViewMode.EDIT &&
!dashboardConfig.getHideWriteControls()
);
};

const getShouldShowViewHelp = () => {
return (
!appStateData.panels.length &&
appStateData.viewMode === ViewMode.VIEW &&
!dashboardConfig.getHideWriteControls()
);
};

const shouldShowUnauthorizedEmptyState = () => {
const readonlyMode =
!appStateData.panels.length &&
!getShouldShowEditHelp() &&
!getShouldShowViewHelp() &&
dashboardConfig.getHideWriteControls();
const userHasNoPermissions =
!appStateData.panels.length && !visualizeCapabilities.save && !mapsCapabilities.save;
return readonlyMode || userHasNoPermissions;
};

const getEmptyScreenProps = (
shouldShowEditHelp: boolean,
isEmptyInReadOnlyMode: boolean
): DashboardEmptyScreenProps => {
const emptyScreenProps: DashboardEmptyScreenProps = {
onLinkClick: () => {}, // TODO
showLinkToVisualize: shouldShowEditHelp,
uiSettings,
http,
};
if (shouldShowEditHelp) {
emptyScreenProps.onVisualizeClick = () => {
alert('click'); // TODO
};
}
if (isEmptyInReadOnlyMode) {
emptyScreenProps.isReadonlyMode = true;
}
return emptyScreenProps;
};
const { dashboardContainer } = useDashboardContainer(
services,
isChromeVisible,
eventEmitter,
savedDashboardInstance,
appState
);

const getDashboardInput = () => {
const embeddablesMap: {
[key: string]: DashboardPanelState;
} = {};
appStateData.panels.forEach((panel: SavedDashboardPanel) => {
embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState(panel);
});
const { isEmbeddableRendered, currentAppState } = useEditorUpdates(
services,
eventEmitter,
savedDashboardInstance,
dashboardContainer,
appState
);

const lastReloadRequestTime = 0;
return {
id: savedDashboardInstance.id || '',
filters: appStateData.filters,
hidePanelTitles: appStateData?.options.hidePanelTitles,
query: appStateData.query,
timeRange: {
..._.cloneDeep(timefilter.getTime()),
},
refreshConfig: timefilter.getRefreshInterval(),
viewMode: appStateData.viewMode,
panels: embeddablesMap,
isFullScreenMode: appStateData?.fullScreenMode,
isEmbeddedExternally: false, // TODO
// isEmptyState: shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadonlyMode,
isEmptyState: false, // TODO
useMargins: appStateData.options.useMargins,
lastReloadRequestTime, // TODO
title: appStateData.title,
description: appStateData.description,
expandedPanelId: appStateData.expandedPanelId,
useEffect(() => {
// clean up all registered listeners if any is left
return () => {
eventEmitter.removeAllListeners();
};
};

if (dashboardFactory) {
dashboardFactory
.create(getDashboardInput())
.then((container: DashboardContainer | ErrorEmbeddable | undefined) => {
if (container && !isErrorEmbeddable(container)) {
dashboardContainer = container;

dashboardContainer.renderEmpty = () => {
const shouldShowEditHelp = getShouldShowEditHelp();
const shouldShowViewHelp = getShouldShowViewHelp();
const isEmptyInReadOnlyMode = shouldShowUnauthorizedEmptyState();
const isEmptyState = shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadOnlyMode;
return isEmptyState ? (
<DashboardEmptyScreen
{...getEmptyScreenProps(shouldShowEditHelp, isEmptyInReadOnlyMode)}
/>
) : null;
};

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 //TODO
)
.subscribe();

inputSubscription = dashboardContainer.getInput$().subscribe(() => {});
}, [eventEmitter]);

if (dashboardDom && container) {
container.render(dashboardDom);
}
}
});
}
console.log('savedDashboardInstance', savedDashboardInstance);
console.log('appState', appState);
console.log('currentAppState', currentAppState);
console.log('isEmbeddableRendered', isEmbeddableRendered);
console.log('dashboardContainer', dashboardContainer);

return (
<div>
{savedDashboardInstance && appState && (
<DashboardTopNav
isChromeVisible={isChromeVisible}
savedDashboardInstance={savedDashboardInstance}
appState={appState}
/>
)}
<div>
{savedDashboardInstance && appState && dashboardContainer && currentAppState && (
<DashboardTopNav
isChromeVisible={isChromeVisible}
savedDashboardInstance={savedDashboardInstance}
stateContainer={appState}
currentAppState={currentAppState}
isEmbeddableRendered={isEmbeddableRendered}
dashboardContainer={dashboardContainer}
/>
)}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,22 @@ import { Filter } from 'src/plugins/data/public';
import { useCallback } from 'react';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
import { getTopNavConfig } from '../top_nav/get_top_nav_config';
import { DashboardAppState, DashboardServices, NavAction } from '../../types';
import {
DashboardAppStateContainer,
DashboardAppState,
DashboardServices,
NavAction,
} from '../../types';
import { getNavActions } from '../utils/get_nav_actions';
import { DashboardContainer } from '../embeddable';

interface DashboardTopNavProps {
isChromeVisible: boolean;
savedDashboardInstance: any;
appState: DashboardAppState;
stateContainer: DashboardAppStateContainer;
currentAppState: DashboardAppState;
isEmbeddableRendered: boolean;
dashboardContainer?: DashboardContainer;
}

enum UrlParams {
Expand All @@ -24,7 +34,14 @@ enum UrlParams {
HIDE_FILTER_BAR = 'hide-filter-bar',
}

const TopNav = ({ isChromeVisible, savedDashboardInstance, appState }: DashboardTopNavProps) => {
const TopNav = ({
isChromeVisible,
savedDashboardInstance,
stateContainer,
currentAppState,
isEmbeddableRendered,
dashboardContainer,
}: DashboardTopNavProps) => {
const [filters, setFilters] = useState<Filter[]>([]);
const [topNavMenu, setTopNavMenu] = useState<any>();
const [isFullScreenMode, setIsFullScreenMode] = useState<any>();
Expand All @@ -44,27 +61,44 @@ const TopNav = ({ isChromeVisible, savedDashboardInstance, appState }: Dashboard
};

const shouldShowNavBarComponent = (forceShow: boolean): boolean =>
(forceShow || isChromeVisible) && !appState?.fullScreenMode;
(forceShow || isChromeVisible) && !currentAppState?.fullScreenMode;

useEffect(() => {
setFilters(queryService.filterManager.getFilters());
}, [services, queryService]);

useEffect(() => {
const navActions: {
[key: string]: NavAction;
} = {}; // TODO: need to implement nav actions
setTopNavMenu(
getTopNavConfig(appState?.viewMode, navActions, dashboardConfig.getHideWriteControls())
);
}, [appState, services, dashboardConfig]);
if (isEmbeddableRendered) {
const navActions = getNavActions(
stateContainer,
savedDashboardInstance,
services,
dashboardContainer
);
setTopNavMenu(
getTopNavConfig(
currentAppState?.viewMode,
navActions,
dashboardConfig.getHideWriteControls()
)
);
}
}, [
currentAppState,
services,
dashboardConfig,
dashboardContainer,
savedDashboardInstance,
stateContainer,
isEmbeddableRendered,
]);

useEffect(() => {
setIsFullScreenMode(appState?.fullScreenMode);
}, [appState, services]);
setIsFullScreenMode(currentAppState?.fullScreenMode);
}, [currentAppState, services]);

const shouldShowFilterBar = (forceHide: boolean): boolean =>
!forceHide && (filters!.length > 0 || !appState?.fullScreenMode);
!forceHide && (filters!.length > 0 || !currentAppState?.fullScreenMode);

const forceShowTopNavMenu = shouldForceDisplay(UrlParams.SHOW_TOP_MENU);
const forceShowQueryInput = shouldForceDisplay(UrlParams.SHOW_QUERY_INPUT);
Expand Down Expand Up @@ -93,10 +127,10 @@ const TopNav = ({ isChromeVisible, savedDashboardInstance, appState }: Dashboard
return isChromeVisible ? (
<TopNavMenu
appName={'dashboard'}
savedQueryId={appState?.savedQuery}
savedQueryId={currentAppState?.savedQuery}
config={showTopNavMenu ? topNavMenu : undefined}
className={isFullScreenMode ? 'osdTopNavMenu-isFullScreen' : undefined}
screenTitle={appState.title}
screenTitle={currentAppState.title}
// showTopNavMenu={showTopNavMenu}
showSearchBar={showSearchBar}
showQueryBar={showQueryBar}
Expand Down
Loading

0 comments on commit 124fb9d

Please sign in to comment.