Skip to content

Commit

Permalink
[Dashboard De-Angular] Add dashboard class for discard flow (#4478)
Browse files Browse the repository at this point in the history
* Add Dashboard class for state managing

Signed-off-by: abbyhu2000 <[email protected]>

* isDirty working for cancel flow

Signed-off-by: abbyhu2000 <[email protected]>

---------

Signed-off-by: abbyhu2000 <[email protected]>
  • Loading branch information
abbyhu2000 committed Jul 5, 2023
1 parent a491e06 commit 7d005a1
Show file tree
Hide file tree
Showing 14 changed files with 488 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const DashboardEditor = () => {
const isChromeVisible = useChromeVisibility(services.chrome);
const [eventEmitter] = useState(new EventEmitter());

const savedDashboardInstance = useSavedDashboardInstance(
const { savedDashboard: savedDashboardInstance, dashboard } = useSavedDashboardInstance(
services,
eventEmitter,
isChromeVisible,
Expand All @@ -34,13 +34,15 @@ export const DashboardEditor = () => {
services,
isChromeVisible,
eventEmitter,
dashboard,
savedDashboardInstance,
appState
);

const { isEmbeddableRendered, currentAppState } = useEditorUpdates(
services,
eventEmitter,
dashboard,
savedDashboardInstance,
dashboardContainer,
appState
Expand All @@ -54,25 +56,32 @@ export const DashboardEditor = () => {
}, [eventEmitter]);

console.log('savedDashboardInstance', savedDashboardInstance);
console.log('dashboard', dashboard);
console.log('appState', appState);
console.log('appStateData', appState?.getState());
console.log('currentAppState', currentAppState);
console.log('isEmbeddableRendered', isEmbeddableRendered);
console.log('app state isDirty', appState?.getState().isDirty);
console.log('dashboardContainer', dashboardContainer);

return (
<div>
<div>
{savedDashboardInstance && appState && dashboardContainer && currentAppState && (
<DashboardTopNav
isChromeVisible={isChromeVisible}
savedDashboardInstance={savedDashboardInstance}
stateContainer={appState}
currentAppState={currentAppState}
isEmbeddableRendered={isEmbeddableRendered}
dashboardContainer={dashboardContainer}
/>
)}
{savedDashboardInstance &&
appState &&
dashboardContainer &&
currentAppState &&
dashboard && (
<DashboardTopNav
isChromeVisible={isChromeVisible}
savedDashboardInstance={savedDashboardInstance}
stateContainer={appState}
dashboard={dashboard}
currentAppState={currentAppState}
isEmbeddableRendered={isEmbeddableRendered}
dashboardContainer={dashboardContainer}
/>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import { DashboardAppStateContainer, DashboardAppState, DashboardServices } from
import { getNavActions } from '../utils/get_nav_actions';
import { DashboardContainer } from '../embeddable';
import { isErrorEmbeddable } from '../../embeddable_plugin';
import { Dashboard } from '../../dashboard';

interface DashboardTopNavProps {
isChromeVisible: boolean;
savedDashboardInstance: any;
stateContainer: DashboardAppStateContainer;
dashboard: Dashboard;
currentAppState: DashboardAppState;
isEmbeddableRendered: boolean;
dashboardContainer?: DashboardContainer;
Expand All @@ -36,6 +38,7 @@ const TopNav = ({
isChromeVisible,
savedDashboardInstance,
stateContainer,
dashboard,
currentAppState,
isEmbeddableRendered,
dashboardContainer,
Expand Down Expand Up @@ -83,6 +86,7 @@ const TopNav = ({
stateContainer,
savedDashboardInstance,
services,
dashboard,
dashboardContainer
);
setTopNavMenu(
Expand All @@ -101,6 +105,7 @@ const TopNav = ({
savedDashboardInstance,
stateContainer,
isEmbeddableRendered,
dashboard
]);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@ export function getAppStateDefaults(
query: savedDashboard.getQuery(),
filters: savedDashboard.getFilters(),
viewMode: savedDashboard.id || hideWriteControls ? ViewMode.VIEW : ViewMode.EDIT,
isDirty: false,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const createDashboardAppState = ({
[option]: value,
},
}),
// setDashboard: (state)
} as DashboardAppStateTransitions;
/*
make sure url ('_a') matches initial state
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 { Dashboard, DashboardParams } from '../../dashboard';
import { SavedObjectDashboard } from '../../saved_dashboards';
import { convertToSerializedDashboard } from '../../saved_dashboards/_saved_dashboard';
import { DashboardServices } from '../../types';

export const getDashboardInstance = async (
dashboardServices: DashboardServices,
/**
* opts can be either a saved dashboard id passed as string,
* or an object of new dashboard params.
* Both come from url search query
*/
opts?: Record<string, unknown> | string
): Promise<{
savedDashboard: SavedObjectDashboard;
dashboard: Dashboard<DashboardParams>;
}> => {
const { savedDashboards } = dashboardServices;

// Get the existing dashboard/default new dashboard from saved object loader
const savedDashboard: SavedObjectDashboard = await savedDashboards.get(opts);
// Serialized the saved object dashboard
const serializedDashboard = convertToSerializedDashboard(savedDashboard);
// Create a Dashboard class using the serialized dashboard
// const dashboard = (await dashboards.createDashboard(serializedDashboard)) as Dashboard;
const dashboard = new Dashboard(serializedDashboard);
await dashboard.setState(serializedDashboard);

return {
savedDashboard,
dashboard,
};
};
56 changes: 34 additions & 22 deletions src/plugins/dashboard/public/application/utils/get_nav_actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { DashboardContainer } from '../embeddable/dashboard_container';
import { DashboardConstants, createDashboardEditUrl } from '../../dashboard_constants';
import { unhashUrl } from '../../../../opensearch_dashboards_utils/public';
import { UrlParams } from '../components/dashboard_top_nav';
import { Dashboard } from '../../dashboard';

interface UrlParamsSelectedMap {
[UrlParams.SHOW_TOP_MENU]: boolean;
Expand All @@ -46,6 +47,7 @@ export const getNavActions = (
stateContainer: DashboardAppStateContainer,
savedDashboard: any,
services: DashboardServices,
dashboard: Dashboard,
dashboardContainer?: DashboardContainer
) => {
const {
Expand Down Expand Up @@ -292,40 +294,50 @@ export const getNavActions = (
function onChangeViewMode(newMode: ViewMode) {
const isPageRefresh = newMode === appState.viewMode;
const isLeavingEditMode = !isPageRefresh && newMode === ViewMode.VIEW;
// TODO: check if any query and filter changed
const willLoseChanges = isLeavingEditMode;
const willLoseChanges = isLeavingEditMode && stateContainer.getState().isDirty === true;

// If there are no changes, do not show the discard window
if (!willLoseChanges) {
stateContainer.transitions.set('viewMode', newMode);
return;
}

// If there are changes, show the discard window, and reset the states to original
function revertChangesAndExitEditMode() {
stateContainer.transitions.set('viewMode', ViewMode.VIEW);
const pathname = savedDashboard.id
? createDashboardEditUrl(savedDashboard.id)
: DashboardConstants.CREATE_NEW_DASHBOARD_URL;
history.push(pathname);

/* dashboardStateManager.resetState();
// This is only necessary for new dashboards, which will default to Edit mode.
updateViewMode(ViewMode.VIEW);
// We need to do a hard reset of the timepicker. appState will not reload like
// it does on 'open' because it's been saved to the url and the getAppState.previouslyStored() check on
// reload will cause it not to sync.
if (dashboardStateManager.getIsTimeSavedWithDashboard()) {
dashboardStateManager.syncTimefilterWithDashboardTime(timefilter);
dashboardStateManager.syncTimefilterWithDashboardRefreshInterval(timefilter);
}
// Angular's $location skips this update because of history updates from syncState which happen simultaneously
// when calling osdUrl.change() angular schedules url update and when angular finally starts to process it,
// the update is considered outdated and angular skips it
// so have to use implementation of dashboardStateManager.changeDashboardUrl, which workarounds those issues
dashboardStateManager.changeDashboardUrl(
dash.id ? createDashboardEditUrl(dash.id) : DashboardConstants.CREATE_NEW_DASHBOARD_URL
);*/
// This is only necessary for new dashboards, which will default to Edit mode.
stateContainer.transitions.set('viewMode', ViewMode.VIEW);

// We need to reset the app state to its original state
if (dashboard.panels) {
stateContainer.transitions.set('panels', dashboard.panels);
}

stateContainer.transitions.set('filters', dashboard.filters);
stateContainer.transitions.set('query', dashboard.query);
stateContainer.transitions.setOption('hidePanelTitles', dashboard.options.hidePanelTitles);
stateContainer.transitions.setOption('useMargins', dashboard.options.useMargins);

// Need to see if needed
stateContainer.transitions.set('timeRestore', dashboard.timeRestore);

// Since time filters are not tracked by app state, we need to manually reset it
if (stateContainer.getState().timeRestore) {
queryService.timefilter.timefilter.setTime({
from: dashboard.timeFrom,
to: dashboard.timeTo,
});
if (dashboard.refreshInterval) {
queryService.timefilter.timefilter.setRefreshInterval(dashboard.refreshInterval);
}
}

// Set the isDirty flag back to false since we discard all the changes
stateContainer.transitions.set('isDirty', false);
}

overlays
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { DashboardServices } from '../../../types';
import { DashboardAppStateContainer } from '../../../types';
import { migrateAppState, getAppStateDefaults } from '../../lib';
import { createDashboardAppState } from '../create_dashboard_app_state';
import { SavedObjectDashboard } from '../../../saved_dashboards';

/**
* This effect is responsible for instantiating the dashboard app state container,
Expand All @@ -22,7 +23,7 @@ import { createDashboardAppState } from '../create_dashboard_app_state';
export const useDashboardAppState = (
services: DashboardServices,
eventEmitter: EventEmitter,
instance: any
instance?: SavedObjectDashboard
) => {
const [appState, setAppState] = useState<DashboardAppStateContainer | undefined>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import deepEqual from 'fast-deep-equal';
import { EventEmitter } from 'stream';
import { useEffect } from 'react';
import { i18n } from '@osd/i18n';
import _ from 'lodash';
import { IndexPattern, opensearchFilters } from '../../../../../data/public';
import {
DASHBOARD_CONTAINER_TYPE,
Expand Down Expand Up @@ -49,24 +50,28 @@ import {
import { migrateLegacyQuery } from '../../lib/migrate_legacy_query';
import { getSavedObjectFinder } from '../../../../../saved_objects/public';
import { DashboardConstants } from '../../../dashboard_constants';
import { SavedObjectDashboard } from '../../../saved_dashboards';
import { Dashboard } from '../../../dashboard';

export const useDashboardContainer = (
services: DashboardServices,
isChromeVisible: boolean,
eventEmitter: EventEmitter,
savedDashboardInstance?: any,
dashboard?: Dashboard,
savedDashboardInstance?: SavedObjectDashboard,
appState?: DashboardAppStateContainer
) => {
const [dashboardContainer, setDashboardContainer] = useState<DashboardContainer>();

useEffect(() => {
const getDashboardContainer = async () => {
try {
if (savedDashboardInstance && appState) {
if (savedDashboardInstance && appState && dashboard) {
const dashboardContainerEmbeddable = await createDashboardEmbeddable(
savedDashboardInstance,
services,
appState
appState,
dashboard
);

setDashboardContainer(dashboardContainerEmbeddable);
Expand All @@ -82,7 +87,7 @@ export const useDashboardContainer = (
};

getDashboardContainer();
}, [savedDashboardInstance, appState, services]);
}, [savedDashboardInstance, appState, services, dashboard]);

useEffect(() => {
const incomingEmbeddable = services.embeddable
Expand All @@ -106,7 +111,8 @@ export const useDashboardContainer = (
const createDashboardEmbeddable = (
savedDash: any,
dashboardServices: DashboardServices,
appState: DashboardAppStateContainer
appState: DashboardAppStateContainer,
dashboard: Dashboard
) => {
let dashboardContainer: DashboardContainer;
let inputSubscription: Subscription | undefined;
Expand Down Expand Up @@ -342,7 +348,7 @@ const createDashboardEmbeddable = (
appState.transitions.set('query', queryStringManager.getQuery());
}
// triggered when dashboard embeddable container has changes, and update the appState
handleDashboardContainerChanges(container, appState, dashboardServices);
handleDashboardContainerChanges(container, appState, dashboardServices, dashboard);
});
return dashboardContainer;
}
Expand All @@ -354,7 +360,8 @@ const createDashboardEmbeddable = (
const handleDashboardContainerChanges = (
dashboardContainer: DashboardContainer,
appState: DashboardAppStateContainer,
dashboardServices: DashboardServices
dashboardServices: DashboardServices,
dashboard: Dashboard
) => {
let dirty = false;
let dirtyBecauseOfInitialStateMigration = false;
Expand All @@ -370,6 +377,7 @@ const handleDashboardContainerChanges = (
dirty = true;
}
});

const convertedPanelStateMap: { [key: string]: SavedDashboardPanel } = {};
Object.values(input.panels).forEach((panelState) => {
if (savedDashboardPanelMap[panelState.explicitInput.id] === undefined) {
Expand All @@ -396,8 +404,8 @@ const handleDashboardContainerChanges = (
});
if (dirty) {
appState.transitions.set('panels', Object.values(convertedPanelStateMap));
if (dirtyBecauseOfInitialStateMigration) {
// this.saveState({ replace: true });
if (!dirtyBecauseOfInitialStateMigration) {
appState.transitions.set('isDirty', true);
}
}
if (input.isFullScreenMode !== appStateData.fullScreenMode) {
Expand Down
Loading

0 comments on commit 7d005a1

Please sign in to comment.