From b7c049ad35aae4836e7cd99b6985bd836ddeee39 Mon Sep 17 00:00:00 2001 From: Josh Romero Date: Thu, 30 Jun 2022 15:44:21 -0700 Subject: [PATCH] [D&D] Enable basic saved object management (#1816) * [D&D] Enable basic saved object management - Create README stub for saved_objects_management plugin - Register wizard saved object loader with management plugin - Add management methods to SavedObjectsType - Add capabilities provider to wizard - Add saved wizard vis SavedObjectClass and SavedObjectLoader - Add public plugin start method partially addresses #1620 Signed-off-by: Josh Romero * [Doc] Add clarifications to README for save objects management plugin Signed-off-by: Josh Romero --- .../saved_objects_management/README.md | 41 ++++++++++++++ .../opensearch_dashboards.json | 2 +- .../saved_objects_management/public/plugin.ts | 3 ++ .../public/register_services.ts | 10 +++- src/plugins/wizard/public/index.ts | 2 +- src/plugins/wizard/public/plugin.ts | 19 ++++++- .../public/saved_visualizations/_saved_vis.ts | 53 +++++++++++++++++++ .../public/saved_visualizations/index.ts | 6 +++ .../saved_visualizations.ts | 18 +++++++ .../services/type_service/type_service.ts | 2 +- src/plugins/wizard/public/types.ts | 18 ++++--- .../wizard/server/capabilities_provider.ts | 17 ++++++ src/plugins/wizard/server/plugin.ts | 12 +++-- .../wizard/server/saved_objects/index.ts | 2 +- .../wizard/server/saved_objects/wizard_app.ts | 17 ++++-- 15 files changed, 199 insertions(+), 23 deletions(-) create mode 100644 src/plugins/saved_objects_management/README.md create mode 100644 src/plugins/wizard/public/saved_visualizations/_saved_vis.ts create mode 100644 src/plugins/wizard/public/saved_visualizations/index.ts create mode 100644 src/plugins/wizard/public/saved_visualizations/saved_visualizations.ts create mode 100644 src/plugins/wizard/server/capabilities_provider.ts diff --git a/src/plugins/saved_objects_management/README.md b/src/plugins/saved_objects_management/README.md new file mode 100644 index 00000000000..9fc21786c66 --- /dev/null +++ b/src/plugins/saved_objects_management/README.md @@ -0,0 +1,41 @@ +# Save objects management + +Provides a UI (via the `management` plugin) to find and manage all saved objects in one place (you can see the primary page by navigating to `/app/management/opensearch-dashboards/objects`). Not to be confused with the `savedObjects` plugin, which provides all the core capabilities of saved objects. + +From the primary UI page, this plugin allows you to: +1. Search/view/delete saved objects and their relationships +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). +## 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) +2. Register saved object type via `core.savedObjects.registerType(...)` as part of plugin server setup method +3. Implement a way to save the object via `savedObjectsClient.create(...)` +4. After these steps, you should be able to save objects and view/search for them in Saved Objects management (`/app/management/opensearch-dashboards/objects`) + +## Enabling edit links from saved objects management + +1. Make sure `management.getInAppUrl` method of the `SavedObjectsType` is defined with a `path` (which will specify the link target) and the `uiCapabilitiesPath` +2. For `uiCapabilitiesPath` to work without additional hardcoding, it should be in the format `{plugin}.show`, so that [the default logic of `src/plugins/saved_objects_management/public/lib/in_app_url.ts`](https://github.com/opensearch-project/OpenSearch-Dashboards/blob/a9984f63a38e964007ab94fae99237a14d8f9ee2/src/plugins/saved_objects_management/public/lib/in_app_url.ts#L48-L50) will correctly match. Otherwise, you'll need to [add a case for your `uiCapabilities` path](https://github.com/opensearch-project/OpenSearch-Dashboards/blob/a9984f63a38e964007ab94fae99237a14d8f9ee2/src/plugins/saved_objects_management/public/lib/in_app_url.ts#L45-L47) to that function +3. Create default plugin capabilities provider +3. Register plugin capabilities via `core.capabilities.registerProvider(...);` as part of plugin server setup method + +## Using saved objects management to inspect/edit new plugin objects + +You'll notice that when clicking on the "Inspect" button from the saved objects management table, you'll usually be routed to something like `/app/management/opensearch-dashboards/objects/savedVisualizations/` (where the route itself is determined by the `management.getEditUrl` method of the `SavedObjectsType`). But to register a similar route for a new saved object type, you'll need to create a new `savedObjectLoader` and register it with the management plugin. + +### Creating `savedObjectLoader` + +1. In your plugin's public directory, create a class for your saved object that extends `SavedObjectClass`. The mapping should match the `mappings` defined in your `SavedObjectsType`. +2. Create a `savedObjectLoader` creation function that returns a `new SavedObjectLoader(YourSavedObjectClass, savedObjectsClient)` +3. Return that `savedObjectLoader` as part of your public plugin `start` method + +### Registering + +Ideally, we'd allow plugins to self-register their `savedObjectLoader` and (declare a dependency on this plugin). However, as currently implemented, any plugins that want this plugin to handle their inpect routes need to be added as optional dependencies and registered here. + +1. Add your plugin to the `optionalPlugins` array in `./opensearch_dashboards.json` +2. Update the `StartDependencies` interface of this plugin to include the public plugin start type +3. Update `registerServices` to register a new type of `SavedObjectLoader`, where `id` will be the route, `title` will likely match your saved object type, and `service` is your `SavedObjectLoader` that is defined in your plugin start. diff --git a/src/plugins/saved_objects_management/opensearch_dashboards.json b/src/plugins/saved_objects_management/opensearch_dashboards.json index deca4778e34..21a0b00860b 100644 --- a/src/plugins/saved_objects_management/opensearch_dashboards.json +++ b/src/plugins/saved_objects_management/opensearch_dashboards.json @@ -4,7 +4,7 @@ "server": true, "ui": true, "requiredPlugins": ["management", "data"], - "optionalPlugins": ["dashboard", "visualizations", "discover", "home"], + "optionalPlugins": ["dashboard", "visualizations", "discover", "home", "wizard"], "extraPublicDirs": ["public/lib"], "requiredBundles": ["opensearchDashboardsReact", "home"] } diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts index fd9eebba901..74d03a15047 100644 --- a/src/plugins/saved_objects_management/public/plugin.ts +++ b/src/plugins/saved_objects_management/public/plugin.ts @@ -30,6 +30,8 @@ import { i18n } from '@osd/i18n'; import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; + +import { WizardStart } from '../../wizard/public'; import { ManagementSetup } from '../../management/public'; import { DataPublicPluginStart } from '../../data/public'; import { DashboardStart } from '../../dashboard/public'; @@ -69,6 +71,7 @@ export interface StartDependencies { dashboard?: DashboardStart; visualizations?: VisualizationsStart; discover?: DiscoverStart; + wizard?: WizardStart; } export class SavedObjectsManagementPlugin diff --git a/src/plugins/saved_objects_management/public/register_services.ts b/src/plugins/saved_objects_management/public/register_services.ts index 757876b8407..8ce6ed20bc9 100644 --- a/src/plugins/saved_objects_management/public/register_services.ts +++ b/src/plugins/saved_objects_management/public/register_services.ts @@ -36,7 +36,7 @@ export const registerServices = async ( registry: ISavedObjectsManagementServiceRegistry, getStartServices: StartServicesAccessor ) => { - const [, { dashboard, visualizations, discover }] = await getStartServices(); + const [, { dashboard, visualizations, discover, wizard }] = await getStartServices(); if (dashboard) { registry.register({ @@ -61,4 +61,12 @@ export const registerServices = async ( service: discover.savedSearchLoader, }); } + + if (wizard) { + registry.register({ + id: 'savedWizard', + title: 'wizard', + service: wizard.savedWizardLoader, + }); + } }; diff --git a/src/plugins/wizard/public/index.ts b/src/plugins/wizard/public/index.ts index 97f9007549a..713e9448b93 100644 --- a/src/plugins/wizard/public/index.ts +++ b/src/plugins/wizard/public/index.ts @@ -11,4 +11,4 @@ import { WizardPlugin } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new WizardPlugin(initializerContext); } -export { WizardServices, WizardPluginStartDependencies } from './types'; +export { WizardServices, WizardPluginStartDependencies, WizardStart } from './types'; diff --git a/src/plugins/wizard/public/plugin.ts b/src/plugins/wizard/public/plugin.ts index 67b6dea6dc7..d238603cbfa 100644 --- a/src/plugins/wizard/public/plugin.ts +++ b/src/plugins/wizard/public/plugin.ts @@ -17,15 +17,17 @@ import { WizardPluginStartDependencies, WizardServices, WizardSetup, + WizardStart, } from './types'; import { PLUGIN_NAME } from '../common'; import { TypeService } from './services/type_service'; import { getPreloadedStore } from './application/utils/state_management'; import { setAggService, setIndexPatterns } from './plugin_services'; +import { createSavedWizardLoader } from './saved_visualizations'; export class WizardPlugin implements - Plugin { + Plugin { private typeService = new TypeService(); constructor(public initializerContext: PluginInitializerContext) {} @@ -101,7 +103,20 @@ export class WizardPlugin }; } - public start(core: CoreStart) {} + public start(core: CoreStart, { data }: WizardPluginStartDependencies): WizardStart { + const typeService = this.typeService; + + return { + ...typeService.start(), + savedWizardLoader: createSavedWizardLoader({ + savedObjectsClient: core.savedObjects.client, + indexPatterns: data.indexPatterns, + search: data.search, + chrome: core.chrome, + overlays: core.overlays, + }), + }; + } public stop() {} } diff --git a/src/plugins/wizard/public/saved_visualizations/_saved_vis.ts b/src/plugins/wizard/public/saved_visualizations/_saved_vis.ts new file mode 100644 index 00000000000..8ff823812f7 --- /dev/null +++ b/src/plugins/wizard/public/saved_visualizations/_saved_vis.ts @@ -0,0 +1,53 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + createSavedObjectClass, + SavedObjectOpenSearchDashboardsServices, +} from '../../../saved_objects/public'; + +export function createSavedWizardVisClass(services: SavedObjectOpenSearchDashboardsServices) { + const SavedObjectClass = createSavedObjectClass(services); + + class SavedWizardVis extends SavedObjectClass { + public static type = 'wizard'; + + // 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', + }; + + // Order these fields to the top, the rest are alphabetical + static fieldOrder = ['title', 'description']; + + // ID is optional, without it one will be generated on save. + constructor(id: string) { + super({ + type: SavedWizardVis.type, + mapping: SavedWizardVis.mapping, + + // 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: '', + description: '', + state: '{}', + // savedSearchId, + // version: 1, + }, + }); + this.showInRecentlyAccessed = true; + this.getFullPath = () => `/app/wizard#/edit/${this.id}`; + } + } + + return SavedWizardVis; +} diff --git a/src/plugins/wizard/public/saved_visualizations/index.ts b/src/plugins/wizard/public/saved_visualizations/index.ts new file mode 100644 index 00000000000..442d5107ea0 --- /dev/null +++ b/src/plugins/wizard/public/saved_visualizations/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './saved_visualizations'; diff --git a/src/plugins/wizard/public/saved_visualizations/saved_visualizations.ts b/src/plugins/wizard/public/saved_visualizations/saved_visualizations.ts new file mode 100644 index 00000000000..f07dfd94031 --- /dev/null +++ b/src/plugins/wizard/public/saved_visualizations/saved_visualizations.ts @@ -0,0 +1,18 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + SavedObjectLoader, + SavedObjectOpenSearchDashboardsServices, +} from '../../../saved_objects/public'; +import { createSavedWizardVisClass } from './_saved_vis'; + +export type SavedWizardLoader = ReturnType; +export function createSavedWizardLoader(services: SavedObjectOpenSearchDashboardsServices) { + const { savedObjectsClient } = services; + const SavedWizardVisClass = createSavedWizardVisClass(services); + + return new SavedObjectLoader(SavedWizardVisClass, savedObjectsClient); +} diff --git a/src/plugins/wizard/public/services/type_service/type_service.ts b/src/plugins/wizard/public/services/type_service/type_service.ts index 38c13ebcfb3..ddbd735fb9e 100644 --- a/src/plugins/wizard/public/services/type_service/type_service.ts +++ b/src/plugins/wizard/public/services/type_service/type_service.ts @@ -28,7 +28,7 @@ * under the License. */ -import { CoreService } from 'src/core/types'; +import { CoreService } from '../../../../../core/types'; import { VisualizationTypeOptions } from './types'; import { VisualizationType } from './visualization_type'; diff --git a/src/plugins/wizard/public/types.ts b/src/plugins/wizard/public/types.ts index 529ebc58a60..8ebcea1e19b 100644 --- a/src/plugins/wizard/public/types.ts +++ b/src/plugins/wizard/public/types.ts @@ -3,17 +3,21 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { SavedObjectsStart } from 'src/plugins/saved_objects/public'; -import { AppMountParameters, CoreStart, ToastsStart } from 'opensearch-dashboards/public'; -import { EmbeddableSetup } from 'src/plugins/embeddable/public'; -import { DashboardStart } from 'src/plugins/dashboard/public'; -import { VisualizationsSetup } from 'src/plugins/visualizations/public'; -import { ExpressionsStart } from 'src/plugins/expressions/public'; +import { SavedObjectsStart } from '../../saved_objects/public'; +import { EmbeddableSetup } from '../../embeddable/public'; +import { DashboardStart } from '../../dashboard/public'; +import { VisualizationsSetup } from '../../visualizations/public'; +import { ExpressionsStart } from '../../expressions/public'; import { NavigationPublicPluginStart } from '../../navigation/public'; -import { DataPublicPluginStart, IndexPatternField } from '../../data/public'; +import { DataPublicPluginStart } from '../../data/public'; import { TypeServiceSetup, TypeServiceStart } from './services/type_service'; +import { SavedObjectLoader } from '../../saved_objects/public'; +import { AppMountParameters, CoreStart, ToastsStart } from '../../../core/public'; export type WizardSetup = TypeServiceSetup; +export interface WizardStart extends TypeServiceStart { + savedWizardLoader: SavedObjectLoader; +} export interface WizardPluginSetupDependencies { embeddable: EmbeddableSetup; diff --git a/src/plugins/wizard/server/capabilities_provider.ts b/src/plugins/wizard/server/capabilities_provider.ts new file mode 100644 index 00000000000..9bbede2d53a --- /dev/null +++ b/src/plugins/wizard/server/capabilities_provider.ts @@ -0,0 +1,17 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const capabilitiesProvider = () => ({ + wizard: { + // TODO: investigate which capabilities we need to provide + // createNew: true, + // createShortUrl: true, + // delete: true, + show: true, + // showWriteControls: true, + // save: true, + // saveQuery: true, + }, +}); diff --git a/src/plugins/wizard/server/plugin.ts b/src/plugins/wizard/server/plugin.ts index d45e4081cce..26781a236f8 100644 --- a/src/plugins/wizard/server/plugin.ts +++ b/src/plugins/wizard/server/plugin.ts @@ -12,8 +12,9 @@ import { } from '../../../core/server'; import { WizardPluginSetup, WizardPluginStart } from './types'; +import { capabilitiesProvider } from './capabilities_provider'; import { defineRoutes } from './routes'; -import { wizardApp } from './saved_objects'; +import { wizardSavedObjectType } from './saved_objects'; export class WizardPlugin implements Plugin { private readonly logger: Logger; @@ -22,7 +23,7 @@ export class WizardPlugin implements Plugin obj.attributes.title, - // getInAppUrl: TODO: Enable once editing is supported + getTitle: ({ attributes: { title } }: SavedObject) => title, + getEditUrl: ({ id }: SavedObject) => + `/management/opensearch-dashboards/objects/savedWizard/${encodeURIComponent(id)}`, + getInAppUrl({ id }: SavedObject) { + return { + path: `/app/wizard#/edit/${encodeURIComponent(id)}`, + uiCapabilitiesPath: 'wizard.show', + }; + }, }, migrations: {}, mappings: {