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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