From ab0463cda37dc08faa4bfc5f4e2271dae777312e Mon Sep 17 00:00:00 2001 From: Josh Romero Date: Mon, 25 Jul 2022 17:43:37 -0700 Subject: [PATCH] [D&D] Refactor and cleanup embeddables (#1947) General plugin updates (#1939): - add start service getters/setters to plugin service - move setters from setup to start so they're available to embeddable Embeddable updates: - use getters instead of depending on start services in constructor - remove wizard from add panel "create" list - add correct edit paths/URLs for linking to wizard #1940 - add basic error embeddable rendering - render via ExpressionLoader instead of wizard_component #1920 - wizard_component no longer used, but updated for future use - add subscription handling for query, filter, timerange changes #1937 - fix clone/replace panel actions #1943, #1944 - fix title/description panel rendering #1921 - add inspection panel action #1936 Asset updates: - Update empty workspace illustration - Add secondary fill icon version to match new visualization icons fixes #1936, #1920, #1937, #1940, #1921, #1939, #1941, #1943, #1944 Signed-off-by: Josh Romero --- .../wizard/public/assets/fields_bg.svg | 91 +++-- .../wizard/public/assets/hand_field.svg | 24 +- .../assets/wizard_icon_secondary_fill.svg | 25 ++ .../public/embeddable/wizard_component.tsx | 66 +--- .../public/embeddable/wizard_embeddable.tsx | 314 +++++++++++++----- .../embeddable/wizard_embeddable_factory.tsx | 101 +++--- src/plugins/wizard/public/plugin.ts | 66 ++-- src/plugins/wizard/public/plugin_services.ts | 26 +- .../type_service/visualization_type.tsx | 6 +- .../visualizations/metric/to_expression.ts | 11 +- 10 files changed, 468 insertions(+), 262 deletions(-) create mode 100644 src/plugins/wizard/public/assets/wizard_icon_secondary_fill.svg diff --git a/src/plugins/wizard/public/assets/fields_bg.svg b/src/plugins/wizard/public/assets/fields_bg.svg index 3c22912ec34b..d7ac9e455f0c 100644 --- a/src/plugins/wizard/public/assets/fields_bg.svg +++ b/src/plugins/wizard/public/assets/fields_bg.svg @@ -1,36 +1,61 @@ - - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/wizard/public/assets/hand_field.svg b/src/plugins/wizard/public/assets/hand_field.svg index 753a3af354c0..8c38e60edd59 100644 --- a/src/plugins/wizard/public/assets/hand_field.svg +++ b/src/plugins/wizard/public/assets/hand_field.svg @@ -1,44 +1,44 @@ - + - + - + - + - - + + - + - - + + - + - - + + diff --git a/src/plugins/wizard/public/assets/wizard_icon_secondary_fill.svg b/src/plugins/wizard/public/assets/wizard_icon_secondary_fill.svg new file mode 100644 index 000000000000..cdaad42f6276 --- /dev/null +++ b/src/plugins/wizard/public/assets/wizard_icon_secondary_fill.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/wizard/public/embeddable/wizard_component.tsx b/src/plugins/wizard/public/embeddable/wizard_component.tsx index 58087c83ffda..675baf28796a 100644 --- a/src/plugins/wizard/public/embeddable/wizard_component.tsx +++ b/src/plugins/wizard/public/embeddable/wizard_component.tsx @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { SavedObjectEmbeddableInput, withEmbeddableSubscription } from '../../../embeddable/public'; import { WizardEmbeddable, WizardOutput } from './wizard_embeddable'; -import { validateSchemaState } from '../application/utils/validate_schema_state'; +import { getReactExpressionRenderer } from '../plugin_services'; interface Props { embeddable: WizardEmbeddable; @@ -15,54 +15,20 @@ interface Props { output: WizardOutput; } -function WizardEmbeddableComponentInner({ - embeddable, - input: {}, - output: { savedAttributes }, -}: Props) { - const { ReactExpressionRenderer, toasts, types, indexPatterns, aggs } = embeddable; - const [expression, setExpression] = useState(); - - useEffect(() => { - const { visualizationState: visualization, styleState: style } = savedAttributes || {}; - if (savedAttributes === undefined || visualization === undefined || style === undefined) { - return; - } - - const rootState = { - visualization: JSON.parse(visualization), - style: JSON.parse(style), - }; - - const visualizationType = types.get(rootState.visualization?.activeVisualization?.name ?? ''); - if (!visualizationType) { - throw new Error(`Invalid visualization type ${visualizationType}`); - } - const { toExpression, ui } = visualizationType; - - async function loadExpression() { - const schemas = ui.containerConfig.data.schemas; - const [valid, errorMsg] = validateSchemaState(schemas, rootState); - - if (!valid) { - if (errorMsg) { - toasts.addWarning(errorMsg); - } - setExpression(undefined); - return; - } - const exp = await toExpression(rootState, indexPatterns, aggs); - setExpression(exp); - } - - if (savedAttributes !== undefined) { - loadExpression(); - } - }, [aggs, indexPatterns, savedAttributes, toasts, types]); - - return ; - - // TODO: add correct loading and error states +function WizardEmbeddableComponentInner({ embeddable, input: {}, output: { error } }: Props) { + const { expression } = embeddable; + const ReactExpressionRenderer = getReactExpressionRenderer(); + + return ( + <> + {error?.message ? ( + // TODO: add correct loading and error states +
{error.message}
+ ) : ( + + )} + + ); } export const WizardEmbeddableComponent = withEmbeddableSubscription< diff --git a/src/plugins/wizard/public/embeddable/wizard_embeddable.tsx b/src/plugins/wizard/public/embeddable/wizard_embeddable.tsx index 12449674ce6c..bc2d4548dda1 100644 --- a/src/plugins/wizard/public/embeddable/wizard_embeddable.tsx +++ b/src/plugins/wizard/public/embeddable/wizard_embeddable.tsx @@ -3,125 +3,289 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React from 'react'; +import { cloneDeep, isEqual } from 'lodash'; import ReactDOM from 'react-dom'; -import { Subscription } from 'rxjs'; +import { merge, Subscription } from 'rxjs'; -import { WizardSavedObjectAttributes } from '../../common'; +import { PLUGIN_ID, WizardSavedObjectAttributes, WIZARD_SAVED_OBJECT } from '../../common'; import { Embeddable, EmbeddableOutput, + ErrorEmbeddable, IContainer, SavedObjectEmbeddableInput, } from '../../../embeddable/public'; -import { IToasts, SavedObjectsClientContract } from '../../../../core/public'; -import { WizardEmbeddableComponent } from './wizard_component'; -import { ReactExpressionRendererType } from '../../../expressions/public'; -import { TypeServiceStart } from '../services/type_service'; -import { DataPublicPluginStart } from '../../../data/public'; +import { + ExpressionRenderError, + ExpressionsStart, + IExpressionLoaderParams, +} from '../../../expressions/public'; +import { + Filter, + opensearchFilters, + Query, + TimefilterContract, + TimeRange, +} from '../../../data/public'; +import { validateSchemaState } from '../application/utils/validate_schema_state'; +import { getExpressionLoader, getTypeService } from '../plugin_services'; -export const WIZARD_EMBEDDABLE = 'WIZARD_EMBEDDABLE'; +// Apparently this needs to match the saved object type for the clone and replace panel actions to work +export const WIZARD_EMBEDDABLE = WIZARD_SAVED_OBJECT; + +export interface WizardEmbeddableConfiguration { + savedWizard: WizardSavedObjectAttributes; + // TODO: add indexPatterns as part of configuration + // indexPatterns?: IIndexPattern[]; + editPath: string; + editUrl: string; + editable: boolean; +} export interface WizardOutput extends EmbeddableOutput { /** * Will contain the saved object attributes of the Wizard Saved Object that matches * `input.savedObjectId`. If the id is invalid, this may be undefined. */ - savedAttributes?: WizardSavedObjectAttributes; + savedWizard?: WizardSavedObjectAttributes; } +type ExpressionLoader = InstanceType; + export class WizardEmbeddable extends Embeddable { public readonly type = WIZARD_EMBEDDABLE; - private subscription: Subscription; + private handler?: ExpressionLoader; + private timeRange?: TimeRange; + private query?: Query; + private filters?: Filter[]; + private abortController?: AbortController; + public expression: string = ''; + private autoRefreshFetchSubscription: Subscription; + private subscriptions: Subscription[] = []; private node?: HTMLElement; - private savedObjectsClient: SavedObjectsClientContract; - public ReactExpressionRenderer: ReactExpressionRendererType; - public toasts: IToasts; - public types: TypeServiceStart; - public indexPatterns: DataPublicPluginStart['indexPatterns']; - public aggs: DataPublicPluginStart['search']['aggs']; - private savedObjectId?: string; + private savedWizard?: WizardSavedObjectAttributes; + private serializedState?: { visualization: string; style: string }; constructor( + timefilter: TimefilterContract, + { savedWizard, editPath, editUrl, editable }: WizardEmbeddableConfiguration, initialInput: SavedObjectEmbeddableInput, { parent, - savedObjectsClient, - data, - ReactExpressionRenderer, - toasts, - types, }: { parent?: IContainer; - data: DataPublicPluginStart; - savedObjectsClient: SavedObjectsClientContract; - ReactExpressionRenderer: ReactExpressionRendererType; - toasts: IToasts; - types: TypeServiceStart; } ) { - // TODO: can default title come from saved object? - super(initialInput, { defaultTitle: 'wizard' }, parent); - this.savedObjectsClient = savedObjectsClient; - this.ReactExpressionRenderer = ReactExpressionRenderer; - this.toasts = toasts; - this.types = types; - this.indexPatterns = data.indexPatterns; - this.aggs = data.search.aggs; - - this.subscription = this.getInput$().subscribe(async () => { - // There is a little more work today for this embeddable because it has - // more output it needs to update in response to input state changes. - let savedAttributes: WizardSavedObjectAttributes | undefined; - - // Since this is an expensive task, we save a local copy of the previous - // savedObjectId locally and only retrieve the new saved object if the id - // actually changed. - if (this.savedObjectId !== this.input.savedObjectId) { - this.savedObjectId = this.input.savedObjectId; - const wizardSavedObject = await this.savedObjectsClient.get( - 'wizard', - this.input.savedObjectId - ); - savedAttributes = wizardSavedObject?.attributes; + super( + initialInput, + { + defaultTitle: savedWizard.title, + editPath, + editApp: PLUGIN_ID, + editUrl, + editable, + savedWizard, + }, + parent + ); + + this.savedWizard = savedWizard; + + this.autoRefreshFetchSubscription = timefilter + .getAutoRefreshFetch$() + .subscribe(this.updateHandler.bind(this)); + + this.subscriptions.push( + merge(this.getOutput$(), this.getInput$()).subscribe(() => { + this.handleChanges(); + }) + ); + } + + private getSerializedState = () => { + const { visualizationState: visualization = '{}', styleState: style = '{}' } = + this.savedWizard || {}; + return { + visualization, + style, + }; + }; + + private getExpression = async () => { + if (!this.serializedState) { + return; + } + const { visualization, style } = this.serializedState; + const rootState = { + visualization: JSON.parse(visualization), + style: JSON.parse(style), + }; + const visualizationName = rootState.visualization?.activeVisualization?.name ?? ''; + const visualizationType = getTypeService().get(visualizationName); + if (!visualizationType) { + this.onContainerError(new Error(`Invalid visualization type ${visualizationName}`)); + return; + } + const { toExpression, ui } = visualizationType; + const schemas = ui.containerConfig.data.schemas; + const [valid, errorMsg] = validateSchemaState(schemas, rootState); + + if (!valid) { + if (errorMsg) { + this.onContainerError(new Error(errorMsg)); + return; } + } else { + // TODO: handle error in Expression creation + const exp = await toExpression(rootState); + return exp; + } + }; - this.updateOutput({ - savedAttributes, - title: savedAttributes?.title, - }); - }); + // Needed to enable inspection panel option + public getInspectorAdapters = () => { + if (!this.handler) { + return undefined; + } + return this.handler.inspect(); + }; + + // Needed to add informational tooltip + public getDescription() { + return this.savedWizard?.description; } public render(node: HTMLElement) { - if (this.node) { - ReactDOM.unmountComponentAtNode(this.node); + if (this.output.error) { + // TODO: Can we find a more elegant way to throw, propagate, and render errors? + const errorEmbeddable = new ErrorEmbeddable( + this.output.error as Error, + this.input, + this.parent + ); + return errorEmbeddable.render(node); } - this.node = node; - ReactDOM.render(, node); + this.timeRange = cloneDeep(this.input.timeRange); + + const div = document.createElement('div'); + div.className = `wizard visualize panel-content panel-content--fullWidth`; + node.appendChild(div); + + this.node = div; + super.render(this.node); + + // TODO: Investigate migrating to using `./wizard_component` for React rendering instead + const ExpressionLoader = getExpressionLoader(); + this.handler = new ExpressionLoader(this.node, undefined, { + onRenderError: (_element: HTMLElement, error: ExpressionRenderError) => { + this.onContainerError(error); + }, + }); + + if (this.savedWizard?.description) { + div.setAttribute('data-description', this.savedWizard.description); + } + + div.setAttribute('data-test-subj', 'wizardLoader'); + + this.subscriptions.push(this.handler.loading$.subscribe(this.onContainerLoading)); + this.subscriptions.push(this.handler.render$.subscribe(this.onContainerRender)); + + this.updateHandler(); } - /** - * Lets re-sync our saved object to make sure it's up to date! - */ public async reload() { - this.savedObjectId = this.input.savedObjectId; - const wizardSavedObject = await this.savedObjectsClient.get( - 'wizard', - this.input.savedObjectId - ); - const savedAttributes = wizardSavedObject?.attributes; - this.updateOutput({ - savedAttributes, - title: wizardSavedObject?.attributes?.title, - }); + this.updateHandler(); } public destroy() { super.destroy(); - this.subscription.unsubscribe(); + this.subscriptions.forEach((s) => s.unsubscribe()); if (this.node) { ReactDOM.unmountComponentAtNode(this.node); } + + if (this.handler) { + this.handler.destroy(); + this.handler.getElement().remove(); + } + this.autoRefreshFetchSubscription.unsubscribe(); + } + + private async updateHandler() { + const expressionParams: IExpressionLoaderParams = { + searchContext: { + timeRange: this.timeRange, + query: this.input.query, + filters: this.input.filters, + }, + }; + if (this.abortController) { + this.abortController.abort(); + } + this.abortController = new AbortController(); + const abortController = this.abortController; + + if (this.handler && !abortController.signal.aborted) { + this.handler.update(this.expression, expressionParams); + } + } + + public async handleChanges() { + // TODO: refactor (here and in visualize) to remove lodash dependency - immer probably a better choice + + let dirty = false; + + // Check if timerange has changed + if (!isEqual(this.input.timeRange, this.timeRange)) { + this.timeRange = cloneDeep(this.input.timeRange); + dirty = true; + } + + // Check if filters has changed + if (!opensearchFilters.onlyDisabledFiltersChanged(this.input.filters, this.filters)) { + this.filters = this.input.filters; + dirty = true; + } + + // Check if query has changed + if (!isEqual(this.input.query, this.query)) { + this.query = this.input.query; + dirty = true; + } + + // Check if rootState has changed + if (!isEqual(this.getSerializedState(), this.serializedState)) { + this.serializedState = this.getSerializedState(); + this.expression = (await this.getExpression()) ?? ''; + dirty = true; + } + + if (this.handler && dirty) { + this.updateHandler(); + } } + + onContainerLoading = () => { + this.renderComplete.dispatchInProgress(); + this.updateOutput({ loading: true, error: undefined }); + }; + + onContainerRender = () => { + this.renderComplete.dispatchComplete(); + this.updateOutput({ loading: false, error: undefined }); + }; + + onContainerError = (error: ExpressionRenderError) => { + if (this.abortController) { + this.abortController.abort(); + } + this.renderComplete.dispatchError(); + this.updateOutput({ loading: false, error }); + }; + + // TODO: we may eventually need to add support for visualizations that use triggers like filter or brush, but current wizard vis types don't support triggers + // public supportedTriggers(): TriggerId[] { + // return this.visType.getSupportedTriggers?.() ?? []; + // } } diff --git a/src/plugins/wizard/public/embeddable/wizard_embeddable_factory.tsx b/src/plugins/wizard/public/embeddable/wizard_embeddable_factory.tsx index 6e8a49a05d3f..f2d92d001303 100644 --- a/src/plugins/wizard/public/embeddable/wizard_embeddable_factory.tsx +++ b/src/plugins/wizard/public/embeddable/wizard_embeddable_factory.tsx @@ -4,38 +4,26 @@ */ import { i18n } from '@osd/i18n'; -import { - IUiSettingsClient, - NotificationsStart, - SavedObjectsClientContract, -} from '../../../../core/public'; -import { DataPublicPluginStart } from '../../../data/public'; import { EmbeddableFactory, EmbeddableFactoryDefinition, EmbeddableOutput, - EmbeddableStart, ErrorEmbeddable, IContainer, SavedObjectEmbeddableInput, } from '../../../embeddable/public'; -import { ExpressionsStart } from '../../../expressions/public'; import { VISUALIZE_ENABLE_LABS_SETTING } from '../../../visualizations/public'; -import { WizardSavedObjectAttributes } from '../../common'; -import { TypeServiceStart } from '../services/type_service'; +import { + EDIT_PATH, + PLUGIN_ID, + PLUGIN_NAME, + WizardSavedObjectAttributes, + WIZARD_SAVED_OBJECT, +} from '../../common'; import { DisabledEmbeddable } from './disabled_embeddable'; import { WizardEmbeddable, WizardOutput, WIZARD_EMBEDDABLE } from './wizard_embeddable'; import wizardIcon from '../assets/wizard_icon.svg'; - -interface StartServices { - data: DataPublicPluginStart; - expressions: ExpressionsStart; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - savedObjectsClient: SavedObjectsClientContract; - notifications: NotificationsStart; - types: TypeServiceStart; - uiSettings: IUiSettingsClient; -} +import { getHttp, getSavedWizardLoader, getTimeFilter, getUISettings } from '../plugin_services'; // TODO: use or remove? export type WizardEmbeddableFactory = EmbeddableFactory< @@ -56,13 +44,19 @@ export class WizardEmbeddableFactoryDefinition public readonly type = WIZARD_EMBEDDABLE; public readonly savedObjectMetaData = { // TODO: Update to include most vis functionality - name: 'Wizard', + name: PLUGIN_NAME, includeFields: ['visualizationState'], - type: 'wizard', + type: WIZARD_SAVED_OBJECT, getIconForSavedObject: () => wizardIcon, }; - constructor(private getStartServices: () => Promise) {} + // TODO: Would it be better to explicitly declare start service dependencies? + constructor() {} + + public canCreateNew() { + // Because wizard creation starts with the visualization modal, no need to have a separate entry for wizard until it's separate + return false; + } public async isEditable() { // TODO: Add proper access controls @@ -70,44 +64,53 @@ export class WizardEmbeddableFactoryDefinition return true; } - public createFromSavedObject = ( + public async createFromSavedObject( savedObjectId: string, input: Partial & { id: string }, parent?: IContainer - ): Promise => { - return this.create({ ...input, savedObjectId }, parent); - }; + ): Promise { + try { + const savedWizard = await getSavedWizardLoader().get(savedObjectId); + + const editPath = `${EDIT_PATH}/${savedObjectId}`; + + const editUrl = getHttp().basePath.prepend(`/app/${PLUGIN_ID}${editPath}`); - public async create(input: SavedObjectEmbeddableInput, parent?: IContainer) { - // TODO: Use savedWizardLoader here instead - const { - data, - expressions: { ReactExpressionRenderer }, - notifications: { toasts }, - savedObjectsClient, - types, - uiSettings, - } = await this.getStartServices(); + const isLabsEnabled = getUISettings().get(VISUALIZE_ENABLE_LABS_SETTING); - const isLabsEnabled = uiSettings.get(VISUALIZE_ENABLE_LABS_SETTING); + if (!isLabsEnabled) { + return new DisabledEmbeddable(PLUGIN_NAME, input); + } - if (!isLabsEnabled) { - return new DisabledEmbeddable('Wizard', input); + return new WizardEmbeddable( + getTimeFilter(), + { + savedWizard, + editUrl, + editPath, + editable: true, + }, + { + ...input, + savedObjectId: input.savedObjectId ?? '', + }, + { + parent, + } + ); + } catch (e) { + console.error(e); // eslint-disable-line no-console + return new ErrorEmbeddable(e as Error, input, parent); } + } - return new WizardEmbeddable(input, { - parent, - data, - savedObjectsClient, - ReactExpressionRenderer, - toasts, - types, - }); + public async create(_input: SavedObjectEmbeddableInput, _parent?: IContainer) { + return undefined; } public getDisplayName() { return i18n.translate('wizard.displayName', { - defaultMessage: 'Wizard', + defaultMessage: PLUGIN_ID, }); } } diff --git a/src/plugins/wizard/public/plugin.ts b/src/plugins/wizard/public/plugin.ts index 4946c93761e9..9c56562f4ba1 100644 --- a/src/plugins/wizard/public/plugin.ts +++ b/src/plugins/wizard/public/plugin.ts @@ -20,11 +20,22 @@ import { WizardStart, } from './types'; import { WizardEmbeddableFactoryDefinition, WIZARD_EMBEDDABLE } from './embeddable'; +import wizardIconSecondaryFill from './assets/wizard_icon_secondary_fill.svg'; import wizardIcon from './assets/wizard_icon.svg'; import { EDIT_PATH, PLUGIN_ID, PLUGIN_NAME, WIZARD_SAVED_OBJECT } from '../common'; import { TypeService } from './services/type_service'; import { getPreloadedStore } from './application/utils/state_management'; -import { setAggService, setIndexPatterns } from './plugin_services'; +import { + setAggService, + setIndexPatterns, + setHttp, + setSavedWizardLoader, + setExpressionLoader, + setTimeFilter, + setUISettings, + setTypeService, + setReactExpressionRenderer, +} from './plugin_services'; import { createSavedWizardLoader } from './saved_visualizations'; import { registerDefaultTypes } from './visualizations'; import { ConfigSchema } from '../config'; @@ -63,10 +74,6 @@ export class WizardPlugin // TODO: Add the redirect await pluginsStart.data.indexPatterns.ensureDefaultIndexPattern(); - // Register plugin services - setAggService(data.search.aggs); - setIndexPatterns(data.indexPatterns); - // Register Default Visualizations const services: WizardServices = { @@ -94,19 +101,7 @@ export class WizardPlugin // TODO: investigate simplification via getter a la visualizations: // const start = createStartServicesGetter(core.getStartServices)); // const embeddableFactory = new WizardEmbeddableFactoryDefinition({ start }); - const embeddableFactory = new WizardEmbeddableFactoryDefinition(async () => { - const [coreStart, pluginsStart, _wizardStart] = await core.getStartServices(); - // TODO: refactor to pass minimal service methods? - return { - savedObjectsClient: coreStart.savedObjects.client, - data: pluginsStart.data, - getEmbeddableFactory: pluginsStart.embeddable.getEmbeddableFactory, - expressions: pluginsStart.expressions, - notifications: coreStart.notifications, - types: this.typeService.start(), - uiSettings: coreStart.uiSettings, - }; - }); + const embeddableFactory = new WizardEmbeddableFactoryDefinition(); embeddable.registerEmbeddableFactory(WIZARD_EMBEDDABLE, embeddableFactory); // Register the plugin as an alias to create visualization @@ -116,7 +111,7 @@ export class WizardPlugin description: i18n.translate('wizard.visPicker.description', { defaultMessage: 'Create visualizations using the new Drag & Drop experience', }), - icon: wizardIcon, + icon: wizardIconSecondaryFill, stage: 'experimental', aliasApp: PLUGIN_ID, aliasPath: '#/', @@ -143,18 +138,31 @@ export class WizardPlugin }; } - public start(core: CoreStart, { data }: WizardPluginStartDependencies): WizardStart { - const typeService = this.typeService; + public start(core: CoreStart, { data, expressions }: WizardPluginStartDependencies): WizardStart { + const typeService = this.typeService.start(); + + const savedWizardLoader = createSavedWizardLoader({ + savedObjectsClient: core.savedObjects.client, + indexPatterns: data.indexPatterns, + search: data.search, + chrome: core.chrome, + overlays: core.overlays, + }); + + // Register plugin services + setAggService(data.search.aggs); + setExpressionLoader(expressions.ExpressionLoader); + setReactExpressionRenderer(expressions.ReactExpressionRenderer); + setHttp(core.http); + setIndexPatterns(data.indexPatterns); + setSavedWizardLoader(savedWizardLoader); + setTimeFilter(data.query.timefilter.timefilter); + setTypeService(typeService); + setUISettings(core.uiSettings); return { - ...typeService.start(), - savedWizardLoader: createSavedWizardLoader({ - savedObjectsClient: core.savedObjects.client, - indexPatterns: data.indexPatterns, - search: data.search, - chrome: core.chrome, - overlays: core.overlays, - }), + ...typeService, + savedWizardLoader, }; } diff --git a/src/plugins/wizard/public/plugin_services.ts b/src/plugins/wizard/public/plugin_services.ts index 67b562d6eca9..8f01ea6e9b6b 100644 --- a/src/plugins/wizard/public/plugin_services.ts +++ b/src/plugins/wizard/public/plugin_services.ts @@ -4,12 +4,36 @@ */ import { createGetterSetter } from '../../opensearch_dashboards_utils/common'; -import { DataPublicPluginStart } from '../../data/public'; +import { DataPublicPluginStart, TimefilterContract } from '../../data/public'; +import { SavedWizardLoader } from './saved_visualizations'; +import { HttpStart, IUiSettingsClient } from '../../../core/public'; +import { ExpressionsStart } from '../../expressions/public'; +import { TypeServiceStart } from './services/type_service'; export const [getAggService, setAggService] = createGetterSetter< DataPublicPluginStart['search']['aggs'] >('data.search.aggs'); +export const [getExpressionLoader, setExpressionLoader] = createGetterSetter< + ExpressionsStart['ExpressionLoader'] +>('expressions.ExpressionLoader'); + +export const [getReactExpressionRenderer, setReactExpressionRenderer] = createGetterSetter< + ExpressionsStart['ReactExpressionRenderer'] +>('expressions.ReactExpressionRenderer'); + +export const [getHttp, setHttp] = createGetterSetter('Http'); + export const [getIndexPatterns, setIndexPatterns] = createGetterSetter< DataPublicPluginStart['indexPatterns'] >('data.indexPatterns'); + +export const [getSavedWizardLoader, setSavedWizardLoader] = createGetterSetter( + 'SavedWizardLoader' +); + +export const [getTimeFilter, setTimeFilter] = createGetterSetter('TimeFilter'); + +export const [getTypeService, setTypeService] = createGetterSetter('TypeService'); + +export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); diff --git a/src/plugins/wizard/public/services/type_service/visualization_type.tsx b/src/plugins/wizard/public/services/type_service/visualization_type.tsx index 62fddfff85bb..90f30d8f8a95 100644 --- a/src/plugins/wizard/public/services/type_service/visualization_type.tsx +++ b/src/plugins/wizard/public/services/type_service/visualization_type.tsx @@ -15,11 +15,7 @@ export class VisualizationType implements IVisualizationType { public readonly icon: IconType; public readonly stage: 'beta' | 'production'; public readonly ui: IVisualizationType['ui']; - public readonly toExpression: ( - state: RootState, - indexPatterns?, - aggs? - ) => Promise; + public readonly toExpression: (state: RootState) => Promise; constructor(options: VisualizationTypeOptions) { this.name = options.name; diff --git a/src/plugins/wizard/public/visualizations/metric/to_expression.ts b/src/plugins/wizard/public/visualizations/metric/to_expression.ts index 0a459c23a7a1..ce930d9b8e40 100644 --- a/src/plugins/wizard/public/visualizations/metric/to_expression.ts +++ b/src/plugins/wizard/public/visualizations/metric/to_expression.ts @@ -91,18 +91,13 @@ export interface MetricRootState extends RootState { style: MetricOptionsDefaults; } -export const toExpression = async ( - { style: styleState, visualization }: MetricRootState, - indexPatterns, - aggs -) => { +export const toExpression = async ({ style: styleState, visualization }: MetricRootState) => { const { activeVisualization, indexPattern: indexId = '' } = visualization; const { aggConfigParams } = activeVisualization || {}; - const indexPatternsService = indexPatterns ?? getIndexPatterns(); + const indexPatternsService = getIndexPatterns(); const indexPattern = await indexPatternsService.get(indexId); - const aggService = aggs ?? getAggService(); - const aggConfigs = aggService.createAggConfigs(indexPattern, cloneDeep(aggConfigParams)); + const aggConfigs = getAggService().createAggConfigs(indexPattern, cloneDeep(aggConfigParams)); // soon this becomes: const opensearchaggs = vis.data.aggs!.toExpressionAst(); const opensearchaggs = buildExpressionFunction(