diff --git a/src/plugins/saved_objects_management/README.md b/src/plugins/saved_objects_management/README.md index 9fc21786c66..748ce5b8992 100644 --- a/src/plugins/saved_objects_management/README.md +++ b/src/plugins/saved_objects_management/README.md @@ -7,7 +7,7 @@ From the primary UI page, this plugin allows you to: 2. Import/export saved objects 3. Inspect/edit raw saved object values without validation -For 3., this plugin can also be used to provide a route/page for editing, such as `/app/management/opensearch-dashboards/objects/savedVisualizations/{visualizationId}`, although plugins are alos free to provide or host alternate routes for this purpose (see index patterns, for instance, which provide their own integration and UI via the `management` plugin directly). +For 3., this plugin can also be used to provide a route/page for editing, such as `/app/management/opensearch-dashboards/objects/savedVisualizations/{visualizationId}`, although plugins are also free to provide or host alternate routes for this purpose (see index patterns, for instance, which provide their own integration and UI via the `management` plugin directly). ## Making a new saved object type manageable 1. Create a new `SavedObjectsType` or add the `management` property to an existing one. (See `SavedObjectsTypeManagementDefinition` for explanation of its properties: https://github.com/opensearch-project/OpenSearch-Dashboards/blob/e1380f14deb98cc7cce55c3b82c2d501826a78c3/src/core/server/saved_objects/types.ts#L247-L285) diff --git a/src/plugins/wizard/common/index.ts b/src/plugins/wizard/common/index.ts index f2ceda0f2af..b55653d4154 100644 --- a/src/plugins/wizard/common/index.ts +++ b/src/plugins/wizard/common/index.ts @@ -6,5 +6,6 @@ export const PLUGIN_ID = 'wizard'; export const PLUGIN_NAME = 'Wizard'; export const VISUALIZE_ID = 'visualize'; +export const EDIT_PATH = '/edit'; export { WizardSavedObjectAttributes, WIZARD_SAVED_OBJECT } from './wizard_saved_object_attributes'; diff --git a/src/plugins/wizard/common/wizard_saved_object_attributes.ts b/src/plugins/wizard/common/wizard_saved_object_attributes.ts index ff6c12417d2..9fd67783347 100644 --- a/src/plugins/wizard/common/wizard_saved_object_attributes.ts +++ b/src/plugins/wizard/common/wizard_saved_object_attributes.ts @@ -3,12 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { SavedObjectAttributes } from 'opensearch-dashboards/public'; +import { integer } from '@opensearch-project/opensearch/api/types'; +import { SavedObjectAttributes } from '../../../core/types'; export const WIZARD_SAVED_OBJECT = 'wizard'; export interface WizardSavedObjectAttributes extends SavedObjectAttributes { title: string; description?: string; - state: string; + visualizationState?: string; + styleState?: string; + version: integer; } diff --git a/src/plugins/wizard/public/application/components/top_nav.tsx b/src/plugins/wizard/public/application/components/top_nav.tsx index e2bdc2b33a2..299425e5b1d 100644 --- a/src/plugins/wizard/public/application/components/top_nav.tsx +++ b/src/plugins/wizard/public/application/components/top_nav.tsx @@ -3,47 +3,54 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { i18n } from '@osd/i18n'; -import React, { useMemo, useEffect } from 'react'; -import { PLUGIN_ID, VISUALIZE_ID } from '../../../common'; +import React, { useMemo } from 'react'; +import { useParams } from 'react-router-dom'; +import { PLUGIN_ID } from '../../../common'; import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; -import { getTopNavconfig } from '../utils/get_top_nav_config'; +import { getTopNavConfig } from '../utils/get_top_nav_config'; import { WizardServices } from '../../types'; import './top_nav.scss'; import { useIndexPattern } from '../utils/use'; +import { useTypedSelector } from '../utils/state_management'; +import { useSavedWizardVis } from '../utils/use/use_saved_wizard_vis'; export const TopNav = () => { + // id will only be set for the edit route + const { id: visualizationIdFromUrl } = useParams<{ id: string }>(); const { services } = useOpenSearchDashboards(); const { setHeaderActionMenu, - chrome, navigation: { ui: { TopNavMenu }, }, } = services; + const rootState = useTypedSelector((state) => state); + const hasUnappliedChanges = useTypedSelector( + (state) => !!state.visualization.activeVisualization?.draftAgg + ); - const config = useMemo(() => getTopNavconfig(services), [services]); - const indexPattern = useIndexPattern(); + const savedWizardVis = useSavedWizardVis(services, visualizationIdFromUrl); - useEffect(() => { - const visualizeHref = window.location.href.split(`${PLUGIN_ID}#/`)[0] + `${VISUALIZE_ID}#/`; - chrome.setBreadcrumbs([ - { - text: i18n.translate('visualize.listing.breadcrumb', { - defaultMessage: 'Visualize', - }), - href: visualizeHref, - }, + const config = useMemo(() => { + if (savedWizardVis === undefined) { + return; + } + const { visualization: visualizationState, style: styleState } = rootState; + + return getTopNavConfig( { - text: i18n.translate('wizard.nav.breadcrumb.create', { - defaultMessage: 'Create', - }), + visualizationIdFromUrl, + savedWizardVis, + visualizationState, + styleState, + hasUnappliedChanges, }, - ]); - // we want to run this hook exactly once, which you do by an empty dep array - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + services + ); + }, [hasUnappliedChanges, rootState, savedWizardVis, services, visualizationIdFromUrl]); + + const indexPattern = useIndexPattern(); return (
diff --git a/src/plugins/wizard/public/application/index.tsx b/src/plugins/wizard/public/application/index.tsx index c451d082b15..28ee13a80bf 100644 --- a/src/plugins/wizard/public/application/index.tsx +++ b/src/plugins/wizard/public/application/index.tsx @@ -5,25 +5,30 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { BrowserRouter as Router } from 'react-router-dom'; +import { Router, Route, Switch } from 'react-router-dom'; import { Provider as ReduxProvider } from 'react-redux'; import { Store } from 'redux'; import { AppMountParameters } from '../../../../core/public'; import { WizardServices } from '../types'; import { WizardApp } from './app'; import { OpenSearchDashboardsContextProvider } from '../../../opensearch_dashboards_react/public'; +import { EDIT_PATH } from '../../common'; export const renderApp = ( - { appBasePath, element }: AppMountParameters, + { element, history }: AppMountParameters, services: WizardServices, store: Store ) => { ReactDOM.render( - + - + + + + + diff --git a/src/plugins/wizard/public/application/utils/breadcrumbs.ts b/src/plugins/wizard/public/application/utils/breadcrumbs.ts new file mode 100644 index 00000000000..7bdb41075f3 --- /dev/null +++ b/src/plugins/wizard/public/application/utils/breadcrumbs.ts @@ -0,0 +1,42 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import { VISUALIZE_ID } from '../../../common'; + +const defaultEditText = i18n.translate('wizard.editor.defaultEditBreadcrumbText', { + defaultMessage: 'Edit', +}); + +export function getVisualizeLandingBreadcrumbs(navigateToApp) { + return [ + { + text: i18n.translate('wizard.listing.breadcrumb', { + defaultMessage: 'Visualize', + }), + onClick: () => navigateToApp(VISUALIZE_ID), + }, + ]; +} + +export function getCreateBreadcrumbs(navigateToApp) { + return [ + ...getVisualizeLandingBreadcrumbs(navigateToApp), + { + text: i18n.translate('wizard.editor.createBreadcrumb', { + defaultMessage: 'Create', + }), + }, + ]; +} + +export function getEditBreadcrumbs(text: string = defaultEditText, navigateToApp) { + return [ + ...getVisualizeLandingBreadcrumbs(navigateToApp), + { + text, + }, + ]; +} diff --git a/src/plugins/wizard/public/application/utils/get_saved_wizard_vis.ts b/src/plugins/wizard/public/application/utils/get_saved_wizard_vis.ts new file mode 100644 index 00000000000..f56169114f6 --- /dev/null +++ b/src/plugins/wizard/public/application/utils/get_saved_wizard_vis.ts @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { WizardServices } from '../..'; + +export const getSavedWizardVis = async (services: WizardServices, wizardVisId?: string) => { + const { savedWizardLoader } = services; + if (!savedWizardLoader) { + return {}; + } + const savedWizardVis = await savedWizardLoader.get(wizardVisId); + + return savedWizardVis; +}; diff --git a/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx index 725f7f2baa9..67051318784 100644 --- a/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx @@ -37,22 +37,50 @@ import { showSaveModal, } from '../../../../saved_objects/public'; import { WizardServices } from '../..'; +import { WizardVisSavedObject } from '../../types'; +import { StyleState, VisualizationState } from './state_management'; +import { EDIT_PATH } from '../../../common'; +interface TopNavConfigParams { + visualizationIdFromUrl: string; + savedWizardVis: WizardVisSavedObject; + visualizationState: VisualizationState; + styleState: StyleState; + hasUnappliedChanges: boolean; +} -export const getTopNavconfig = ({ - savedObjects: { client: savedObjectsClient }, - toastNotifications, - i18n: { Context: I18nContext }, -}: WizardServices) => { +export const getTopNavConfig = ( + { + visualizationIdFromUrl, + savedWizardVis, + visualizationState, + styleState, + hasUnappliedChanges, + }: TopNavConfigParams, + { history, toastNotifications, i18n: { Context: I18nContext } }: WizardServices +) => { const topNavConfig: TopNavMenuData[] = [ { id: 'save', iconType: 'save', - emphasize: true, - label: 'Save', + emphasize: savedWizardVis && !savedWizardVis.id, + description: i18n.translate('wizard.topNavMenu.saveVisualizationButtonAriaLabel', { + defaultMessage: 'Save Visualization', + }), + className: 'saveButton', + label: i18n.translate('wizard.topNavMenu.saveVisualizationButtonLabel', { + defaultMessage: 'save', + }), testId: 'wizardSaveButton', - run: (anchorElement) => { + disableButton: hasUnappliedChanges, + tooltip() { + if (hasUnappliedChanges) { + return i18n.translate('wizard.topNavMenu.saveVisualizationDisabledButtonTooltip', { + defaultMessage: 'Apply aggregation configuration changes before saving', // TODO: Update text to match agg save flow + }); + } + }, + run: (_anchorElement) => { const onSave = async ({ - // TODO: Figure out what the other props here do newTitle, newCopyOnSave, isTitleDuplicateConfirmed, @@ -60,15 +88,22 @@ export const getTopNavconfig = ({ newDescription, returnToOrigin, }: OnSaveProps & { returnToOrigin: boolean }) => { - // TODO: Save the actual state of the wizard - const wizardSavedObject = await savedObjectsClient.create('wizard', { - title: newTitle, - description: newDescription, - state: JSON.stringify({}), - }); + if (!savedWizardVis) { + return; + } + savedWizardVis.visualizationState = JSON.stringify(visualizationState); + savedWizardVis.styleState = JSON.stringify(styleState); + savedWizardVis.title = newTitle; + savedWizardVis.description = newDescription; + savedWizardVis.copyOnSave = newCopyOnSave; try { - const id = await wizardSavedObject.save(); + const id = await savedWizardVis.save({ + confirmOverwrite: false, + isTitleDuplicateConfirmed, + onTitleDuplicate, + returnToOrigin, + }); if (id) { toastNotifications.addSuccess({ @@ -77,13 +112,21 @@ export const getTopNavconfig = ({ { defaultMessage: `Saved '{visTitle}'`, values: { - visTitle: newTitle, + visTitle: savedWizardVis.title, }, } ), 'data-test-subj': 'saveVisualizationSuccess', }); + // Update URL + if (id !== visualizationIdFromUrl) { + history.push({ + ...history.location, + pathname: `${EDIT_PATH}/${id}`, + }); + } + return { id }; } @@ -93,15 +136,12 @@ export const getTopNavconfig = ({ console.error(error); toastNotifications.addDanger({ - title: i18n.translate( - 'visualize.topNavMenu.saveVisualization.failureNotificationText', - { - defaultMessage: `Error on saving '{visTitle}'`, - values: { - visTitle: newTitle, - }, - } - ), + title: i18n.translate('wizard.topNavMenu.saveVisualization.failureNotificationText', { + defaultMessage: `Error on saving '{visTitle}'`, + values: { + visTitle: newTitle, + }, + }), text: error.message, 'data-test-subj': 'saveVisualizationError', }); @@ -111,9 +151,9 @@ export const getTopNavconfig = ({ const saveModal = ( {}} /> ); diff --git a/src/plugins/wizard/public/application/utils/state_management/store.ts b/src/plugins/wizard/public/application/utils/state_management/store.ts index 29af0e9d73b..d8fff094cd1 100644 --- a/src/plugins/wizard/public/application/utils/state_management/store.ts +++ b/src/plugins/wizard/public/application/utils/state_management/store.ts @@ -30,3 +30,6 @@ export const getPreloadedStore = async (services: WizardServices) => { export type RootState = ReturnType; type Store = ReturnType; export type AppDispatch = Store['dispatch']; + +export { setState as setStyleState, StyleState } from './style_slice'; +export { setState as setVisualizationState, VisualizationState } from './visualization_slice'; diff --git a/src/plugins/wizard/public/application/utils/state_management/style_slice.ts b/src/plugins/wizard/public/application/utils/state_management/style_slice.ts index 98a425184d4..55579c759cc 100644 --- a/src/plugins/wizard/public/application/utils/state_management/style_slice.ts +++ b/src/plugins/wizard/public/application/utils/state_management/style_slice.ts @@ -6,7 +6,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { WizardServices } from '../../../types'; -type StyleState = T; +export type StyleState = T; const initialState = {} as StyleState; diff --git a/src/plugins/wizard/public/application/utils/state_management/visualization_slice.ts b/src/plugins/wizard/public/application/utils/state_management/visualization_slice.ts index f7d4d3ff6d4..06c865e1439 100644 --- a/src/plugins/wizard/public/application/utils/state_management/visualization_slice.ts +++ b/src/plugins/wizard/public/application/utils/state_management/visualization_slice.ts @@ -7,7 +7,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { CreateAggConfigParams } from '../../../../../data/common'; import { WizardServices } from '../../../types'; -interface VisualizationState { +export interface VisualizationState { indexPattern?: string; searchField: string; activeVisualization?: { @@ -105,6 +105,9 @@ export const slice = createSlice({ updateAggConfigParams: (state, action: PayloadAction) => { state.activeVisualization!.aggConfigParams = action.payload; }, + setState: (_state, action: PayloadAction) => { + return action.payload; + }, }, }); @@ -117,4 +120,5 @@ export const { updateAggConfigParams, saveAgg, reorderAgg, + setState, } = slice.actions; diff --git a/src/plugins/wizard/public/application/utils/use/use_saved_wizard_vis.ts b/src/plugins/wizard/public/application/utils/use/use_saved_wizard_vis.ts new file mode 100644 index 00000000000..30b72199f10 --- /dev/null +++ b/src/plugins/wizard/public/application/utils/use/use_saved_wizard_vis.ts @@ -0,0 +1,90 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import { useEffect, useState } from 'react'; +import { SavedObject } from '../../../../../saved_objects/public'; +import { + redirectWhenMissing, + SavedObjectNotFound, +} from '../../../../../opensearch_dashboards_utils/public'; +import { EDIT_PATH, PLUGIN_ID } from '../../../../common'; +import { WizardServices } from '../../../types'; +import { MetricOptionsDefaults } from '../../../visualizations/metric/metric_viz_type'; +import { getCreateBreadcrumbs, getEditBreadcrumbs } from '../breadcrumbs'; +import { getSavedWizardVis } from '../get_saved_wizard_vis'; +import { useTypedDispatch, setStyleState, setVisualizationState } from '../state_management'; + +export const useSavedWizardVis = ( + services: WizardServices, + visualizationIdFromUrl: string | undefined +) => { + const [savedVisState, setSavedVisState] = useState(undefined); + const dispatch = useTypedDispatch(); + + useEffect(() => { + const { + application: { navigateToApp }, + chrome, + history, + http: { basePath }, + toastNotifications, + } = services; + const loadSavedWizardVis = async () => { + try { + const savedWizardVis = await getSavedWizardVis(services, visualizationIdFromUrl); + + if (savedWizardVis.id) { + chrome.setBreadcrumbs(getEditBreadcrumbs(savedWizardVis.title, navigateToApp)); + chrome.docTitle.change(savedWizardVis.title); + } else { + chrome.setBreadcrumbs(getCreateBreadcrumbs(navigateToApp)); + } + + if (savedWizardVis.styleState !== '{}' && savedWizardVis.visualizationState !== '{}') { + const styleState = JSON.parse(savedWizardVis.styleState); + const visualizationState = JSON.parse(savedWizardVis.visualizationState); + // TODO: Add validation and transformation, throw/handle errors + dispatch(setStyleState(styleState)); + dispatch(setVisualizationState(visualizationState)); + } + + setSavedVisState(savedWizardVis); + } catch (error) { + const managementRedirectTarget = { + [PLUGIN_ID]: { + app: 'management', + path: `opensearch-dashboards/objects/savedWizard/${visualizationIdFromUrl}`, + }, + }; + + try { + if (error instanceof SavedObjectNotFound) { + redirectWhenMissing({ + history, + navigateToApp, + toastNotifications, + basePath, + mapping: managementRedirectTarget, + })(error); + } + } catch (e) { + const message = e instanceof Error ? e.message : ''; + toastNotifications.addWarning({ + title: i18n.translate('visualize.createVisualization.failedToLoadErrorMessage', { + defaultMessage: 'Failed to load the visualization', + }), + text: message, + }); + history.replace(EDIT_PATH); + } + } + }; + + loadSavedWizardVis(); + }, [dispatch, services, visualizationIdFromUrl]); + + return savedVisState; +}; diff --git a/src/plugins/wizard/public/plugin.test.ts b/src/plugins/wizard/public/plugin.test.ts index c83f4c6a845..74936288a76 100644 --- a/src/plugins/wizard/public/plugin.test.ts +++ b/src/plugins/wizard/public/plugin.test.ts @@ -9,11 +9,12 @@ import { dataPluginMock } from '../../../plugins/data/public/mocks'; import { embeddablePluginMock } from '../../../plugins/embeddable/public/mocks'; import { navigationPluginMock } from '../../../plugins/navigation/public/mocks'; import { visualizationsPluginMock } from '../../../plugins/visualizations/public/mocks'; +import { PLUGIN_ID, PLUGIN_NAME } from '../common'; import { WizardPlugin } from './plugin'; describe('WizardPlugin', () => { describe('setup', () => { - it('initializes the plugin correctly and registers it as an alias vizualization', () => { + it('initializes the plugin correctly and registers it as an alias visualization', () => { const plugin = new WizardPlugin(coreMock.createPluginInitializerContext()); const pluginStartContract = { data: dataPluginMock.createStartContract(), @@ -34,8 +35,8 @@ describe('WizardPlugin', () => { expect(setupDeps.visualizations.registerAlias).toHaveBeenCalledWith( // TODO: Update this once the properties are final expect.objectContaining({ - name: 'wizard', - title: 'Wizard', + name: PLUGIN_ID, + title: PLUGIN_NAME, aliasPath: '#/', }) ); diff --git a/src/plugins/wizard/public/plugin.ts b/src/plugins/wizard/public/plugin.ts index 6bd8a3d970d..7af37ccf61b 100644 --- a/src/plugins/wizard/public/plugin.ts +++ b/src/plugins/wizard/public/plugin.ts @@ -20,7 +20,7 @@ import { WizardStart, } from './types'; import wizardIcon from './assets/wizard_icon.svg'; -import { PLUGIN_NAME } from '../common'; +import { PLUGIN_ID, PLUGIN_NAME } from '../common'; import { TypeService } from './services/type_service'; import { getPreloadedStore } from './application/utils/state_management'; import { setAggService, setIndexPatterns } from './plugin_services'; @@ -35,7 +35,7 @@ export class WizardPlugin constructor(public initializerContext: PluginInitializerContext) {} public setup( - core: CoreSetup, + core: CoreSetup, { visualizations }: WizardPluginSetupDependencies ) { const typeService = this.typeService; @@ -43,7 +43,7 @@ export class WizardPlugin // Register the plugin to core core.application.register({ - id: 'wizard', + id: PLUGIN_ID, title: PLUGIN_NAME, navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { @@ -51,7 +51,7 @@ export class WizardPlugin const { renderApp } = await import('./application'); // Get start services as specified in opensearch_dashboards.json - const [coreStart, pluginsStart] = await core.getStartServices(); + const [coreStart, pluginsStart, selfStart] = await core.getStartServices(); const { data, savedObjects, navigation, expressions } = pluginsStart; // make sure the index pattern list is up to date @@ -74,8 +74,10 @@ export class WizardPlugin savedObjectsPublic: savedObjects, navigation, expressions, + history: params.history, setHeaderActionMenu: params.setHeaderActionMenu, types: typeService.start(), + savedWizardLoader: selfStart.savedWizardLoader, }; // Instantiate the store @@ -88,15 +90,15 @@ export class WizardPlugin // Register the plugin as an alias to create visualization visualizations.registerAlias({ - name: 'wizard', - title: 'Wizard', - description: i18n.translate('wizard.vizPicker.description', { + name: PLUGIN_ID, + title: PLUGIN_NAME, + description: i18n.translate('wizard.visPicker.description', { defaultMessage: 'TODO...', }), // TODO: Replace with actual icon once available icon: wizardIcon, stage: 'beta', - aliasApp: 'wizard', + aliasApp: PLUGIN_ID, aliasPath: '#/', }); diff --git a/src/plugins/wizard/public/saved_visualizations/_saved_vis.ts b/src/plugins/wizard/public/saved_visualizations/_saved_vis.ts index 8ff823812f7..66a02a974dd 100644 --- a/src/plugins/wizard/public/saved_visualizations/_saved_vis.ts +++ b/src/plugins/wizard/public/saved_visualizations/_saved_vis.ts @@ -7,20 +7,21 @@ import { createSavedObjectClass, SavedObjectOpenSearchDashboardsServices, } from '../../../saved_objects/public'; +import { EDIT_PATH, PLUGIN_ID, WIZARD_SAVED_OBJECT } from '../../common'; export function createSavedWizardVisClass(services: SavedObjectOpenSearchDashboardsServices) { const SavedObjectClass = createSavedObjectClass(services); class SavedWizardVis extends SavedObjectClass { - public static type = 'wizard'; + public static type = WIZARD_SAVED_OBJECT; // if type:wizard has no mapping, we push this mapping into OpenSearch public static mapping = { title: 'text', description: 'text', - state: 'text', - // savedSearchId: 'keyword', - // version: 'integer', + visualizationState: 'text', + styleState: 'text', + version: 'integer', }; // Order these fields to the top, the rest are alphabetical @@ -39,13 +40,13 @@ export function createSavedWizardVisClass(services: SavedObjectOpenSearchDashboa defaults: { title: '', description: '', - state: '{}', - // savedSearchId, - // version: 1, + visualizationState: '{}', + styleState: '{}', + version: 1, }, }); this.showInRecentlyAccessed = true; - this.getFullPath = () => `/app/wizard#/edit/${this.id}`; + this.getFullPath = () => `/app/${PLUGIN_ID}${EDIT_PATH}/${this.id}`; } } diff --git a/src/plugins/wizard/public/types.ts b/src/plugins/wizard/public/types.ts index 8ebcea1e19b..55b3f1b4134 100644 --- a/src/plugins/wizard/public/types.ts +++ b/src/plugins/wizard/public/types.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { SavedObjectsStart } from '../../saved_objects/public'; +import { History } from 'history'; +import { SavedObject, SavedObjectsStart } from '../../saved_objects/public'; import { EmbeddableSetup } from '../../embeddable/public'; import { DashboardStart } from '../../dashboard/public'; import { VisualizationsSetup } from '../../visualizations/public'; @@ -33,10 +34,23 @@ export interface WizardPluginStartDependencies { export interface WizardServices extends CoreStart { setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; + savedWizardLoader: WizardStart['savedWizardLoader']; toastNotifications: ToastsStart; savedObjectsPublic: SavedObjectsStart; navigation: NavigationPublicPluginStart; data: DataPublicPluginStart; types: TypeServiceStart; expressions: ExpressionsStart; + history: History; } + +export interface ISavedVis { + id?: string; + title: string; + description?: string; + visualizationState?: string; + styleState?: string; + version?: number; +} + +export interface WizardVisSavedObject extends SavedObject, ISavedVis {} diff --git a/src/plugins/wizard/server/saved_objects/wizard_app.ts b/src/plugins/wizard/server/saved_objects/wizard_app.ts index 7060dfafe18..9f17dd502a8 100644 --- a/src/plugins/wizard/server/saved_objects/wizard_app.ts +++ b/src/plugins/wizard/server/saved_objects/wizard_app.ts @@ -4,7 +4,12 @@ */ import { SavedObject, SavedObjectsType } from '../../../../core/server'; -import { WizardSavedObjectAttributes, WIZARD_SAVED_OBJECT } from '../../common'; +import { + EDIT_PATH, + PLUGIN_ID, + WizardSavedObjectAttributes, + WIZARD_SAVED_OBJECT, +} from '../../common'; export const wizardSavedObjectType: SavedObjectsType = { name: WIZARD_SAVED_OBJECT, @@ -19,7 +24,7 @@ export const wizardSavedObjectType: SavedObjectsType = { `/management/opensearch-dashboards/objects/savedWizard/${encodeURIComponent(id)}`, getInAppUrl({ id }: SavedObject) { return { - path: `/app/wizard#/edit/${encodeURIComponent(id)}`, + path: `/app/${PLUGIN_ID}${EDIT_PATH}/${encodeURIComponent(id)}`, uiCapabilitiesPath: 'wizard.show', }; }, @@ -33,11 +38,15 @@ export const wizardSavedObjectType: SavedObjectsType = { description: { type: 'text', }, - // TODO: Determine what needs to be pulled out of state and added directly into the mapping - state: { + visualizationState: { type: 'text', index: false, }, + styleState: { + type: 'text', + index: false, + }, + version: { type: 'integer' }, }, }, };