From abd381f76a11449d2b45dc44e3999bfbc15488ec Mon Sep 17 00:00:00 2001 From: Ashwin Pc Date: Tue, 5 Apr 2022 09:13:38 +0000 Subject: [PATCH 1/8] feat(Expressions): Initial demo plugin Signed-off-by: Ashwin Pc --- examples/expressions_example/.eslintrc.js | 12 +++ examples/expressions_example/.i18nrc.json | 7 ++ examples/expressions_example/README.md | 11 ++ .../expression_functions/basic/index.ts | 7 ++ .../expression_functions/basic/sleep.ts | 31 ++++++ .../expression_functions/basic/square.ts | 18 ++++ .../common/expression_functions/index.ts | 7 ++ .../expression_functions/render/avatar_fn.ts | 52 +++++++++ .../render/avatar_renderer.tsx | 30 ++++++ .../expression_functions/render/index.ts | 7 ++ examples/expressions_example/common/index.ts | 7 ++ .../opensearch_dashboards.json | 14 +++ .../public/application.tsx | 32 ++++++ .../public/components/app.tsx | 102 ++++++++++++++++++ .../public/components/basic_tab.tsx | 91 ++++++++++++++++ .../public/components/render_tab.tsx | 73 +++++++++++++ examples/expressions_example/public/index.ts | 13 +++ examples/expressions_example/public/plugin.ts | 85 +++++++++++++++ examples/expressions_example/public/types.ts | 29 +++++ 19 files changed, 628 insertions(+) create mode 100644 examples/expressions_example/.eslintrc.js create mode 100644 examples/expressions_example/.i18nrc.json create mode 100755 examples/expressions_example/README.md create mode 100644 examples/expressions_example/common/expression_functions/basic/index.ts create mode 100644 examples/expressions_example/common/expression_functions/basic/sleep.ts create mode 100644 examples/expressions_example/common/expression_functions/basic/square.ts create mode 100644 examples/expressions_example/common/expression_functions/index.ts create mode 100644 examples/expressions_example/common/expression_functions/render/avatar_fn.ts create mode 100644 examples/expressions_example/common/expression_functions/render/avatar_renderer.tsx create mode 100644 examples/expressions_example/common/expression_functions/render/index.ts create mode 100644 examples/expressions_example/common/index.ts create mode 100644 examples/expressions_example/opensearch_dashboards.json create mode 100644 examples/expressions_example/public/application.tsx create mode 100644 examples/expressions_example/public/components/app.tsx create mode 100644 examples/expressions_example/public/components/basic_tab.tsx create mode 100644 examples/expressions_example/public/components/render_tab.tsx create mode 100644 examples/expressions_example/public/index.ts create mode 100644 examples/expressions_example/public/plugin.ts create mode 100644 examples/expressions_example/public/types.ts diff --git a/examples/expressions_example/.eslintrc.js b/examples/expressions_example/.eslintrc.js new file mode 100644 index 000000000000..d40ef4ec9091 --- /dev/null +++ b/examples/expressions_example/.eslintrc.js @@ -0,0 +1,12 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +module.exports = { + root: true, + extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'], + rules: { + '@osd/eslint/require-license-header': 'off', + }, +}; diff --git a/examples/expressions_example/.i18nrc.json b/examples/expressions_example/.i18nrc.json new file mode 100644 index 000000000000..a4777b0c81b7 --- /dev/null +++ b/examples/expressions_example/.i18nrc.json @@ -0,0 +1,7 @@ +{ + "prefix": "expressionsExample", + "paths": { + "expressionsExample": "." + }, + "translations": ["translations/ja-JP.json"] +} diff --git a/examples/expressions_example/README.md b/examples/expressions_example/README.md new file mode 100755 index 000000000000..009a67cd6504 --- /dev/null +++ b/examples/expressions_example/README.md @@ -0,0 +1,11 @@ +# expressions_example + +An OpenSearch Dashboards example plugin to demonstrate the expressions plugin + +--- + +## Development + +See the [OpenSearch Dashboards contributing +guide](https://github.com/opensearch-project/OpenSearch-Dashboards/blob/master/CONTRIBUTING.md) for instructions +setting up your development environment. diff --git a/examples/expressions_example/common/expression_functions/basic/index.ts b/examples/expressions_example/common/expression_functions/basic/index.ts new file mode 100644 index 000000000000..551f8e4f2d59 --- /dev/null +++ b/examples/expressions_example/common/expression_functions/basic/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './sleep'; +export * from './square'; diff --git a/examples/expressions_example/common/expression_functions/basic/sleep.ts b/examples/expressions_example/common/expression_functions/basic/sleep.ts new file mode 100644 index 000000000000..5722369920b5 --- /dev/null +++ b/examples/expressions_example/common/expression_functions/basic/sleep.ts @@ -0,0 +1,31 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import { ExpressionFunctionDefinition } from '../../../../../src/plugins/expressions/public'; + +interface Arguments { + time: number; +} + +export const sleep = (): ExpressionFunctionDefinition<'sleep', any, Arguments, any> => ({ + name: 'sleep', + help: i18n.translate('expressionsExample.function.sleep.help', { + defaultMessage: 'Generates range object', + }), + args: { + time: { + types: ['number'], + help: i18n.translate('expressionsExample.function.sleep.time.help', { + defaultMessage: 'Time for settimeout', + }), + required: false, + }, + }, + fn: async (input, args, context) => { + await new Promise((r) => setTimeout(r, args.time)); + return input; + }, +}); diff --git a/examples/expressions_example/common/expression_functions/basic/square.ts b/examples/expressions_example/common/expression_functions/basic/square.ts new file mode 100644 index 000000000000..899afed37e05 --- /dev/null +++ b/examples/expressions_example/common/expression_functions/basic/square.ts @@ -0,0 +1,18 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import { ExpressionFunctionDefinition } from '../../../../../src/plugins/expressions/public'; + +export const square = (): ExpressionFunctionDefinition<'square', number, {}, any> => ({ + name: 'square', + help: i18n.translate('expressionsExample.function.square.help', { + defaultMessage: 'Squares the input', + }), + args: {}, + fn: async (input, args, context) => { + return input * input; + }, +}); diff --git a/examples/expressions_example/common/expression_functions/index.ts b/examples/expressions_example/common/expression_functions/index.ts new file mode 100644 index 000000000000..f4a68ddbe75a --- /dev/null +++ b/examples/expressions_example/common/expression_functions/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './basic'; +export * from './render'; diff --git a/examples/expressions_example/common/expression_functions/render/avatar_fn.ts b/examples/expressions_example/common/expression_functions/render/avatar_fn.ts new file mode 100644 index 000000000000..7fe377b3f1d3 --- /dev/null +++ b/examples/expressions_example/common/expression_functions/render/avatar_fn.ts @@ -0,0 +1,52 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import { + ExpressionFunctionDefinition, + Render, +} from '../../../../../src/plugins/expressions/public'; +import { AvatarRenderValue } from './avatar_renderer'; + +type Arguments = AvatarRenderValue; + +export const avatarFn = (): ExpressionFunctionDefinition< + 'avatar', + unknown, + Arguments, + Render +> => ({ + name: 'avatar', + type: 'render', + help: i18n.translate('expressionsExample.function.avatar.help', { + defaultMessage: 'Avatar expression function', + }), + args: { + name: { + types: ['string'], + help: i18n.translate('expressionsExample.function.avatar.args.name.help', { + defaultMessage: 'Enter Name', + }), + required: true, + }, + size: { + types: ['string'], + help: i18n.translate('expressionsExample.function.avatar.args.size.help', { + defaultMessage: 'Size of the avatar', + }), + default: 'l', + }, + }, + fn: (input, args) => { + return { + type: 'render', + as: 'avatar', + value: { + name: args.name, + size: args.size, + }, + }; + }, +}); diff --git a/examples/expressions_example/common/expression_functions/render/avatar_renderer.tsx b/examples/expressions_example/common/expression_functions/render/avatar_renderer.tsx new file mode 100644 index 000000000000..8b2fb3e8efaa --- /dev/null +++ b/examples/expressions_example/common/expression_functions/render/avatar_renderer.tsx @@ -0,0 +1,30 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiAvatar, EuiAvatarProps } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { ExpressionRenderDefinition } from '../../../../../src/plugins/expressions/public'; + +export interface AvatarRenderValue { + name: string; + size: EuiAvatarProps['size']; +} + +export const avatar: ExpressionRenderDefinition = { + name: 'avatar', + displayName: i18n.translate('expressionsExample.render.help', { + defaultMessage: 'Render an avatar', + }), + reuseDomNode: true, + render: (domNode, { name, size }, handlers) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + render(, domNode, handlers.done); + }, +}; diff --git a/examples/expressions_example/common/expression_functions/render/index.ts b/examples/expressions_example/common/expression_functions/render/index.ts new file mode 100644 index 000000000000..437b2ac9512b --- /dev/null +++ b/examples/expressions_example/common/expression_functions/render/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './avatar_fn'; +export * from './avatar_renderer'; diff --git a/examples/expressions_example/common/index.ts b/examples/expressions_example/common/index.ts new file mode 100644 index 000000000000..3208d933b77c --- /dev/null +++ b/examples/expressions_example/common/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const PLUGIN_ID = 'expressionsExample'; +export const PLUGIN_NAME = 'expressions_example'; diff --git a/examples/expressions_example/opensearch_dashboards.json b/examples/expressions_example/opensearch_dashboards.json new file mode 100644 index 000000000000..8a1eb85f6176 --- /dev/null +++ b/examples/expressions_example/opensearch_dashboards.json @@ -0,0 +1,14 @@ +{ + "id": "expressionsExample", + "version": "1.0.0", + "opensearchDashboardsVersion": "opensearchDashboards", + "server": false, + "ui": true, + "requiredPlugins": [ + "navigation", + "expressions", + "developerExamples", + "opensearchDashboardsReact" + ], + "optionalPlugins": [] +} \ No newline at end of file diff --git a/examples/expressions_example/public/application.tsx b/examples/expressions_example/public/application.tsx new file mode 100644 index 000000000000..1213451cbdff --- /dev/null +++ b/examples/expressions_example/public/application.tsx @@ -0,0 +1,32 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { AppMountParameters, CoreStart } from '../../../src/core/public'; +import { ExpressionsExampleStartDependencies } from './types'; +import { ExpressionsExampleApp } from './components/app'; +import { OpenSearchDashboardsContextProvider } from '../../../src/plugins/opensearch_dashboards_react/public'; + +export const renderApp = ( + { notifications, http }: CoreStart, + { navigation, expressions }: ExpressionsExampleStartDependencies, + { appBasePath, element }: AppMountParameters +) => { + const services = { expressions }; + ReactDOM.render( + + + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/examples/expressions_example/public/components/app.tsx b/examples/expressions_example/public/components/app.tsx new file mode 100644 index 000000000000..38bbf4edefad --- /dev/null +++ b/examples/expressions_example/public/components/app.tsx @@ -0,0 +1,102 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useMemo } from 'react'; +import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; +import { BrowserRouter as Router } from 'react-router-dom'; + +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageContentHeader, + EuiPageHeader, + EuiTitle, + EuiTabbedContent, +} from '@elastic/eui'; + +import { CoreStart } from '../../../../src/core/public'; +import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; + +import { BasicTab } from './basic_tab'; +import { RenderTab } from './render_tab'; + +interface ExpressionsExampleAppDeps { + basename: string; + notifications: CoreStart['notifications']; + http: CoreStart['http']; + navigation: NavigationPublicPluginStart; +} + +export const ExpressionsExampleApp = ({ basename }: ExpressionsExampleAppDeps) => { + const tabs = useMemo( + () => [ + { + id: 'demo1', + name: ( + + ), + content: , + }, + { + id: 'demo2', + + name: ( + + ), + content: , + }, + ], + [] + ); + // Render the application DOM. + return ( + + + <> + + + + +

+ +

+
+
+ + + +

+ +

+
+
+ + + +
+
+
+ +
+
+ ); +}; diff --git a/examples/expressions_example/public/components/basic_tab.tsx b/examples/expressions_example/public/components/basic_tab.tsx new file mode 100644 index 000000000000..bd70c8f8dbbb --- /dev/null +++ b/examples/expressions_example/public/components/basic_tab.tsx @@ -0,0 +1,91 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiCodeBlock, + EuiFieldNumber, + EuiForm, + EuiFormRow, + EuiSpacer, + EuiStat, + EuiText, + EuiTitle, + EuiHorizontalRule, + EuiFormLabel, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { FormattedMessage } from '@osd/i18n/react'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useOpenSearchDashboards } from '../../../../src/plugins/opensearch_dashboards_react/public'; +import { ExpressionsExampleServices } from '../types'; + +export function BasicTab() { + const { + services: { expressions }, + } = useOpenSearchDashboards(); + const [value, setValue] = useState(2); + const [result, setResult] = useState( + i18n.translate('expressionsExample.tab.demo1.loading', { + defaultMessage: 'Still sleeping', + }) + ); + const expressionString = `sleep time=2000 | square`; + + useEffect(() => { + const execution = expressions.execute(expressionString, value); + execution.getData().then((data) => { + setResult(data); + }); + return () => execution.cancel(); + }, [expressionString, expressions, value]); + + return ( + <> + + +

+ +

+
+ +

+ +

+
+ + + + setValue(Number(e.target.value))} /> + + + + + + + {expressionString} + + + + ); +} diff --git a/examples/expressions_example/public/components/render_tab.tsx b/examples/expressions_example/public/components/render_tab.tsx new file mode 100644 index 000000000000..d386f012c4f0 --- /dev/null +++ b/examples/expressions_example/public/components/render_tab.tsx @@ -0,0 +1,73 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiCodeBlock, + EuiFieldText, + EuiForm, + EuiFormLabel, + EuiFormRow, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { FormattedMessage } from '@osd/i18n/react'; +import React, { useState } from 'react'; +import { ReactExpressionRenderer } from '../../../../src/plugins/expressions/public'; + +export function RenderTab() { + const [value, setValue] = useState('OpenSearch Dashboards'); + const expressionString = `avatar name="${value}" size="xl"`; + + return ( + <> + + +

+ +

+
+ +

+ +

+
+ + + + setValue(String(e.target.value))} /> + + + + + + + {expressionString} + + + + + + + ); +} diff --git a/examples/expressions_example/public/index.ts b/examples/expressions_example/public/index.ts new file mode 100644 index 000000000000..16106a471156 --- /dev/null +++ b/examples/expressions_example/public/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ExpressionsExamplePlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, OpenSearch Dashboards Platform `plugin()` initializer. +export function plugin() { + return new ExpressionsExamplePlugin(); +} +export { ExpressionsExamplePluginSetup, ExpressionsExamplePluginStart } from './types'; diff --git a/examples/expressions_example/public/plugin.ts b/examples/expressions_example/public/plugin.ts new file mode 100644 index 000000000000..6158d4a05cc6 --- /dev/null +++ b/examples/expressions_example/public/plugin.ts @@ -0,0 +1,85 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import { + AppMountParameters, + AppNavLinkStatus, + CoreSetup, + CoreStart, + Plugin, +} from '../../../src/core/public'; +import { + ExpressionsExamplePluginSetup, + ExpressionsExamplePluginStart, + ExpressionsExampleSetupDependencies, + ExpressionsExampleStartDependencies, +} from './types'; +import { PLUGIN_NAME } from '../common'; + +import { sleep, square, avatar, avatarFn } from '../common/expression_functions'; + +export class ExpressionsExamplePlugin + implements Plugin { + public setup( + core: CoreSetup, + { expressions, developerExamples }: ExpressionsExampleSetupDependencies + ): ExpressionsExamplePluginSetup { + // Register an application into the side navigation menu + core.application.register({ + id: 'expressions-example', + title: PLUGIN_NAME, + navLinkStatus: AppNavLinkStatus.hidden, + async mount(params: AppMountParameters) { + // Load application bundle + const { renderApp } = await import('./application'); + // Get start services as specified in opensearch_dashboards.json + const [coreStart, depsStart] = await core.getStartServices(); + // Render the application + return renderApp(coreStart, depsStart as ExpressionsExampleStartDependencies, params); + }, + }); + + const expressionFunctions = [sleep, square, avatarFn]; + const expressionRenderers = [avatar]; + + expressionFunctions.forEach((createExpressionFunction) => { + expressions.registerFunction(createExpressionFunction); + }); + + expressionRenderers.forEach((createExpressionRenderer) => { + expressions.registerRenderer(createExpressionRenderer); + }); + + developerExamples.register({ + appId: 'expressions-example', + title: i18n.translate('expressionsExample.developerExamples.title', { + defaultMessage: 'Expressions', + }), + description: i18n.translate('expressionsExample.developerExamples.description', { + defaultMessage: + 'Examples showing you how the expressions plugin is used to chain functions and render content', + }), + links: [ + { + label: 'README', + href: + 'https://github.com/opensearch-project/OpenSearch-Dashboards/blob/main/src/plugins/expressions/README.md', + iconType: 'logoGithub', + target: '_blank', + size: 's', + }, + ], + }); + + return {}; + } + + public start(core: CoreStart): ExpressionsExamplePluginStart { + return {}; + } + + public stop() {} +} diff --git a/examples/expressions_example/public/types.ts b/examples/expressions_example/public/types.ts new file mode 100644 index 000000000000..ee48f3b673ce --- /dev/null +++ b/examples/expressions_example/public/types.ts @@ -0,0 +1,29 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { CoreStart } from '../../../src/core/public'; +import { ExpressionsSetup, ExpressionsStart } from '../../../src/plugins/expressions/public'; +import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public'; +import { DeveloperExamplesSetup } from '../../developer_examples/public'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ExpressionsExamplePluginSetup {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ExpressionsExamplePluginStart {} + +export interface ExpressionsExampleSetupDependencies { + expressions: ExpressionsSetup; + developerExamples: DeveloperExamplesSetup; +} + +export interface ExpressionsExampleStartDependencies { + navigation: NavigationPublicPluginStart; + expressions: ExpressionsStart; +} + +export interface ExpressionsExampleServices extends CoreStart { + expressions: ExpressionsStart; +} From fe4f0c1407d1c8f945b356a5106328b296f6a9e8 Mon Sep 17 00:00:00 2001 From: Ashwin Pc Date: Tue, 5 Apr 2022 09:53:50 +0000 Subject: [PATCH 2/8] feat(Expressions): Adds handlers tab Signed-off-by: Ashwin Pc --- .../expression_functions/action/index.ts | 7 +++ .../action/quick_form_fn.ts | 53 ++++++++++++++++ .../action/quick_form_renderer.tsx | 61 +++++++++++++++++++ .../common/expression_functions/index.ts | 1 + .../public/application.tsx | 2 +- .../public/components/actions_tab.tsx | 58 ++++++++++++++++++ .../public/components/app.tsx | 17 +++++- .../public/components/render_tab.tsx | 29 ++++++++- examples/expressions_example/public/plugin.ts | 13 +++- examples/expressions_example/public/types.ts | 3 +- 10 files changed, 233 insertions(+), 11 deletions(-) create mode 100644 examples/expressions_example/common/expression_functions/action/index.ts create mode 100644 examples/expressions_example/common/expression_functions/action/quick_form_fn.ts create mode 100644 examples/expressions_example/common/expression_functions/action/quick_form_renderer.tsx create mode 100644 examples/expressions_example/public/components/actions_tab.tsx diff --git a/examples/expressions_example/common/expression_functions/action/index.ts b/examples/expressions_example/common/expression_functions/action/index.ts new file mode 100644 index 000000000000..17e5f37c3d31 --- /dev/null +++ b/examples/expressions_example/common/expression_functions/action/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './quick_form_fn'; +export * from './quick_form_renderer'; diff --git a/examples/expressions_example/common/expression_functions/action/quick_form_fn.ts b/examples/expressions_example/common/expression_functions/action/quick_form_fn.ts new file mode 100644 index 000000000000..f0466acb8b91 --- /dev/null +++ b/examples/expressions_example/common/expression_functions/action/quick_form_fn.ts @@ -0,0 +1,53 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import { + ExpressionFunctionDefinition, + Render, +} from '../../../../../src/plugins/expressions/public'; +import { QuickFormRenderValue } from './quick_form_renderer'; + +type Arguments = QuickFormRenderValue; + +export const quickFormFn = (): ExpressionFunctionDefinition< + 'quick-form', + unknown, + Arguments, + Render +> => ({ + name: 'quick-form', + type: 'render', + help: i18n.translate('expressionsExample.function.avatar.help', { + defaultMessage: 'Avatar expression function', + }), + args: { + label: { + types: ['string'], + help: i18n.translate('expressionsExample.function.form.args.label.help', { + defaultMessage: 'Form label', + }), + default: i18n.translate('expressionsExample.function.form.args.label.default', { + defaultMessage: 'Input', + }), + }, + buttonLabel: { + types: ['string'], + help: i18n.translate('expressionsExample.function.form.args.buttonLabel.help', { + defaultMessage: 'Button label', + }), + default: i18n.translate('expressionsExample.function.form.args.buttonLabel.default', { + defaultMessage: 'Submit', + }), + }, + }, + fn: (input, args) => { + return { + type: 'render', + as: 'quick-form-renderer', + value: { ...args }, + }; + }, +}); diff --git a/examples/expressions_example/common/expression_functions/action/quick_form_renderer.tsx b/examples/expressions_example/common/expression_functions/action/quick_form_renderer.tsx new file mode 100644 index 000000000000..5c987595e1f2 --- /dev/null +++ b/examples/expressions_example/common/expression_functions/action/quick_form_renderer.tsx @@ -0,0 +1,61 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useCallback, useState } from 'react'; +import { EuiForm, EuiFormRow, EuiButton, EuiFieldText } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { ExpressionRenderDefinition } from '../../../../../src/plugins/expressions/public'; + +export interface QuickFormRenderValue { + label: string; + buttonLabel: string; +} + +export const quickFormRenderer: ExpressionRenderDefinition = { + name: 'quick-form-renderer', + displayName: i18n.translate('expressionsExample.form.render.help', { + defaultMessage: 'Render a simple input form', + }), + reuseDomNode: true, + render: (domNode, config, handlers) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + render( + + handlers.event({ + data: value, + }) + } + />, + domNode, + handlers.done + ); + }, +}; + +interface QuickFormProps extends QuickFormRenderValue { + onSubmit: Function; +} + +const QuickForm = ({ onSubmit, buttonLabel, label }: QuickFormProps) => { + const [value, setValue] = useState(''); + const handleClick = useCallback(() => { + onSubmit(value); + }, [onSubmit, value]); + + return ( + + + setValue(e.target.value)} /> + + {buttonLabel} + + ); +}; diff --git a/examples/expressions_example/common/expression_functions/index.ts b/examples/expressions_example/common/expression_functions/index.ts index f4a68ddbe75a..2bf7e68d0038 100644 --- a/examples/expressions_example/common/expression_functions/index.ts +++ b/examples/expressions_example/common/expression_functions/index.ts @@ -5,3 +5,4 @@ export * from './basic'; export * from './render'; +export * from './action'; diff --git a/examples/expressions_example/public/application.tsx b/examples/expressions_example/public/application.tsx index 1213451cbdff..15953cf73ce4 100644 --- a/examples/expressions_example/public/application.tsx +++ b/examples/expressions_example/public/application.tsx @@ -15,7 +15,7 @@ export const renderApp = ( { navigation, expressions }: ExpressionsExampleStartDependencies, { appBasePath, element }: AppMountParameters ) => { - const services = { expressions }; + const services = { expressions, notifications }; ReactDOM.render( (); + const handleEvent = useCallback( + ({ data }) => { + notifications.toasts.addSuccess(data); + }, + [notifications.toasts] + ); + + const expressionString = `quick-form label="Toast message" buttonLabel="Toast"`; + + return ( + <> + + +

+ +

+
+ +

+ +

+
+ + + + + {expressionString} + + + + ); +} diff --git a/examples/expressions_example/public/components/app.tsx b/examples/expressions_example/public/components/app.tsx index 38bbf4edefad..1f6c7b87799c 100644 --- a/examples/expressions_example/public/components/app.tsx +++ b/examples/expressions_example/public/components/app.tsx @@ -23,6 +23,7 @@ import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/ import { BasicTab } from './basic_tab'; import { RenderTab } from './render_tab'; +import { ActionsTab } from './actions_tab'; interface ExpressionsExampleAppDeps { basename: string; @@ -38,7 +39,7 @@ export const ExpressionsExampleApp = ({ basename }: ExpressionsExampleAppDeps) = id: 'demo1', name: ( @@ -47,16 +48,26 @@ export const ExpressionsExampleApp = ({ basename }: ExpressionsExampleAppDeps) = }, { id: 'demo2', - name: ( ), content: , }, + { + id: 'demo3', + name: ( + + ), + content: , + }, ], [] ); diff --git a/examples/expressions_example/public/components/render_tab.tsx b/examples/expressions_example/public/components/render_tab.tsx index d386f012c4f0..ff7ce2ac80fa 100644 --- a/examples/expressions_example/public/components/render_tab.tsx +++ b/examples/expressions_example/public/components/render_tab.tsx @@ -10,6 +10,7 @@ import { EuiFormLabel, EuiFormRow, EuiSpacer, + EuiSelect, EuiText, EuiTitle, } from '@elastic/eui'; @@ -20,7 +21,8 @@ import { ReactExpressionRenderer } from '../../../../src/plugins/expressions/pub export function RenderTab() { const [value, setValue] = useState('OpenSearch Dashboards'); - const expressionString = `avatar name="${value}" size="xl"`; + const [size, setSize] = useState('xl'); + const expressionString = `avatar name="${value}" size="${size}"`; return ( <> @@ -45,12 +47,33 @@ export function RenderTab() { setValue(String(e.target.value))} /> + + setSize(String(e.target.value))} + /> + diff --git a/examples/expressions_example/public/plugin.ts b/examples/expressions_example/public/plugin.ts index 6158d4a05cc6..47e02ae1d006 100644 --- a/examples/expressions_example/public/plugin.ts +++ b/examples/expressions_example/public/plugin.ts @@ -19,7 +19,14 @@ import { } from './types'; import { PLUGIN_NAME } from '../common'; -import { sleep, square, avatar, avatarFn } from '../common/expression_functions'; +import { + sleep, + square, + avatar, + avatarFn, + quickFormFn, + quickFormRenderer, +} from '../common/expression_functions'; export class ExpressionsExamplePlugin implements Plugin { @@ -42,8 +49,8 @@ export class ExpressionsExamplePlugin }, }); - const expressionFunctions = [sleep, square, avatarFn]; - const expressionRenderers = [avatar]; + const expressionFunctions = [sleep, square, avatarFn, quickFormFn]; + const expressionRenderers = [avatar, quickFormRenderer]; expressionFunctions.forEach((createExpressionFunction) => { expressions.registerFunction(createExpressionFunction); diff --git a/examples/expressions_example/public/types.ts b/examples/expressions_example/public/types.ts index ee48f3b673ce..ea32b4783283 100644 --- a/examples/expressions_example/public/types.ts +++ b/examples/expressions_example/public/types.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { CoreStart } from '../../../src/core/public'; +import { CoreStart, NotificationsStart } from '../../../src/core/public'; import { ExpressionsSetup, ExpressionsStart } from '../../../src/plugins/expressions/public'; import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public'; import { DeveloperExamplesSetup } from '../../developer_examples/public'; @@ -26,4 +26,5 @@ export interface ExpressionsExampleStartDependencies { export interface ExpressionsExampleServices extends CoreStart { expressions: ExpressionsStart; + notifications: NotificationsStart; } From 65c63e1f53dd3d101cefa34457ec3ad2be447326 Mon Sep 17 00:00:00 2001 From: Ashwin Pc Date: Tue, 5 Apr 2022 23:46:25 +0000 Subject: [PATCH 3/8] feat(Expressions): Adds playground tab Signed-off-by: Ashwin Pc --- .../public/components/actions_tab.tsx | 31 ++--- .../public/components/app.tsx | 31 +++-- .../public/components/basic_tab.tsx | 36 ++--- .../public/components/playground_section.tsx | 126 ++++++++++++++++++ .../public/components/playground_tab.tsx | 53 ++++++++ .../public/components/render_tab.tsx | 31 ++--- 6 files changed, 240 insertions(+), 68 deletions(-) create mode 100644 examples/expressions_example/public/components/playground_section.tsx create mode 100644 examples/expressions_example/public/components/playground_tab.tsx diff --git a/examples/expressions_example/public/components/actions_tab.tsx b/examples/expressions_example/public/components/actions_tab.tsx index 81d256abd8d2..0d1d9a699c36 100644 --- a/examples/expressions_example/public/components/actions_tab.tsx +++ b/examples/expressions_example/public/components/actions_tab.tsx @@ -3,7 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiCodeBlock, EuiFormLabel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import { EuiCallOut, EuiCodeBlock, EuiFormLabel, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; import React, { useCallback } from 'react'; import { ReactExpressionRenderer } from '../../../../src/plugins/expressions/public'; @@ -26,23 +27,17 @@ export function ActionsTab() { return ( <> - -

- -

-
- -

- -

-
+ + + [ { - id: 'demo1', + id: 'demo-1', name: ( , }, { - id: 'demo2', + id: 'demo-2', name: ( , }, { - id: 'demo3', + id: 'demo-3', name: ( , }, + { + id: 'demo-4', + name: ( + + ), + content: , + }, ], [] ); @@ -91,17 +104,17 @@ export const ExpressionsExampleApp = ({ basename }: ExpressionsExampleAppDeps) = - -

+ +

-

-
+

+
- +
diff --git a/examples/expressions_example/public/components/basic_tab.tsx b/examples/expressions_example/public/components/basic_tab.tsx index bd70c8f8dbbb..221bf2e90919 100644 --- a/examples/expressions_example/public/components/basic_tab.tsx +++ b/examples/expressions_example/public/components/basic_tab.tsx @@ -10,14 +10,12 @@ import { EuiFormRow, EuiSpacer, EuiStat, - EuiText, - EuiTitle, - EuiHorizontalRule, EuiFormLabel, + EuiCallOut, } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useOpenSearchDashboards } from '../../../../src/plugins/opensearch_dashboards_react/public'; import { ExpressionsExampleServices } from '../types'; @@ -44,24 +42,18 @@ export function BasicTab() { return ( <> - -

- -

-
- -

- -

-
- + + + + (); + const [loading, setLoading] = useState(false); + const [input, setInput] = useState(defaultInput); + const [expression, setExpression] = useState(defaultExpression); + const [result, setResult] = useState(''); + + useEffect(() => { + let isMounted = true; + if (renderType) return; + + try { + setLoading(true); + const execution = expressions.execute(expression, input); + execution.getData().then((data: any) => { + if (!isMounted) return; + + const value = + data?.type === 'error' + ? `Error: ${data?.error?.message ?? 'Something went wrong'}` + : data; + + setLoading(false); + setResult(String(value)); + }); + } catch (error) { + setLoading(false); + setResult(String(error)); + } + + return () => { + isMounted = false; + }; + }, [expressions, input, expression, renderType]); + + return ( + <> + + +

{title}

+
+ + {/* Rendered the input field only for non renderable expressions */} + {!renderType && ( + <> + + + setInput(e.target.value)} /> + + + + + )} + + + + setExpression(value)} + /> + + + + + {renderType ? ( + + ) : ( + + {loading && } + {result} + + )} +
+ + ); +} diff --git a/examples/expressions_example/public/components/playground_tab.tsx b/examples/expressions_example/public/components/playground_tab.tsx new file mode 100644 index 000000000000..9acdbfa89796 --- /dev/null +++ b/examples/expressions_example/public/components/playground_tab.tsx @@ -0,0 +1,53 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { FormattedMessage } from '@osd/i18n/react'; +import React from 'react'; +import { PlaygroundSection } from './playground_section'; + +export function PlaygroundTab() { + return ( + <> + + + + + + + + + + + + + ); +} diff --git a/examples/expressions_example/public/components/render_tab.tsx b/examples/expressions_example/public/components/render_tab.tsx index ff7ce2ac80fa..30342da63081 100644 --- a/examples/expressions_example/public/components/render_tab.tsx +++ b/examples/expressions_example/public/components/render_tab.tsx @@ -11,8 +11,7 @@ import { EuiFormRow, EuiSpacer, EuiSelect, - EuiText, - EuiTitle, + EuiCallOut, } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; @@ -27,23 +26,17 @@ export function RenderTab() { return ( <> - -

- -

-
- -

- -

-
+ + + Date: Wed, 6 Apr 2022 00:13:14 +0000 Subject: [PATCH 4/8] chore: Better expression playground messaging Signed-off-by: Ashwin Pc --- .../public/components/playground_section.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/examples/expressions_example/public/components/playground_section.tsx b/examples/expressions_example/public/components/playground_section.tsx index f90ca496aa6a..9cfa8660213b 100644 --- a/examples/expressions_example/public/components/playground_section.tsx +++ b/examples/expressions_example/public/components/playground_section.tsx @@ -14,11 +14,16 @@ import { EuiFieldText, EuiTitle, EuiProgress, + EuiCallOut, } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; -import React, { useEffect, useState } from 'react'; -import { ReactExpressionRenderer } from '../../../../src/plugins/expressions/public'; +import React, { useCallback, useEffect, useState } from 'react'; +import { + ReactExpressionRenderer, + ExpressionRenderError, + IInterpreterRenderHandlers, +} from '../../../../src/plugins/expressions/public'; import { useOpenSearchDashboards } from '../../../../src/plugins/opensearch_dashboards_react/public'; import { ExpressionsExampleServices } from '../types'; @@ -77,7 +82,7 @@ export function PlaygroundSection({

{title}

- + {/* Rendered the input field only for non renderable expressions */} {!renderType && ( <> @@ -113,7 +118,18 @@ export function PlaygroundSection({ />
{renderType ? ( - + <> + + + + ) : ( {loading && } From 44a69a68616a6b0542ec455828984e554573e7c9 Mon Sep 17 00:00:00 2001 From: Ashwin Pc Date: Wed, 6 Apr 2022 02:37:20 +0000 Subject: [PATCH 5/8] feat(Expressions): Adds expression explorer Signed-off-by: Ashwin Pc --- .../public/components/app.tsx | 18 +++- .../public/components/explorer_section.tsx | 98 +++++++++++++++++ .../public/components/explorer_tab.tsx | 102 ++++++++++++++++++ .../expressions_example/public/index.scss | 13 +++ examples/expressions_example/public/index.ts | 2 + 5 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 examples/expressions_example/public/components/explorer_section.tsx create mode 100644 examples/expressions_example/public/components/explorer_tab.tsx create mode 100644 examples/expressions_example/public/index.scss diff --git a/examples/expressions_example/public/components/app.tsx b/examples/expressions_example/public/components/app.tsx index 5baed595319c..ebfdd144716d 100644 --- a/examples/expressions_example/public/components/app.tsx +++ b/examples/expressions_example/public/components/app.tsx @@ -26,6 +26,7 @@ import { BasicTab } from './basic_tab'; import { RenderTab } from './render_tab'; import { ActionsTab } from './actions_tab'; import { PlaygroundTab } from './playground_tab'; +import { ExplorerTab } from './explorer_tab'; interface ExpressionsExampleAppDeps { basename: string; @@ -71,16 +72,27 @@ export const ExpressionsExampleApp = ({ basename }: ExpressionsExampleAppDeps) = content: , }, { - id: 'demo-4', + id: 'playground', name: ( ), content: , }, + { + id: 'explorer', + name: ( + + ), + content: , + }, ], [] ); @@ -114,7 +126,7 @@ export const ExpressionsExampleApp = ({ basename }: ExpressionsExampleAppDeps) = - + diff --git a/examples/expressions_example/public/components/explorer_section.tsx b/examples/expressions_example/public/components/explorer_section.tsx new file mode 100644 index 000000000000..e451385d4b17 --- /dev/null +++ b/examples/expressions_example/public/components/explorer_section.tsx @@ -0,0 +1,98 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiCallOut, + EuiDescriptionList, + EuiPanel, + EuiSpacer, + EuiTitle, + EuiBasicTable, + EuiText, + EuiBadge, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { FormattedMessage } from '@osd/i18n/react'; +import React, { useMemo } from 'react'; +import { ExpressionFunction } from '../../../../src/plugins/expressions'; + +interface Props { + fn: ExpressionFunction; +} + +export function ExplorerSection({ fn }: Props) { + const argumentItems = useMemo( + () => + Object.values(fn.args).map((arg) => ({ + name: arg.name, + default: arg.default, + types: String(arg.types), + required: arg.required, + help: arg.help, + })), + [fn] + ); + + return ( + + +

{fn.name}

+
+ +

+ {fn.help} + {fn.type && ( + + + {i18n.translate('expressionsExample.tab.explorer.section.type', { + defaultMessage: 'Type', + })} + + {fn.type} + + )} +

+
+ + + {/* arguments */} + +

+ +

+
+ +
+ ); +} diff --git a/examples/expressions_example/public/components/explorer_tab.tsx b/examples/expressions_example/public/components/explorer_tab.tsx new file mode 100644 index 000000000000..57a688a04929 --- /dev/null +++ b/examples/expressions_example/public/components/explorer_tab.tsx @@ -0,0 +1,102 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiCallOut, + EuiFieldSearch, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiSelect, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { FormattedMessage } from '@osd/i18n/react'; +import React, { useMemo, useState } from 'react'; +import { useOpenSearchDashboards } from '../../../../src/plugins/opensearch_dashboards_react/public'; +import { ExpressionsExampleServices } from '../types'; +import { ExplorerSection } from './explorer_section'; + +export function ExplorerTab() { + const [search, setSearch] = useState(''); + const [filter, setFilter] = useState('all'); + const { + services: { expressions }, + } = useOpenSearchDashboards(); + + const functions = expressions.getFunctions(); + + const sections = useMemo( + () => + Object.values(functions) + .filter((fn) => fn.name.includes(search)) + .filter((fn) => (filter === 'all' ? true : fn.type === filter)) + .map((fn) => ), + [filter, functions, search] + ); + + const types = useMemo(() => { + const allTypes = new Set(Object.values(functions).map((fn) => fn.type)); + + // Catch all filter and remove + allTypes.delete(undefined); + allTypes.add('all'); + + return [...allTypes].map((type) => ({ text: type })); + }, [functions]); + + return ( + <> + + + + + + + + + + + setSearch(e.target.value)} + /> + + + + + setFilter(e.target.value)} + /> + + + + + + + {sections} + + ); +} diff --git a/examples/expressions_example/public/index.scss b/examples/expressions_example/public/index.scss new file mode 100644 index 000000000000..c32a562952a9 --- /dev/null +++ b/examples/expressions_example/public/index.scss @@ -0,0 +1,13 @@ +.expressions-demo { + .explorer_list .euiDescriptionList__title { + max-width: 400px; + } + + .explorer_section { + margin-bottom: $euiSizeL; + } + + .explorer_section_type > code { + margin-left: $euiSizeXS; + } +} diff --git a/examples/expressions_example/public/index.ts b/examples/expressions_example/public/index.ts index 16106a471156..b930fc76eb65 100644 --- a/examples/expressions_example/public/index.ts +++ b/examples/expressions_example/public/index.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import './index.scss'; + import { ExpressionsExamplePlugin } from './plugin'; // This exports static code and TypeScript types, From 9ce68cb7648429254d8dcec8367643e3d58acb77 Mon Sep 17 00:00:00 2001 From: Ashwin Pc Date: Wed, 6 Apr 2022 03:15:32 +0000 Subject: [PATCH 6/8] feat(Expressions): A better explorer Signed-off-by: Ashwin Pc --- .../action/quick_form_fn.ts | 2 +- .../public/components/explorer_section.tsx | 22 +---- .../public/components/explorer_tab.tsx | 85 ++++++++++++++++--- .../expressions_example/public/index.scss | 4 - 4 files changed, 75 insertions(+), 38 deletions(-) diff --git a/examples/expressions_example/common/expression_functions/action/quick_form_fn.ts b/examples/expressions_example/common/expression_functions/action/quick_form_fn.ts index f0466acb8b91..1eb4c59d0029 100644 --- a/examples/expressions_example/common/expression_functions/action/quick_form_fn.ts +++ b/examples/expressions_example/common/expression_functions/action/quick_form_fn.ts @@ -21,7 +21,7 @@ export const quickFormFn = (): ExpressionFunctionDefinition< name: 'quick-form', type: 'render', help: i18n.translate('expressionsExample.function.avatar.help', { - defaultMessage: 'Avatar expression function', + defaultMessage: 'Render a simple form that sends the value back as an event on click', }), args: { label: { diff --git a/examples/expressions_example/public/components/explorer_section.tsx b/examples/expressions_example/public/components/explorer_section.tsx index e451385d4b17..b6b04efc61d4 100644 --- a/examples/expressions_example/public/components/explorer_section.tsx +++ b/examples/expressions_example/public/components/explorer_section.tsx @@ -37,28 +37,8 @@ export function ExplorerSection({ fn }: Props) { return ( - -

{fn.name}

-
- -

- {fn.help} - {fn.type && ( - - - {i18n.translate('expressionsExample.tab.explorer.section.type', { - defaultMessage: 'Type', - })} - - {fn.type} - - )} -

-
- - {/* arguments */} - +

(); + const [search, setSearch] = useState(''); + const [filter, setFilter] = useState('all'); + const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState({}); const functions = expressions.getFunctions(); - const sections = useMemo( - () => - Object.values(functions) - .filter((fn) => fn.name.includes(search)) - .filter((fn) => (filter === 'all' ? true : fn.type === filter)) - .map((fn) => ), - [filter, functions, search] - ); - const types = useMemo(() => { const allTypes = new Set(Object.values(functions).map((fn) => fn.type)); @@ -48,6 +49,30 @@ export function ExplorerTab() { return [...allTypes].map((type) => ({ text: type })); }, [functions]); + const items = useMemo( + () => + Object.values(functions) + .filter((fn) => fn.name.includes(search)) + .filter((fn) => (filter === 'all' ? true : fn.type === filter)) + .map((fn) => ({ + name: fn.name, + type: fn.type, + help: fn.help, + })), + [filter, functions, search] + ); + + const toggleDetails = (item: ExpressionFunctionItem) => { + const { name: id } = item; + const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; + if (itemIdToExpandedRowMapValues[id]) { + delete itemIdToExpandedRowMapValues[id]; + } else { + itemIdToExpandedRowMapValues[id] = ; + } + setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); + }; + return ( <> @@ -96,7 +121,43 @@ export function ExplorerTab() { - {sections} + ( + toggleDetails(item)} + aria-label={itemIdToExpandedRowMap[item.name] ? 'Collapse' : 'Expand'} + iconType={itemIdToExpandedRowMap[item.name] ? 'arrowUp' : 'arrowDown'} + /> + ), + }, + ]} + items={items} + /> + + {/* {sections} */} ); } diff --git a/examples/expressions_example/public/index.scss b/examples/expressions_example/public/index.scss index c32a562952a9..551059ff5fff 100644 --- a/examples/expressions_example/public/index.scss +++ b/examples/expressions_example/public/index.scss @@ -3,10 +3,6 @@ max-width: 400px; } - .explorer_section { - margin-bottom: $euiSizeL; - } - .explorer_section_type > code { margin-left: $euiSizeXS; } From 346626a5bcb7a6c5fe94bc45a954ae45e356e593 Mon Sep 17 00:00:00 2001 From: Ashwin Pc Date: Wed, 6 Apr 2022 03:24:48 +0000 Subject: [PATCH 7/8] fix(Expressions): Fix basic demo abort error Signed-off-by: Ashwin Pc --- .../public/components/app.tsx | 2 +- .../public/components/basic_tab.tsx | 44 ++++++++++++++----- .../public/components/playground_section.tsx | 4 +- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/examples/expressions_example/public/components/app.tsx b/examples/expressions_example/public/components/app.tsx index ebfdd144716d..99db0d40765f 100644 --- a/examples/expressions_example/public/components/app.tsx +++ b/examples/expressions_example/public/components/app.tsx @@ -126,7 +126,7 @@ export const ExpressionsExampleApp = ({ basename }: ExpressionsExampleAppDeps) = - + diff --git a/examples/expressions_example/public/components/basic_tab.tsx b/examples/expressions_example/public/components/basic_tab.tsx index 221bf2e90919..06005b7ca8d6 100644 --- a/examples/expressions_example/public/components/basic_tab.tsx +++ b/examples/expressions_example/public/components/basic_tab.tsx @@ -12,6 +12,7 @@ import { EuiStat, EuiFormLabel, EuiCallOut, + EuiProgress, } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; @@ -23,21 +24,41 @@ export function BasicTab() { const { services: { expressions }, } = useOpenSearchDashboards(); - const [value, setValue] = useState(2); + const [input, setInput] = useState(2); + const [loading, setLoading] = useState(false); const [result, setResult] = useState( i18n.translate('expressionsExample.tab.demo1.loading', { defaultMessage: 'Still sleeping', }) ); - const expressionString = `sleep time=2000 | square`; + const expression = `sleep time=2000 | square`; useEffect(() => { - const execution = expressions.execute(expressionString, value); - execution.getData().then((data) => { - setResult(data); - }); - return () => execution.cancel(); - }, [expressionString, expressions, value]); + let isMounted = true; + + try { + setLoading(true); + const execution = expressions.execute(expression, input); + execution.getData().then((data: any) => { + if (!isMounted) return; + + const value = + data?.type === 'error' + ? `Error: ${data?.error?.message ?? 'Something went wrong'}` + : data; + + setLoading(false); + setResult(String(value)); + }); + } catch (error) { + setLoading(false); + setResult(String(error)); + } + + return () => { + isMounted = false; + }; + }, [expressions, input, expression]); return ( <> @@ -60,7 +81,7 @@ export function BasicTab() { defaultMessage: 'Expression Input', })} > - setValue(Number(e.target.value))} /> + setInput(Number(e.target.value))} /> @@ -70,7 +91,10 @@ export function BasicTab() { defaultMessage="Expression that we are running" /> - {expressionString} + + {loading && } + {expression} + Date: Wed, 20 Apr 2022 09:45:10 +0000 Subject: [PATCH 8/8] Updates readme and fixes rendering visualizations Signed-off-by: Ashwin Pc --- .../public/components/playground_section.tsx | 10 +- .../expressions_example/public/index.scss | 4 + src/plugins/expressions/README.md | 86 +++++++++++++----- .../expressions/expressions_example.png | Bin 0 -> 85702 bytes .../public/_expression_renderer.scss | 5 + .../public/react_expression_renderer.tsx | 3 +- src/plugins/expressions/public/render.ts | 2 +- 7 files changed, 79 insertions(+), 31 deletions(-) create mode 100644 src/plugins/expressions/expressions_example.png diff --git a/examples/expressions_example/public/components/playground_section.tsx b/examples/expressions_example/public/components/playground_section.tsx index 58a565659fa8..ce957eeb0845 100644 --- a/examples/expressions_example/public/components/playground_section.tsx +++ b/examples/expressions_example/public/components/playground_section.tsx @@ -18,12 +18,8 @@ import { } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; -import React, { useCallback, useEffect, useState } from 'react'; -import { - ReactExpressionRenderer, - ExpressionRenderError, - IInterpreterRenderHandlers, -} from '../../../../src/plugins/expressions/public'; +import React, { useEffect, useState } from 'react'; +import { ReactExpressionRenderer } from '../../../../src/plugins/expressions/public'; import { useOpenSearchDashboards } from '../../../../src/plugins/opensearch_dashboards_react/public'; import { ExpressionsExampleServices } from '../types'; @@ -130,7 +126,7 @@ export function PlaygroundSection({ iconType="help" /> - + ) : ( diff --git a/examples/expressions_example/public/index.scss b/examples/expressions_example/public/index.scss index 551059ff5fff..e33c1b1f588e 100644 --- a/examples/expressions_example/public/index.scss +++ b/examples/expressions_example/public/index.scss @@ -6,4 +6,8 @@ .explorer_section_type > code { margin-left: $euiSizeXS; } + + .playgroundRenderer { + height: 500px; // Visualizations require the container to have a valid width and height to render + } } diff --git a/src/plugins/expressions/README.md b/src/plugins/expressions/README.md index aca90ea25953..053344c8c28b 100644 --- a/src/plugins/expressions/README.md +++ b/src/plugins/expressions/README.md @@ -5,31 +5,75 @@ string for you, as well as a series of registries for advanced users who might want to incorporate their own functions, types, and renderers into the service for use in their own application. -Expression pipeline is a chain of functions that _pipe_ its output to the -input of the next function. Functions can be configured using arguments provided -by the user. The final output of the expression pipeline can be rendered using -one of the _renderers_ registered in `expressions` plugin. +`Expressions` is a simple custom language designed to write a chain of functions that _pipe_ its output to the +input of the next function. When two or more such functions are chained together, it is an expressions pipeline. Since it is a custom language, any expression can be represented as a string. Functions can be configured using arguments provided. The final output of the expression pipeline can either be rendered using +one of the _renderers_ registered in `expressions` plugin or made to output the result of the final function in the chain. -Expressions power visualizations in Dashboard and Lens, as well as, every -_element_ in Canvas is backed by an expression. +> It is not necessary to chain functions and a single function can be used in isolation. -Below is an example of one Canvas element that fetches data using `opensearchsql` function, -pipes it further to `math` and `metric` functions, and final `render` function -renders the result. +Expressions power visualizations in Dashboard. +Below is an example of an expression that renders a metric visualization that aggregates the average value for the field `AvgTicketPrice` in the index. It does so by first fetching the opensearch dashboards global context, pipes it into the `opensearchaggs` function that fetches the aggregate data and pipes its result to the `metricVis` function that renders a metric visualization for the data. + +``` +opensearchDashboards +| opensearchaggs + index='d3d7af60-4c81-11e8-b3d7-01146121b73d' + aggConfigs='[{"id":"1","type":"avg","params":{"field":"AvgTicketPrice","customLabel":"Avg. Ticket Price"}}]' +| metricVis + metric={visdimension accessor=0 format='number'} +``` + +![image](./expressions_example.png) + +## Anatomy of an expression + +Consider the example below where the expression performs the following. It takes an input, sleeps for 2000ms and then returns the square of the input as its final output + +``` +sleep time=2000 | square +``` + +**Note:** The above example expression functions are only available with the `--run-examples` flag + +The whole string is an expression. `sleep` and `square` are expression functions registered with the expression plugin. `time=2000` is the argument passed to the `sleep` funciton with the value `2000`. `|` is used to denote pipe between the two functions. Every expression can take an input. In the example above, the input provided will be passed on by the sleep function to the square function. + +## Using Expressions + +### Execute Expressions + +One of the two ways an expressions can be used is to execute an expression to return a value. This can be done using the `expressions.execute` or `expressions.run` command. The primary difference being that the `execute` method returns an `ExecutionContract` that tracks the progress of the execution and can be used to interact with the expression. + +```js +const expression = `sleep time=2000 | square`; +const execution = expressions.execute(expression, input); ``` -filters -| opensearchsql - query="SELECT COUNT(timestamp) as total_errors - FROM opensearch_dashboards_sample_data_logs - WHERE tags LIKE '%warning%' OR tags LIKE '%error%'" -| math "total_errors" -| metric "TOTAL ISSUES" - metricFont={font family="'Open Sans', Helvetica, Arial, sans-serif" size=48 align="left" color="#FFFFFF" weight="normal" underline=false italic=false} - labelFont={font family="'Open Sans', Helvetica, Arial, sans-serif" size=30 align="left" color="#FFFFFF" weight="lighter" underline=false italic=false} -| render + +**Note:** The above example expression functions are only available with the `--run-examples` flag + +### Rendering Expressions + +The other way an expression can be used is to render an output using one of the _renderers_ registered in `expressions` plugin. This can be done using a few ways, the easiest of which is to use the `ReactExpressionRenderer` component. + +```jsx +const expressionString = `avatar name="OpenSearch Dashboards" size="xl"`; + +``` + +**Note:** The above example expression functions are only available with the `--run-examples` flag + +## Custom expressions + +Users can extend the service to incorporate their own functions, types, and renderers. Examples of these can be found in `./examples/expressions_example/common/expression_functions` and can be registered using the `registerFunction`, `registertype` and `registerRenderer` api's from the expression setup contract. + +## Playground + +Working with expressions can sometimes be a little tricky. To make this easier we have an example plugin with some examples, a playground to run your own expression functions and explorer to view all the registered expression functions and their propoerties. It can be started up using the `--run-examples` flag and found under the `Developer examples` option in the main menu. + +```sh +yarn start --run-examples ``` -![image](https://user-images.githubusercontent.com/9773803/74162514-3250a880-4c21-11ea-9e68-86f66862a183.png) -[See Canvas documentation about expressions](https://opensearch.org/docs/latest/dashboards/index/). + + diff --git a/src/plugins/expressions/expressions_example.png b/src/plugins/expressions/expressions_example.png new file mode 100644 index 0000000000000000000000000000000000000000..9850f25aba873f6f825645902e086740bc2466a2 GIT binary patch literal 85702 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 H_K*G-&vc3; literal 0 HcmV?d00001 diff --git a/src/plugins/expressions/public/_expression_renderer.scss b/src/plugins/expressions/public/_expression_renderer.scss index 4f030384ed88..c372f3b3bf2a 100644 --- a/src/plugins/expressions/public/_expression_renderer.scss +++ b/src/plugins/expressions/public/_expression_renderer.scss @@ -8,8 +8,13 @@ } .expExpressionRenderer__expression { + display: flex; + flex-direction: column; width: 100%; height: 100%; + overflow: auto; + position: relative; + flex: 1 1 100%; } .expExpressionRenderer-isEmpty, diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index 6d6760756c51..838fa703a750 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -184,10 +184,9 @@ export const ReactExpressionRenderer = ({ } }, [state.error]); - const classes = classNames('expExpressionRenderer', { + const classes = classNames('expExpressionRenderer', className, { 'expExpressionRenderer-isEmpty': state.isEmpty, 'expExpressionRenderer-hasError': !!state.error, - className, }); const expressionStyles: React.CSSProperties = {}; diff --git a/src/plugins/expressions/public/render.ts b/src/plugins/expressions/public/render.ts index c009707f6d42..f743c37e911b 100644 --- a/src/plugins/expressions/public/render.ts +++ b/src/plugins/expressions/public/render.ts @@ -106,7 +106,7 @@ export class ExpressionRenderHandler { }; } - render = async (data: any, uiState: any = {}) => { + render = async (data: any, uiState?: any) => { if (!data || typeof data !== 'object') { return this.handleRenderError(new Error('invalid data provided to the expression renderer')); }