diff --git a/src/plugins/dashboard/public/application/components/dashboard_editor.tsx b/src/plugins/dashboard/public/application/components/dashboard_editor.tsx index c5681aa3f197..76ffa0479e7a 100644 --- a/src/plugins/dashboard/public/application/components/dashboard_editor.tsx +++ b/src/plugins/dashboard/public/application/components/dashboard_editor.tsx @@ -39,7 +39,7 @@ export const DashboardEditor = () => { savedDashboardInstance ); - const { dashboardContainer } = useDashboardContainer( + const { dashboardContainer, indexPatterns } = useDashboardContainer( services, isChromeVisible, eventEmitter, @@ -93,6 +93,7 @@ export const DashboardEditor = () => { console.log('isDirty', dashboard.isDirty); } console.log('dashboardContainer', dashboardContainer); + console.log('indexPatterns', indexPatterns); return (
@@ -109,6 +110,7 @@ export const DashboardEditor = () => { dashboard={dashboard} currentAppState={currentAppState} isEmbeddableRendered={isEmbeddableRendered} + indexPatterns={indexPatterns} dashboardContainer={dashboardContainer} /> )} diff --git a/src/plugins/dashboard/public/application/components/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/components/dashboard_top_nav.tsx index 7675b733fef0..3282dad1f3cf 100644 --- a/src/plugins/dashboard/public/application/components/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/components/dashboard_top_nav.tsx @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { uniqBy } from 'lodash'; import React, { memo, useState, useEffect } from 'react'; import { Filter, IndexPattern } from 'src/plugins/data/public'; import { useCallback } from 'react'; @@ -13,7 +12,6 @@ import { getTopNavConfig } from '../top_nav/get_top_nav_config'; import { DashboardAppStateContainer, DashboardAppState, DashboardServices } from '../../types'; import { getNavActions } from '../utils/get_nav_actions'; import { DashboardContainer } from '../embeddable'; -import { isErrorEmbeddable } from '../../embeddable_plugin'; import { Dashboard } from '../../dashboard'; interface DashboardTopNavProps { @@ -23,6 +21,7 @@ interface DashboardTopNavProps { dashboard: Dashboard; currentAppState: DashboardAppState; isEmbeddableRendered: boolean; + indexPatterns: IndexPattern[]; dashboardContainer?: DashboardContainer; } @@ -42,16 +41,14 @@ const TopNav = ({ currentAppState, isEmbeddableRendered, dashboardContainer, + indexPatterns, }: DashboardTopNavProps) => { - const [filters, setFilters] = useState([]); const [topNavMenu, setTopNavMenu] = useState(); const [isFullScreenMode, setIsFullScreenMode] = useState(); - const [indexPatterns, setIndexPatterns] = useState(); const { services } = useOpenSearchDashboards(); const { TopNavMenu } = services.navigation.ui; - const { data, dashboardConfig, setHeaderActionMenu } = services; - const { query: queryService } = data; + const { dashboardConfig, setHeaderActionMenu } = services; const location = useLocation(); const queryParameters = new URLSearchParams(location.search); @@ -76,10 +73,6 @@ const TopNav = ({ const shouldShowNavBarComponent = (forceShow: boolean): boolean => (forceShow || isChromeVisible) && !currentAppState?.fullScreenMode; - useEffect(() => { - setFilters(queryService.filterManager.getFilters()); - }, [services, queryService]); - useEffect(() => { if (isEmbeddableRendered) { const navActions = getNavActions( @@ -112,33 +105,6 @@ const TopNav = ({ setIsFullScreenMode(currentAppState?.fullScreenMode); }, [currentAppState, services]); - useEffect(() => { - const asyncSetIndexPattern = async () => { - if (dashboardContainer) { - let panelIndexPatterns: IndexPattern[] = []; - dashboardContainer.getChildIds().forEach((id) => { - const embeddableInstance = dashboardContainer.getChild(id); - if (isErrorEmbeddable(embeddableInstance)) return; - const embeddableIndexPatterns = (embeddableInstance.getOutput() as any).indexPatterns; - if (!embeddableIndexPatterns) return; - panelIndexPatterns.push(...embeddableIndexPatterns); - }); - panelIndexPatterns = uniqBy(panelIndexPatterns, 'id'); - - if (panelIndexPatterns.length > 0) { - setIndexPatterns(panelIndexPatterns); - } else { - const defaultIndex = await services.data.indexPatterns.getDefault(); - if (defaultIndex) { - setIndexPatterns([defaultIndex]); - } - } - } - }; - - asyncSetIndexPattern(); - }, [dashboardContainer, stateContainer, currentAppState, services.data.indexPatterns]); - const shouldShowFilterBar = (forceHide: boolean): boolean => !forceHide && (currentAppState.filters!.length > 0 || !currentAppState?.fullScreenMode); @@ -156,7 +122,6 @@ const TopNav = ({ return ( {}} + onSavedQueryIdChange={(savedQueryId?: string) => { + stateContainer.transitions.set('savedQuery', savedQueryId); + }} + savedQueryId={currentAppState?.savedQuery} onQuerySubmit={handleRefresh} setMenuMountPoint={isEmbeddedExternally ? undefined : setHeaderActionMenu} /> diff --git a/src/plugins/dashboard/public/application/lib/save_dashboard.ts b/src/plugins/dashboard/public/application/lib/save_dashboard.ts index 2b48d862af34..871c8d62b286 100644 --- a/src/plugins/dashboard/public/application/lib/save_dashboard.ts +++ b/src/plugins/dashboard/public/application/lib/save_dashboard.ts @@ -33,6 +33,7 @@ import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public'; import { updateSavedDashboard } from './update_saved_dashboard'; import { DashboardAppStateContainer } from '../../types'; +import { Dashboard } from '../../dashboard'; /** * Saves the dashboard. @@ -43,11 +44,12 @@ export function saveDashboard( timeFilter: TimefilterContract, stateContainer: DashboardAppStateContainer, savedDashboard: any, - saveOptions: SavedObjectSaveOpts + saveOptions: SavedObjectSaveOpts, + dashboard: Dashboard ): Promise { const appState = stateContainer.getState(); - updateSavedDashboard(savedDashboard, appState, timeFilter); + updateSavedDashboard(savedDashboard, appState, timeFilter, dashboard); return savedDashboard.save(saveOptions).then((id: string) => { if (id) { diff --git a/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts b/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts index 0baebc0bc3d5..bfbb29865794 100644 --- a/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts +++ b/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts @@ -34,11 +34,13 @@ import { FilterUtils } from './filter_utils'; import { SavedObjectDashboard } from '../../saved_dashboards'; import { DashboardAppState } from '../../types'; import { opensearchFilters } from '../../../../data/public'; +import { Dashboard } from '../../dashboard'; export function updateSavedDashboard( savedDashboard: SavedObjectDashboard, appState: DashboardAppState, - timeFilter: TimefilterContract + timeFilter: TimefilterContract, + dashboard: Dashboard ) { savedDashboard.title = appState.title; savedDashboard.description = appState.description; @@ -46,19 +48,23 @@ export function updateSavedDashboard( savedDashboard.panelsJSON = JSON.stringify(appState.panels); savedDashboard.optionsJSON = JSON.stringify(appState.options); - savedDashboard.timeFrom = savedDashboard.timeRestore + const timeFrom = savedDashboard.timeRestore ? FilterUtils.convertTimeToUTCString(timeFilter.getTime().from) : undefined; - savedDashboard.timeTo = savedDashboard.timeRestore + const timeTo = savedDashboard.timeRestore ? FilterUtils.convertTimeToUTCString(timeFilter.getTime().to) : undefined; + const timeRestoreObj: RefreshInterval = _.pick(timeFilter.getRefreshInterval(), [ 'display', 'pause', 'section', 'value', ]) as RefreshInterval; - savedDashboard.refreshInterval = savedDashboard.timeRestore ? timeRestoreObj : undefined; + const refreshInterval = savedDashboard.timeRestore ? timeRestoreObj : undefined; + savedDashboard.timeFrom = timeFrom; + savedDashboard.timeTo = timeTo; + savedDashboard.refreshInterval = refreshInterval; // save only unpinned filters const unpinnedFilters = appState.filters.filter( @@ -68,4 +74,17 @@ export function updateSavedDashboard( // save the queries savedDashboard.searchSource.setField('query', appState.query as Query); + + dashboard.setState({ + title: appState.title, + description: appState.description, + timeRestore: appState.timeRestore, + panels: appState.panels, + options: appState.options, + timeFrom, + timeTo, + refreshInterval, + query: appState.query as Query, + filters: unpinnedFilters, + }); } diff --git a/src/plugins/dashboard/public/application/utils/get_nav_actions.tsx b/src/plugins/dashboard/public/application/utils/get_nav_actions.tsx index 9c7abab68682..da0b7da7a8c5 100644 --- a/src/plugins/dashboard/public/application/utils/get_nav_actions.tsx +++ b/src/plugins/dashboard/public/application/utils/get_nav_actions.tsx @@ -373,7 +373,13 @@ export const getNavActions = ( async function save(saveOptions: SavedObjectSaveOpts) { const timefilter = queryService.timefilter.timefilter; try { - const id = await saveDashboard(timefilter, stateContainer, savedDashboard, saveOptions); + const id = await saveDashboard( + timefilter, + stateContainer, + savedDashboard, + saveOptions, + dashboard + ); if (id) { notifications.toasts.addSuccess({ diff --git a/src/plugins/dashboard/public/application/utils/use/use_dashboard_container.tsx b/src/plugins/dashboard/public/application/utils/use/use_dashboard_container.tsx index df777460018a..e364a0de293a 100644 --- a/src/plugins/dashboard/public/application/utils/use/use_dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/utils/use/use_dashboard_container.tsx @@ -4,7 +4,7 @@ */ import React, { useState } from 'react'; -import { cloneDeep, isEqual } from 'lodash'; +import { cloneDeep, isEqual, uniqBy } from 'lodash'; import { EMPTY, Observable, Subscription, merge, of, pipe } from 'rxjs'; import { catchError, @@ -62,6 +62,7 @@ export const useDashboardContainer = ( appState?: DashboardAppStateContainer ) => { const [dashboardContainer, setDashboardContainer] = useState(); + const [indexPatterns, setIndexPatterns] = useState([]); useEffect(() => { const getDashboardContainer = async () => { @@ -71,7 +72,8 @@ export const useDashboardContainer = ( savedDashboardInstance, services, appState, - dashboard + dashboard, + setIndexPatterns ); setDashboardContainer(dashboardContainerEmbeddable); @@ -105,14 +107,15 @@ export const useDashboardContainer = ( } }, [dashboardContainer, services]); - return { dashboardContainer }; + return { dashboardContainer, indexPatterns }; }; const createDashboardEmbeddable = ( savedDash: any, dashboardServices: DashboardServices, appState: DashboardAppStateContainer, - dashboard: Dashboard + dashboard: Dashboard, + setIndexPatterns: React.Dispatch> ) => { let dashboardContainer: DashboardContainer; let inputSubscription: Subscription | undefined; @@ -240,6 +243,45 @@ const createDashboardEmbeddable = ( expandedPanelId: appStateData.expandedPanelId, }; }; + const setCurrentIndexPatterns = () => { + let panelIndexPatterns: IndexPattern[] = []; + dashboardContainer.getChildIds().forEach((id) => { + const embeddableInstance = dashboardContainer.getChild(id); + if (isErrorEmbeddable(embeddableInstance)) return; + const embeddableIndexPatterns = (embeddableInstance.getOutput() as any).indexPatterns; + if (!embeddableIndexPatterns) return; + panelIndexPatterns.push(...embeddableIndexPatterns); + }); + panelIndexPatterns = uniqBy(panelIndexPatterns, 'id'); + return panelIndexPatterns; + }; + + const updateIndexPatternsOperator = pipe( + filter((container: DashboardContainer) => !!container && !isErrorEmbeddable(container)), + map(setCurrentIndexPatterns), + distinctUntilChanged((a, b) => + deepEqual( + a.map((ip) => ip.id), + b.map((ip) => ip.id) + ) + ), + // using switchMap for previous task cancellation + switchMap((panelIndexPatterns: IndexPattern[]) => { + return new Observable((observer) => { + if (panelIndexPatterns && panelIndexPatterns.length > 0) { + if (observer.closed) return; + setIndexPatterns(panelIndexPatterns); + observer.complete(); + } else { + data.indexPatterns.getDefault().then((defaultIndexPattern) => { + if (observer.closed) return; + setIndexPatterns([defaultIndexPattern as IndexPattern]); + observer.complete(); + }); + } + }); + }) + ); if (dashboardFactory) { return dashboardFactory @@ -321,7 +363,8 @@ const createDashboardEmbeddable = ( ) .pipe( mapTo(dashboardContainer), - startWith(dashboardContainer) // to trigger initial index pattern update + startWith(dashboardContainer), // to trigger initial index pattern update + updateIndexPatternsOperator ) .subscribe(); diff --git a/src/plugins/dashboard/public/application/utils/use/use_editor_updates.ts b/src/plugins/dashboard/public/application/utils/use/use_editor_updates.ts index bb3b4fe09b1a..31feef28e607 100644 --- a/src/plugins/dashboard/public/application/utils/use/use_editor_updates.ts +++ b/src/plugins/dashboard/public/application/utils/use/use_editor_updates.ts @@ -43,7 +43,13 @@ export const useEditorUpdates = ( if (changes) { dashboardContainer.updateInput(changes); - if (changes.filters || changes.query || changes.timeRange || changes.refreshConfig) { + if (changes.timeRange || changes.refreshConfig) { + if (dashboardInstance.timeRestore) { + dashboard.isDirty = true; + } + } + + if (changes.filters || changes.query) { dashboard.isDirty = true; } } diff --git a/src/plugins/dashboard/public/dashboard.ts b/src/plugins/dashboard/public/dashboard.ts index 4dc192c4d875..668d3afb09a6 100644 --- a/src/plugins/dashboard/public/dashboard.ts +++ b/src/plugins/dashboard/public/dashboard.ts @@ -73,7 +73,7 @@ export class Dashboard { this.filters = cloneDeep(dashboardState.filters); } - async setState(state: PartialDashboardState) { + setState(state: PartialDashboardState) { if (state.id) { this.id = state.id; } @@ -109,12 +109,12 @@ export class Dashboard { if (state.searchSource) { this.searchSource = state.searchSource; } - // if (state.query) { - // this.query = this.getQuery(state.query); - // } - // if (state.filters) { - // this.filters = this.getFilters(state.filters); - // } + if (state.query) { + this.query = state.query; + } + if (state.filters) { + this.filters = state.filters; + } } public setIsDirty(value: boolean) { diff --git a/src/plugins/dashboard/public/saved_dashboards/_saved_dashboard.ts b/src/plugins/dashboard/public/saved_dashboards/_saved_dashboard.ts index 98923f5aa0db..741c9871f51f 100644 --- a/src/plugins/dashboard/public/saved_dashboards/_saved_dashboard.ts +++ b/src/plugins/dashboard/public/saved_dashboards/_saved_dashboard.ts @@ -39,114 +39,3 @@ export const convertToSerializedDashboard = ( filters: savedDashboard.getFilters(), }; }; - -/* export const convertFromSerializedDashboard = ( - serializedDashboard: SerializedDashboard - ): ISavedDashboard => { - const { - id, - timeRestore, - timeTo, - timeFrom, - refreshInterval, - description, - panels, - options, - uiState, - lastSavedTitle, - searchSource, - query, - filters, - } = serializedDashboard; - - return { - id, - timeRestore, - timeTo, - timeFrom, - description, - panelsJSON: JSON.stringify(panels), - optionsJSON: JSON.stringify(options), - uiStateJSON: JSON.stringify(uiState), - lastSavedTitle, - refreshInterval, - searchSource, - getQuery: () => query, - getFilters: () => filters, - }; - }; - - export function createSavedDashboardClass( - services: SavedObjectOpenSearchDashboardsServices - ): new (id: string) => SavedObjectDashboard { - const SavedObjectClass = createSavedObjectClass(services); - class SavedDashboard extends SavedObjectClass { - public static type = 'dashboard'; - public static mapping: Record = { - title: 'text', - hits: 'integer', - description: 'text', - panelsJSON: 'text', - optionsJSON: 'text', - version: 'integer', - timeRestore: 'boolean', - timeTo: 'keyword', - timeFrom: 'keyword', - refreshInterval: { - type: 'object', - properties: { - display: { type: 'keyword' }, - pause: { type: 'boolean' }, - section: { type: 'integer' }, - value: { type: 'integer' }, - }, - }, - }; - // Order these fields to the top, the rest are alphabetical - public static fieldOrder = ['title', 'description']; - public static searchSource = true; - public showInRecentlyAccessed = true; - - constructor(id: string) { - super({ - type: SavedDashboard.type, - mapping: SavedDashboard.mapping, - searchSource: SavedDashboard.searchSource, - extractReferences, - injectReferences, - - // if this is null/undefined then the SavedObject will be assigned the defaults - id, - - // default values that will get assigned if the doc is new - defaults: { - title: '', - hits: 0, - description: '', - panelsJSON: '[]', - optionsJSON: JSON.stringify({ - // for BWC reasons we can't default dashboards that already exist without this setting to true. - useMargins: !id, - hidePanelTitles: false, - }), - version: 1, - timeRestore: false, - timeTo: undefined, - timeFrom: undefined, - refreshInterval: undefined, - }, - }); - this.getFullPath = () => `/app/dashboardsNew#${createDashboardEditUrl(String(this.id))}`; - } - - getQuery() { - return this.searchSource!.getOwnField('query') || { query: '', language: 'kuery' }; - } - - getFilters() { - return this.searchSource!.getOwnField('filter') || []; - } - } - - return (SavedDashboard as unknown) as new (id: string) => SavedObjectDashboard; - }*/