Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[D&D] Refactor and cleanup embeddables
Browse files Browse the repository at this point in the history
General plugin updates (opensearch-project#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 opensearch-project#1940
- add basic error embeddable rendering
- render via ExpressionLoader instead of wizard_component opensearch-project#1920
- wizard_component no longer used, but updated for future use
- add subscription handling for query, filter, timerange changes opensearch-project#1937
- fix clone/replace panel actions opensearch-project#1943, opensearch-project#1944
- fix title/description panel rendering opensearch-project#1921
- add inspection panel action opensearch-project#1936

Asset updates:
- Update empty workspace illustration
- Add secondary fill icon version to match new visualization icons

fixes opensearch-project#1936, opensearch-project#1920, opensearch-project#1937, opensearch-project#1940, opensearch-project#1921, opensearch-project#1939, opensearch-project#1941, opensearch-project#1943, opensearch-project#1944

Signed-off-by: Josh Romero <[email protected]>
joshuarrrr committed Jul 26, 2022

Verified

This commit was signed with the committer’s verified signature.
joshuarrrr Josh Romero
1 parent c599eff commit 2087757
Showing 10 changed files with 468 additions and 262 deletions.
91 changes: 58 additions & 33 deletions src/plugins/wizard/public/assets/fields_bg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 12 additions & 12 deletions src/plugins/wizard/public/assets/hand_field.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions src/plugins/wizard/public/assets/wizard_icon_secondary_fill.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 16 additions & 50 deletions src/plugins/wizard/public/embeddable/wizard_component.tsx
Original file line number Diff line number Diff line change
@@ -3,66 +3,32 @@
* 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;
input: SavedObjectEmbeddableInput;
output: WizardOutput;
}

function WizardEmbeddableComponentInner({
embeddable,
input: {},
output: { savedAttributes },
}: Props) {
const { ReactExpressionRenderer, toasts, types, indexPatterns, aggs } = embeddable;
const [expression, setExpression] = useState<string>();

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 <ReactExpressionRenderer expression={expression ?? ''} />;

// 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
<div>{error.message}</div>
) : (
<ReactExpressionRenderer expression={expression ?? ''} />
)}
</>
);
}

export const WizardEmbeddableComponent = withEmbeddableSubscription<
314 changes: 239 additions & 75 deletions src/plugins/wizard/public/embeddable/wizard_embeddable.tsx
Original file line number Diff line number Diff line change
@@ -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<ExpressionsStart['ExpressionLoader']>;

export class WizardEmbeddable extends Embeddable<SavedObjectEmbeddableInput, WizardOutput> {
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<WizardSavedObjectAttributes>(
'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(<WizardEmbeddableComponent embeddable={this} />, 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<WizardSavedObjectAttributes>(
'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?.() ?? [];
// }
}
101 changes: 52 additions & 49 deletions src/plugins/wizard/public/embeddable/wizard_embeddable_factory.tsx
Original file line number Diff line number Diff line change
@@ -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,58 +44,73 @@ 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<StartServices>) {}
// 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
// return getCapabilities().visualize.save as boolean;
return true;
}

public createFromSavedObject = (
public async createFromSavedObject(
savedObjectId: string,
input: Partial<SavedObjectEmbeddableInput> & { id: string },
parent?: IContainer
): Promise<WizardEmbeddable | ErrorEmbeddable | DisabledEmbeddable> => {
return this.create({ ...input, savedObjectId }, parent);
};
): Promise<WizardEmbeddable | ErrorEmbeddable | DisabledEmbeddable> {
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<boolean>(VISUALIZE_ENABLE_LABS_SETTING);

const isLabsEnabled = uiSettings.get<boolean>(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,
});
}
}
66 changes: 37 additions & 29 deletions src/plugins/wizard/public/plugin.ts
Original file line number Diff line number Diff line change
@@ -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,
};
}

26 changes: 25 additions & 1 deletion src/plugins/wizard/public/plugin_services.ts
Original file line number Diff line number Diff line change
@@ -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<HttpStart>('Http');

export const [getIndexPatterns, setIndexPatterns] = createGetterSetter<
DataPublicPluginStart['indexPatterns']
>('data.indexPatterns');

export const [getSavedWizardLoader, setSavedWizardLoader] = createGetterSetter<SavedWizardLoader>(
'SavedWizardLoader'
);

export const [getTimeFilter, setTimeFilter] = createGetterSetter<TimefilterContract>('TimeFilter');

export const [getTypeService, setTypeService] = createGetterSetter<TypeServiceStart>('TypeService');

export const [getUISettings, setUISettings] = createGetterSetter<IUiSettingsClient>('UISettings');
Original file line number Diff line number Diff line change
@@ -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<string | undefined>;
public readonly toExpression: (state: RootState) => Promise<string | undefined>;

constructor(options: VisualizationTypeOptions) {
this.name = options.name;
11 changes: 3 additions & 8 deletions src/plugins/wizard/public/visualizations/metric/to_expression.ts
Original file line number Diff line number Diff line change
@@ -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<OpenSearchaggsExpressionFunctionDefinition>(

0 comments on commit 2087757

Please sign in to comment.