Skip to content

Commit

Permalink
Basic top nav bar for dashboard
Browse files Browse the repository at this point in the history
Signed-off-by: abbyhu2000 <[email protected]>
  • Loading branch information
abbyhu2000 committed Jun 1, 2023
1 parent 6e5ddd3 commit 7142304
Show file tree
Hide file tree
Showing 14 changed files with 1,174 additions and 63 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,223 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
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 { useDashboardAppState } from '../utils/use/use_dashboard_app_state';

export const DashboardEditor = () => {
return <div>Dashboard Editor</div>;
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(
services,
eventEmitter,
isChromeVisible,
dashboardIdFromUrl
);

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 [dashboardContainer, setDashboardContainer] = useState<DashboardContainer>({});
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 getDashboardInput = () => {
const embeddablesMap: {
[key: string]: DashboardPanelState;
} = {};
appStateData.panels.forEach((panel: SavedDashboardPanel) => {
embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState(panel);
});

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(() => {
if (dashboardFactory) {
dashboardFactory
.create(getDashboardInput())
.then((container: DashboardContainer | ErrorEmbeddable | undefined) => {
if (container && !isErrorEmbeddable(container)) {
// dashboardContainer = container;
setDashboardContainer(container);

}
});
}
}, [dashboardFactory, getDashboardInput]);

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((foo) => {
console.log(foo);
});

if (dashboardDom && dashboardContainer) {
console.log('dashboard container inside', dashboardContainer);
dashboardContainer.render(dashboardDom);
}


return (
<div>
{savedDashboardInstance && appState && (
<DashboardTopNav
isChromeVisible={isChromeVisible}
savedDashboardInstance={savedDashboardInstance}
currentAppState={appStateData}
stateContainer={appState}
dashboardContainer={dashboardContainer}
/>
)}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,143 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import React, { memo, useState, useEffect } from 'react';
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 {
DashboardAppStateContainer,
DashboardAppState,
DashboardServices,
NavAction,
} from '../../types';
import { getNavActions } from '../utils/get_nav_actions';
import { DashboardContainer } from '../embeddable';

export const DashboardTopNav = () => {
return <div>Dashboard Top Nav</div>;
interface DashboardTopNavProps {
isChromeVisible: boolean;
savedDashboardInstance: any;
currentAppState: DashboardAppState;
stateContainer: DashboardAppStateContainer;
dashboardContainer?: DashboardContainer;
}

enum UrlParams {
SHOW_TOP_MENU = 'show-top-menu',
SHOW_QUERY_INPUT = 'show-query-input',
SHOW_TIME_FILTER = 'show-time-filter',
SHOW_FILTER_BAR = 'show-filter-bar',
HIDE_FILTER_BAR = 'hide-filter-bar',
}

const TopNav = ({
isChromeVisible,
savedDashboardInstance,
currentAppState,
stateContainer,
dashboardContainer,
}: DashboardTopNavProps) => {
const [filters, setFilters] = useState<Filter[]>([]);
const [topNavMenu, setTopNavMenu] = useState<any>();
const [isFullScreenMode, setIsFullScreenMode] = useState<any>();

const { services } = useOpenSearchDashboards<DashboardServices>();
const { TopNavMenu } = services.navigation.ui;
const { data, dashboardConfig, setHeaderActionMenu } = services;
const { query: queryService } = data;

// TODO: this should base on URL
const isEmbeddedExternally = false;

// TODO: should use URL params
const shouldForceDisplay = (param: string): boolean => {
// const [searchParams] = useSearchParams();
return false;
};

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

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

useEffect(() => {
const navActions = getNavActions(
stateContainer,
savedDashboardInstance,
services,
dashboardContainer
);
setTopNavMenu(
getTopNavConfig(currentAppState?.viewMode, navActions, dashboardConfig.getHideWriteControls())
);
}, [
currentAppState,
services,
dashboardConfig,
dashboardContainer,
savedDashboardInstance,
stateContainer,
]);

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

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

const forceShowTopNavMenu = shouldForceDisplay(UrlParams.SHOW_TOP_MENU);
const forceShowQueryInput = shouldForceDisplay(UrlParams.SHOW_QUERY_INPUT);
const forceShowDatePicker = shouldForceDisplay(UrlParams.SHOW_TIME_FILTER);
const forceHideFilterBar = shouldForceDisplay(UrlParams.HIDE_FILTER_BAR);
const showTopNavMenu = shouldShowNavBarComponent(forceShowTopNavMenu);
const showQueryInput = shouldShowNavBarComponent(forceShowQueryInput);
const showDatePicker = shouldShowNavBarComponent(forceShowDatePicker);
const showQueryBar = showQueryInput || showDatePicker;
const showFilterBar = shouldShowFilterBar(forceHideFilterBar);
const showSearchBar = showQueryBar || showFilterBar;

// TODO: implement handleRefresh
const handleRefresh = useCallback((_payload: any, isUpdate?: boolean) => {
/* if (isUpdate === false) {
// The user can still request a reload in the query bar, even if the
// query is the same, and in that case, we have to explicitly ask for
// a reload, since no state changes will cause it.
lastReloadRequestTime = new Date().getTime();
const changes = getChangesFromAppStateForContainerState();
if (changes && dashboardContainer) {
dashboardContainer.updateInput(changes);
}*/
}, []);

console.log('currentAppState', currentAppState);
console.log('state container get()', stateContainer.get());
console.log('dashboard container top nav', dashboardContainer);
return isChromeVisible ? (
<TopNavMenu
appName={'dashboard'}
savedQueryId={currentAppState?.savedQuery}
config={showTopNavMenu ? topNavMenu : undefined}
className={isFullScreenMode ? 'osdTopNavMenu-isFullScreen' : undefined}
screenTitle={currentAppState.title}
// showTopNavMenu={showTopNavMenu}
showSearchBar={showSearchBar}
showQueryBar={showQueryBar}
showQueryInput={showQueryInput}
showDatePicker={showDatePicker}
showFilterBar={showFilterBar}
useDefaultBehaviors={true}
indexPatterns={[]}
showSaveQuery={services.dashboardCapabilities.saveQuery as boolean}
savedQuery={undefined}
onSavedQueryIdChange={() => {}}
onQuerySubmit={handleRefresh}
setMenuMountPoint={isEmbeddedExternally ? undefined : setHeaderActionMenu}
/>
) : null;
};

export const DashboardTopNav = memo(TopNav);
Loading

0 comments on commit 7142304

Please sign in to comment.