-
Notifications
You must be signed in to change notification settings - Fork 916
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Basic top nav bar for dashboard (#4108)
Basic top nav bar for dashboard This PR will add basic structure to render top nav bar, including a basic implementation for dashboard app state. This is not functionality complete, but to help implement a basic working dashboard app as the first step. Signed-off-by: abbyhu2000 <abigailhu2000@gmail.com>
1 parent
c1644c6
commit 65d57f7
Showing
8 changed files
with
709 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
src/plugins/dashboard/public/application/utils/create_dashboard_app_state.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { migrateAppState } from '../lib/migrate_app_state'; | ||
import { | ||
IOsdUrlStateStorage, | ||
createStateContainer, | ||
syncState, | ||
} from '../../../../opensearch_dashboards_utils/public'; | ||
import { | ||
DashboardAppState, | ||
DashboardAppStateTransitions, | ||
DashboardAppStateInUrl, | ||
DashboardServices, | ||
} from '../../types'; | ||
import { ViewMode } from '../../embeddable_plugin'; | ||
import { getDashboardIdFromUrl } from '../lib'; | ||
|
||
const STATE_STORAGE_KEY = '_a'; | ||
|
||
interface Arguments { | ||
osdUrlStateStorage: IOsdUrlStateStorage; | ||
stateDefaults: DashboardAppState; | ||
services: DashboardServices; | ||
instance: any; | ||
} | ||
|
||
export const createDashboardAppState = ({ | ||
stateDefaults, | ||
osdUrlStateStorage, | ||
services, | ||
instance, | ||
}: Arguments) => { | ||
const urlState = osdUrlStateStorage.get<DashboardAppState>(STATE_STORAGE_KEY); | ||
const { opensearchDashboardsVersion, usageCollection, history } = services; | ||
const initialState = migrateAppState( | ||
{ | ||
...stateDefaults, | ||
...urlState, | ||
}, | ||
opensearchDashboardsVersion, | ||
usageCollection | ||
); | ||
|
||
const pureTransitions = { | ||
set: (state) => (prop, value) => ({ ...state, [prop]: value }), | ||
setOption: (state) => (option, value) => ({ | ||
...state, | ||
options: { | ||
...state.options, | ||
[option]: value, | ||
}, | ||
}), | ||
} as DashboardAppStateTransitions; | ||
/* | ||
make sure url ('_a') matches initial state | ||
Initializing appState does two things - first it translates the defaults into AppState, | ||
second it updates appState based on the url (the url trumps the defaults). This means if | ||
we update the state format at all and want to handle BWC, we must not only migrate the | ||
data stored with saved vis, but also any old state in the url. | ||
*/ | ||
osdUrlStateStorage.set(STATE_STORAGE_KEY, initialState, { replace: true }); | ||
|
||
const stateContainer = createStateContainer<DashboardAppState, DashboardAppStateTransitions>( | ||
initialState, | ||
pureTransitions | ||
); | ||
|
||
const toUrlState = (state: DashboardAppState): DashboardAppStateInUrl => { | ||
if (state.viewMode === ViewMode.VIEW) { | ||
const { panels, ...stateWithoutPanels } = state; | ||
return stateWithoutPanels; | ||
} | ||
return state; | ||
}; | ||
|
||
const { start: startStateSync, stop: stopStateSync } = syncState({ | ||
storageKey: STATE_STORAGE_KEY, | ||
stateContainer: { | ||
...stateContainer, | ||
get: () => toUrlState(stateContainer.get()), | ||
set: (state: DashboardAppStateInUrl | null) => { | ||
// sync state required state container to be able to handle null | ||
// overriding set() so it could handle null coming from url | ||
if (state) { | ||
// Skip this update if current dashboardId in the url is different from what we have in the current instance of state manager | ||
// As dashboard is driven by angular at the moment, the destroy cycle happens async, | ||
// If the dashboardId has changed it means this instance | ||
// is going to be destroyed soon and we shouldn't sync state anymore, | ||
// as it could potentially trigger further url updates | ||
const currentDashboardIdInUrl = getDashboardIdFromUrl(history.location.pathname); | ||
if (currentDashboardIdInUrl !== instance.id) return; | ||
|
||
stateContainer.set({ | ||
...stateDefaults, | ||
...state, | ||
}); | ||
} else { | ||
// Do nothing in case when state from url is empty, | ||
// this fixes: https://github.com/elastic/kibana/issues/57789 | ||
// There are not much cases when state in url could become empty: | ||
// 1. User manually removed `_a` from the url | ||
// 2. Browser is navigating away from the page and most likely there is no `_a` in the url. | ||
// In this case we don't want to do any state updates | ||
// and just allow $scope.$on('destroy') fire later and clean up everything | ||
} | ||
}, | ||
}, | ||
stateStorage: osdUrlStateStorage, | ||
}); | ||
|
||
// start syncing the appState with the ('_a') url | ||
startStateSync(); | ||
return { stateContainer, stopStateSync }; | ||
}; |
27 changes: 27 additions & 0 deletions
27
src/plugins/dashboard/public/application/utils/use/use_chrome_visibility.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Any modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
import { useState, useEffect } from 'react'; | ||
import { ChromeStart } from 'opensearch-dashboards/public'; | ||
|
||
export const useChromeVisibility = (chrome: ChromeStart) => { | ||
const [isVisible, setIsVisible] = useState<boolean>(true); | ||
|
||
useEffect(() => { | ||
const subscription = chrome.getIsVisible$().subscribe((value: boolean) => { | ||
setIsVisible(value); | ||
}); | ||
|
||
return () => subscription.unsubscribe(); | ||
}, [chrome]); | ||
|
||
return isVisible; | ||
}; |
88 changes: 88 additions & 0 deletions
88
src/plugins/dashboard/public/application/utils/use/use_dashboard_app_state.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import EventEmitter from 'events'; | ||
import { useEffect, useState } from 'react'; | ||
import { cloneDeep } from 'lodash'; | ||
import { map } from 'rxjs/operators'; | ||
import { connectToQueryState, opensearchFilters } from '../../../../../data/public'; | ||
import { migrateLegacyQuery } from '../../lib/migrate_legacy_query'; | ||
import { DashboardServices } from '../../../types'; | ||
|
||
import { DashboardAppStateContainer } from '../../../types'; | ||
import { migrateAppState, getAppStateDefaults } from '../../lib'; | ||
import { createDashboardAppState } from '../create_dashboard_app_state'; | ||
|
||
/** | ||
* This effect is responsible for instantiating the dashboard app state container, | ||
* which is in sync with "_a" url param | ||
*/ | ||
export const useDashboardAppState = ( | ||
services: DashboardServices, | ||
eventEmitter: EventEmitter, | ||
instance: any | ||
) => { | ||
const [appState, setAppState] = useState<DashboardAppStateContainer | null>(null); | ||
|
||
useEffect(() => { | ||
if (!instance) { | ||
return; | ||
} | ||
const { dashboardConfig, usageCollection, opensearchDashboardsVersion } = services; | ||
const hideWriteControls = dashboardConfig.getHideWriteControls(); | ||
const stateDefaults = migrateAppState( | ||
getAppStateDefaults(instance, hideWriteControls), | ||
opensearchDashboardsVersion, | ||
usageCollection | ||
); | ||
|
||
const { stateContainer, stopStateSync } = createDashboardAppState({ | ||
stateDefaults, | ||
osdUrlStateStorage: services.osdUrlStateStorage, | ||
services, | ||
instance, | ||
}); | ||
|
||
const { filterManager, queryString } = services.data.query; | ||
|
||
// sync initial app state from state container to managers | ||
filterManager.setAppFilters(cloneDeep(stateContainer.getState().filters)); | ||
queryString.setQuery(migrateLegacyQuery(stateContainer.getState().query)); | ||
|
||
// setup syncing of app filters between app state and query services | ||
const stopSyncingAppFilters = connectToQueryState( | ||
services.data.query, | ||
{ | ||
set: ({ filters, query }) => { | ||
stateContainer.transitions.set('filters', filters || []); | ||
stateContainer.transitions.set('query', query || queryString.getDefaultQuery()); | ||
}, | ||
get: () => ({ | ||
filters: stateContainer.getState().filters, | ||
query: migrateLegacyQuery(stateContainer.getState().query), | ||
}), | ||
state$: stateContainer.state$.pipe( | ||
map((state) => ({ | ||
filters: state.filters, | ||
query: queryString.formatQuery(state.query), | ||
})) | ||
), | ||
}, | ||
{ | ||
filters: opensearchFilters.FilterStateStore.APP_STATE, | ||
query: true, | ||
} | ||
); | ||
|
||
setAppState(stateContainer); | ||
|
||
return () => { | ||
stopStateSync(); | ||
stopSyncingAppFilters(); | ||
}; | ||
}, [eventEmitter, instance, services]); | ||
|
||
return { appState }; | ||
}; |
120 changes: 120 additions & 0 deletions
120
src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { i18n } from '@osd/i18n'; | ||
import { EventEmitter } from 'events'; | ||
import { useEffect, useRef, useState } from 'react'; | ||
import { | ||
redirectWhenMissing, | ||
SavedObjectNotFound, | ||
} from '../../../../../opensearch_dashboards_utils/public'; | ||
import { DashboardConstants } from '../../../dashboard_constants'; | ||
import { DashboardServices } from '../../../types'; | ||
|
||
/** | ||
* This effect is responsible for instantiating a saved dashboard or creating a new one | ||
* using url parameters, embedding and destroying it in DOM | ||
*/ | ||
export const useSavedDashboardInstance = ( | ||
services: DashboardServices, | ||
eventEmitter: EventEmitter, | ||
isChromeVisible: boolean | undefined, | ||
dashboardIdFromUrl: string | undefined | ||
) => { | ||
const [state, setState] = useState<{ | ||
savedDashboardInstance?: any; | ||
}>({}); | ||
|
||
const dashboardId = useRef(''); | ||
|
||
useEffect(() => { | ||
const getSavedDashboardInstance = async () => { | ||
const { | ||
application: { navigateToApp }, | ||
chrome, | ||
history, | ||
http: { basePath }, | ||
notifications, | ||
savedDashboards, | ||
} = services; | ||
try { | ||
console.log('trying to get saved dashboard'); | ||
let savedDashboardInstance: any; | ||
if (history.location.pathname === '/create') { | ||
try { | ||
savedDashboardInstance = await savedDashboards.get(); | ||
} catch { | ||
redirectWhenMissing({ | ||
history, | ||
basePath, | ||
navigateToApp, | ||
mapping: { | ||
dashboard: DashboardConstants.LANDING_PAGE_PATH, | ||
}, | ||
toastNotifications: notifications.toasts, | ||
}); | ||
} | ||
} else if (dashboardIdFromUrl) { | ||
try { | ||
savedDashboardInstance = await savedDashboards.get(dashboardIdFromUrl); | ||
chrome.recentlyAccessed.add( | ||
savedDashboardInstance.getFullPath(), | ||
savedDashboardInstance.title, | ||
dashboardIdFromUrl | ||
); | ||
console.log('saved dashboard', savedDashboardInstance); | ||
} catch (error) { | ||
// Preserve BWC of v5.3.0 links for new, unsaved dashboards. | ||
// See https://github.com/elastic/kibana/issues/10951 for more context. | ||
if (error instanceof SavedObjectNotFound && dashboardIdFromUrl === 'create') { | ||
// Note preserve querystring part is necessary so the state is preserved through the redirect. | ||
history.replace({ | ||
...history.location, // preserve query, | ||
pathname: DashboardConstants.CREATE_NEW_DASHBOARD_URL, | ||
}); | ||
|
||
notifications.toasts.addWarning( | ||
i18n.translate('dashboard.urlWasRemovedInSixZeroWarningMessage', { | ||
defaultMessage: | ||
'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.', | ||
}) | ||
); | ||
return new Promise(() => {}); | ||
} else { | ||
// E.g. a corrupt or deleted dashboard | ||
notifications.toasts.addDanger(error.message); | ||
history.push(DashboardConstants.LANDING_PAGE_PATH); | ||
return new Promise(() => {}); | ||
} | ||
} | ||
} | ||
|
||
setState({ savedDashboardInstance }); | ||
} catch (error) {} | ||
}; | ||
|
||
if (isChromeVisible === undefined) { | ||
// waiting for specifying chrome | ||
return; | ||
} | ||
|
||
if (!dashboardId.current) { | ||
dashboardId.current = dashboardIdFromUrl || 'new'; | ||
getSavedDashboardInstance(); | ||
} else if ( | ||
dashboardIdFromUrl && | ||
dashboardId.current !== dashboardIdFromUrl && | ||
state.savedDashboardInstance?.id !== dashboardIdFromUrl | ||
) { | ||
dashboardId.current = dashboardIdFromUrl; | ||
setState({}); | ||
getSavedDashboardInstance(); | ||
} | ||
}, [eventEmitter, isChromeVisible, services, state.savedDashboardInstance, dashboardIdFromUrl]); | ||
|
||
return { | ||
...state, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { Filter } from 'src/plugins/data/public'; | ||
import { DashboardServices } from '../../types'; | ||
|
||
export const getDefaultQuery = ({ data }: DashboardServices) => { | ||
return data.query.queryString.getDefaultQuery(); | ||
}; | ||
|
||
export const dashboardStateToEditorState = ( | ||
dashboardInstance: any, | ||
services: DashboardServices | ||
) => { | ||
const savedDashboardState = { | ||
id: dashboardInstance.id, | ||
title: dashboardInstance.title, | ||
description: dashboardInstance.description, | ||
searchSource: dashboardInstance.searchSource, | ||
savedSearchId: dashboardInstance.savedSearchId, | ||
}; | ||
return { | ||
query: dashboardInstance.searchSource?.getOwnField('query') || getDefaultQuery(services), | ||
filters: (dashboardInstance.searchSource?.getOwnField('filter') as Filter[]) || [], | ||
savedDashboardState, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters