Skip to content

Commit

Permalink
Add visualization
Browse files Browse the repository at this point in the history
Add and save visualization to dashboard

Signed-off-by: abbyhu2000 <[email protected]>
  • Loading branch information
abbyhu2000 committed Jun 8, 2023
1 parent f34c404 commit 54e3370
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 31 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"dashboarding"
],
"private": true,
"version": "3.0.0",
"version": "2.8.0",
"branch": "main",
"types": "./opensearch_dashboards.d.ts",
"tsdocMetadata": "./build/tsdoc-metadata.json",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
* SPDX-License-Identifier: Apache-2.0
*/

import _, { uniqBy } from 'lodash';
import React, { memo, useState, useEffect } from 'react';
import { Filter } from 'src/plugins/data/public';
import { Filter, IndexPattern } 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';
import { isErrorEmbeddable } from '../../embeddable_plugin';

interface DashboardTopNavProps {
isChromeVisible: boolean;
Expand Down Expand Up @@ -45,12 +46,19 @@ const TopNav = ({
const [filters, setFilters] = useState<Filter[]>([]);
const [topNavMenu, setTopNavMenu] = useState<any>();
const [isFullScreenMode, setIsFullScreenMode] = useState<any>();
const [indexPatterns, setIndexPatterns] = useState<IndexPattern[]>();

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

const handleRefresh = useCallback((_payload: any, isUpdate?: boolean) => {
if (isUpdate === false && dashboardContainer) {
dashboardContainer.reload()
}
}, [dashboardContainer]);

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

Expand Down Expand Up @@ -97,6 +105,33 @@ const TopNav = ({
setIsFullScreenMode(currentAppState?.fullScreenMode);
}, [currentAppState, services]);

useEffect(() => {
const asyncSetIndexPattern = async () => {
if(dashboardContainer){
let panelIndexPatterns: IndexPattern[] = [];
Object.values(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()
}, [currentAppState, services.data.indexPatterns])

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

Expand All @@ -111,19 +146,7 @@ const TopNav = ({
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("index pattern", indexPatterns)
return isChromeVisible ? (
<TopNavMenu
appName={'dashboard'}
Expand All @@ -138,7 +161,7 @@ const TopNav = ({
showDatePicker={showDatePicker}
showFilterBar={showFilterBar}
useDefaultBehaviors={true}
indexPatterns={[]}
indexPatterns={indexPatterns}
showSaveQuery={services.dashboardCapabilities.saveQuery as boolean}
savedQuery={undefined}
onSavedQueryIdChange={() => {}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
public readonly type = DASHBOARD_CONTAINER_TYPE;

public renderEmpty?: undefined | (() => React.ReactNode);
public getChangesFromAppStateForContainerState?: (containerInput:any) => any;

private embeddablePanel: EmbeddableStart['EmbeddablePanel'];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const getNavActions = (
navActions[TopNavIds.EXIT_EDIT_MODE] = () => onChangeViewMode(ViewMode.VIEW);
navActions[TopNavIds.ENTER_EDIT_MODE] = () => onChangeViewMode(ViewMode.EDIT);
navActions[TopNavIds.SAVE] = () => {
console.log('inside save top nav!');
console.log("inside save top nav!")
const currentTitle = appState.title;
const currentDescription = appState.description;
const currentTimeRestore = appState.timeRestore;
Expand Down Expand Up @@ -362,12 +362,10 @@ export const getNavActions = (
revertChangesAndExitEditMode();
}
});

// updateNavBar();
}

async function save(saveOptions: SavedObjectSaveOpts) {
console.log('in the save function!');
console.log("in the save function!")
const timefilter = queryService.timefilter.timefilter;
try {
const id = await saveDashboard(timefilter, stateContainer, savedDashboard, saveOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@
*/

import React, { useState } from 'react';
import { EMPTY, Subscription, merge } from 'rxjs';
import { catchError, distinctUntilChanged, map, mapTo, startWith, switchMap } from 'rxjs/operators';
import _, { uniqBy } from 'lodash';
import { EMPTY, Observable, Subscription, merge, of, pipe } from 'rxjs';
import {
catchError,
distinctUntilChanged,
filter,
map,
mapTo,
startWith,
switchMap,
} from 'rxjs/operators';
import deepEqual from 'fast-deep-equal';
import { EventEmitter } from 'stream';
import { useEffect } from 'react';
import { opensearchFilters } from '../../../../../data/public';
import { IndexPattern, opensearchFilters } from '../../../../../data/public';
import {
DASHBOARD_CONTAINER_TYPE,
DashboardContainer,
Expand All @@ -18,13 +27,18 @@ import {
} from '../../embeddable';
import {
ContainerOutput,
EmbeddableInput,
ErrorEmbeddable,
ViewMode,
isErrorEmbeddable,
} from '../../../embeddable_plugin';
import { convertSavedDashboardPanelToPanelState } from '../../lib/embeddable_saved_object_converters';
import {
convertPanelStateToSavedDashboardPanel,
convertSavedDashboardPanelToPanelState,
} from '../../lib/embeddable_saved_object_converters';
import { DashboardEmptyScreen, DashboardEmptyScreenProps } from '../../dashboard_empty_screen';
import { DashboardAppStateContainer, DashboardServices, SavedDashboardPanel } from '../../../types';
import { migrateLegacyQuery } from '../../lib/migrate_legacy_query';

export const useDashboardContainer = (
services: DashboardServices,
Expand Down Expand Up @@ -60,6 +74,24 @@ export const useDashboardContainer = (
getDashboardContainer();
}, [appState, dashboardDom, eventEmitter, isChromeVisible, savedDashboardInstance, services]);

useEffect(() => {
const incomingEmbeddable = services.embeddable
.getStateTransfer(services.scopedHistory())
.getIncomingEmbeddablePackage();

if (
incomingEmbeddable &&
dashboardContainer &&
(!incomingEmbeddable.embeddableId ||
!dashboardContainer.getInput().panels[incomingEmbeddable.embeddableId])
) {
dashboardContainer.addNewEmbeddable<EmbeddableInput>(
incomingEmbeddable.type,
incomingEmbeddable.input
);
}
}, [dashboardContainer, services.embeddable, services.scopedHistory]);

return { dashboardContainer };
};

Expand Down Expand Up @@ -142,6 +174,7 @@ const createDashboardEmbeddable = async (

const getDashboardInput = () => {
const appStateData = appState.getState();

const embeddablesMap: {
[key: string]: DashboardPanelState;
} = {};
Expand Down Expand Up @@ -190,6 +223,41 @@ const createDashboardEmbeddable = async (
) : null;
};

dashboardContainer.getChangesFromAppStateForContainerState = (container: any) => {
const appStateDashboardInput = getDashboardInput();
if (!dashboardContainer || isErrorEmbeddable(dashboardContainer)) {
return appStateDashboardInput;
}

const containerInput = container.getInput();
const differences: Partial<DashboardContainerInput> = {};

// Filters shouldn't be compared using regular isEqual
if (
!opensearchFilters.compareFilters(
containerInput.filters,
appStateDashboardInput.filters,
opensearchFilters.COMPARE_ALL_OPTIONS
)
) {
differences.filters = appStateDashboardInput.filters;
}

Object.keys(_.omit(containerInput, ['filters'])).forEach((key) => {
const containerValue = (containerInput as { [key: string]: unknown })[key];
const appStateValue = ((appStateDashboardInput as unknown) as {
[key: string]: unknown;
})[key];
if (!_.isEqual(containerValue, appStateValue)) {
(differences as { [key: string]: unknown })[key] = appStateValue;
}
});

// cloneDeep hack is needed, as there are multiple place, where container's input mutated,
// but values from appStateValue are deeply frozen, as they can't be mutated directly
return Object.values(differences).length === 0 ? undefined : _.cloneDeep(differences);
};

// TODO: handle dashboard container input and output subsciptions
// issue:
outputSubscription = merge(
Expand All @@ -215,11 +283,10 @@ const createDashboardEmbeddable = async (
.pipe(
mapTo(dashboardContainer),
startWith(dashboardContainer) // to trigger initial index pattern update
// updateIndexPatternsOperator //TODO
)
.subscribe();

inputSubscription = dashboardContainer.getInput$().subscribe((foo) => {
inputSubscription = dashboardContainer.getInput$().subscribe(() => {
// This has to be first because handleDashboardContainerChanges causes
// appState.save which will cause refreshDashboardContainer to be called.

Expand All @@ -236,16 +303,76 @@ const createDashboardEmbeddable = async (
/* dashboardStateManager.applyFilters(
$scope.model.query,
container.getInput().filters
);*/
);*/

appState.transitions.set('query', queryStringManager.getQuery());
}
// TODO: triggered when dashboard embeddable container has changes, and update the appState
// handleDashboardContainerChanges(container, appState, dashboardServices);
// triggered when dashboard embeddable container has changes, and update the appState
handleDashboardContainerChanges(container, appState, dashboardServices);
});
return dashboardContainer;
}
});
}
return undefined;
};

const handleDashboardContainerChanges = (
dashboardContainer: DashboardContainer,
appState: DashboardAppStateContainer,
dashboardServices: DashboardServices
) => {
let dirty = false;
let dirtyBecauseOfInitialStateMigration = false;
const appStateData = appState.getState();
const savedDashboardPanelMap: { [key: string]: SavedDashboardPanel } = {};
const { opensearchDashboardsVersion } = dashboardServices;
const input = dashboardContainer.getInput();
appStateData.panels.forEach((savedDashboardPanel) => {
if (input.panels[savedDashboardPanel.panelIndex] !== undefined) {
savedDashboardPanelMap[savedDashboardPanel.panelIndex] = savedDashboardPanel;
} else {
// A panel was deleted.
dirty = true;
}
});
const convertedPanelStateMap: { [key: string]: SavedDashboardPanel } = {};
Object.values(input.panels).forEach((panelState) => {
if (savedDashboardPanelMap[panelState.explicitInput.id] === undefined) {
dirty = true;
}
convertedPanelStateMap[panelState.explicitInput.id] = convertPanelStateToSavedDashboardPanel(
panelState,
opensearchDashboardsVersion
);
if (
!_.isEqual(
convertedPanelStateMap[panelState.explicitInput.id],
savedDashboardPanelMap[panelState.explicitInput.id]
)
) {
// A panel was changed
dirty = true;
const oldVersion = savedDashboardPanelMap[panelState.explicitInput.id]?.version;
const newVersion = convertedPanelStateMap[panelState.explicitInput.id]?.version;
if (oldVersion && newVersion && oldVersion !== newVersion) {
dirtyBecauseOfInitialStateMigration = true;
}
}
});
if (dirty) {
appState.transitions.set('panels', Object.values(convertedPanelStateMap));
if (dirtyBecauseOfInitialStateMigration) {
// this.saveState({ replace: true });
}
}
if (input.isFullScreenMode !== appStateData.fullScreenMode) {
appState.transitions.set('fullScreenMode', input.isFullScreenMode);
}
if (input.expandedPanelId !== appStateData.expandedPanelId) {
appState.transitions.set('expandedPanelId', input.expandedPanelId);
}
if (!_.isEqual(input.query, migrateLegacyQuery(appState.get().query))) {
appState.transitions.set('query', input.query);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ export const useEditorUpdates = (

const unsubscribeStateUpdates = appState.subscribe((state) => {
setCurrentAppState(state);
dashboardContainer.reload();
if(dashboardContainer.getChangesFromAppStateForContainerState){
const changes = dashboardContainer.getChangesFromAppStateForContainerState(dashboardContainer);
if(changes){
dashboardContainer.updateInput(changes)
}
}
});

return () => {
Expand Down

0 comments on commit 54e3370

Please sign in to comment.