From a2e05d598bb0d68dfa9f8644bca23d8a5ee42093 Mon Sep 17 00:00:00 2001 From: Ashwin P Chandran Date: Tue, 23 Nov 2021 11:21:56 -0800 Subject: [PATCH 1/7] Initial Drag and Drop plugin code (#946) * Initial Drag and Drop plugin code Signed-off-by: Ashwin Pc --- src/plugins/wizard/.i18nrc.json | 7 + src/plugins/wizard/README.md | 11 ++ src/plugins/wizard/common/index.ts | 9 ++ .../common/wizard_saved_object_attributes.ts | 14 ++ src/plugins/wizard/opensearch_dashboards.json | 16 +++ .../wizard/public/application/_variables.scss | 3 + .../wizard/public/application/app.scss | 13 ++ src/plugins/wizard/public/application/app.tsx | 54 ++++++++ .../public/application/components/_util.scss | 8 ++ .../components/data_tab/config_panel.scss | 9 ++ .../components/data_tab/config_panel.tsx | 43 ++++++ .../components/data_tab/config_section.scss | 23 ++++ .../components/data_tab/config_section.tsx | 62 +++++++++ .../components/data_tab/field_selector.scss | 10 ++ .../components/data_tab/field_selector.tsx | 113 +++++++++++++++ .../data_tab/field_selector_field.scss | 12 ++ .../data_tab/field_selector_field.tsx | 85 ++++++++++++ .../components/data_tab/index.scss | 7 + .../application/components/data_tab/index.tsx | 24 ++++ .../application/components/side_nav.scss | 18 +++ .../application/components/side_nav.tsx | 110 +++++++++++++++ .../application/components/style_tab.tsx | 10 ++ .../application/components/top_nav.scss | 4 + .../public/application/components/top_nav.tsx | 41 ++++++ .../application/components/workspace.scss | 12 ++ .../application/components/workspace.tsx | 37 +++++ .../wizard/public/application/index.tsx | 30 ++++ .../application/utils/async_search/index.ts | 48 +++++++ .../utils/drag_drop/drag_drop_context.tsx | 113 +++++++++++++++ .../application/utils/drag_drop/index.ts | 6 + .../application/utils/get_top_nav_config.tsx | 129 ++++++++++++++++++ src/plugins/wizard/public/index.ts | 13 ++ src/plugins/wizard/public/plugin.ts | 55 ++++++++ src/plugins/wizard/public/types.ts | 29 ++++ .../wizard/public/visualizations/xy_chart.tsx | 6 + src/plugins/wizard/server/index.ts | 16 +++ src/plugins/wizard/server/plugin.ts | 44 ++++++ src/plugins/wizard/server/routes/index.ts | 22 +++ .../wizard/server/saved_objects/index.ts | 6 + .../wizard/server/saved_objects/wizard_app.ts | 36 +++++ src/plugins/wizard/server/types.ts | 9 ++ 41 files changed, 1317 insertions(+) create mode 100644 src/plugins/wizard/.i18nrc.json create mode 100755 src/plugins/wizard/README.md create mode 100644 src/plugins/wizard/common/index.ts create mode 100644 src/plugins/wizard/common/wizard_saved_object_attributes.ts create mode 100644 src/plugins/wizard/opensearch_dashboards.json create mode 100644 src/plugins/wizard/public/application/_variables.scss create mode 100644 src/plugins/wizard/public/application/app.scss create mode 100644 src/plugins/wizard/public/application/app.tsx create mode 100644 src/plugins/wizard/public/application/components/_util.scss create mode 100644 src/plugins/wizard/public/application/components/data_tab/config_panel.scss create mode 100644 src/plugins/wizard/public/application/components/data_tab/config_panel.tsx create mode 100644 src/plugins/wizard/public/application/components/data_tab/config_section.scss create mode 100644 src/plugins/wizard/public/application/components/data_tab/config_section.tsx create mode 100644 src/plugins/wizard/public/application/components/data_tab/field_selector.scss create mode 100644 src/plugins/wizard/public/application/components/data_tab/field_selector.tsx create mode 100644 src/plugins/wizard/public/application/components/data_tab/field_selector_field.scss create mode 100644 src/plugins/wizard/public/application/components/data_tab/field_selector_field.tsx create mode 100644 src/plugins/wizard/public/application/components/data_tab/index.scss create mode 100644 src/plugins/wizard/public/application/components/data_tab/index.tsx create mode 100644 src/plugins/wizard/public/application/components/side_nav.scss create mode 100644 src/plugins/wizard/public/application/components/side_nav.tsx create mode 100644 src/plugins/wizard/public/application/components/style_tab.tsx create mode 100644 src/plugins/wizard/public/application/components/top_nav.scss create mode 100644 src/plugins/wizard/public/application/components/top_nav.tsx create mode 100644 src/plugins/wizard/public/application/components/workspace.scss create mode 100644 src/plugins/wizard/public/application/components/workspace.tsx create mode 100644 src/plugins/wizard/public/application/index.tsx create mode 100644 src/plugins/wizard/public/application/utils/async_search/index.ts create mode 100644 src/plugins/wizard/public/application/utils/drag_drop/drag_drop_context.tsx create mode 100644 src/plugins/wizard/public/application/utils/drag_drop/index.ts create mode 100644 src/plugins/wizard/public/application/utils/get_top_nav_config.tsx create mode 100644 src/plugins/wizard/public/index.ts create mode 100644 src/plugins/wizard/public/plugin.ts create mode 100644 src/plugins/wizard/public/types.ts create mode 100644 src/plugins/wizard/public/visualizations/xy_chart.tsx create mode 100644 src/plugins/wizard/server/index.ts create mode 100644 src/plugins/wizard/server/plugin.ts create mode 100644 src/plugins/wizard/server/routes/index.ts create mode 100644 src/plugins/wizard/server/saved_objects/index.ts create mode 100644 src/plugins/wizard/server/saved_objects/wizard_app.ts create mode 100644 src/plugins/wizard/server/types.ts diff --git a/src/plugins/wizard/.i18nrc.json b/src/plugins/wizard/.i18nrc.json new file mode 100644 index 00000000000..2b511494a46 --- /dev/null +++ b/src/plugins/wizard/.i18nrc.json @@ -0,0 +1,7 @@ +{ + "prefix": "wizard", + "paths": { + "wizard": "." + }, + "translations": ["translations/ja-JP.json"] +} diff --git a/src/plugins/wizard/README.md b/src/plugins/wizard/README.md new file mode 100755 index 00000000000..bcb362b374c --- /dev/null +++ b/src/plugins/wizard/README.md @@ -0,0 +1,11 @@ +# wizard + +A OpenSearch Dashboards 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/src/plugins/wizard/common/index.ts b/src/plugins/wizard/common/index.ts new file mode 100644 index 00000000000..4b3522fec70 --- /dev/null +++ b/src/plugins/wizard/common/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const PLUGIN_ID = 'wizard'; +export const PLUGIN_NAME = 'Wizard'; + +export { WizardSavedObjectAttributes, WIZARD_SAVED_OBJECT } from './wizard_saved_object_attributes'; diff --git a/src/plugins/wizard/common/wizard_saved_object_attributes.ts b/src/plugins/wizard/common/wizard_saved_object_attributes.ts new file mode 100644 index 00000000000..ff6c12417d2 --- /dev/null +++ b/src/plugins/wizard/common/wizard_saved_object_attributes.ts @@ -0,0 +1,14 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SavedObjectAttributes } from 'opensearch-dashboards/public'; + +export const WIZARD_SAVED_OBJECT = 'wizard'; + +export interface WizardSavedObjectAttributes extends SavedObjectAttributes { + title: string; + description?: string; + state: string; +} diff --git a/src/plugins/wizard/opensearch_dashboards.json b/src/plugins/wizard/opensearch_dashboards.json new file mode 100644 index 00000000000..93eb9d9a040 --- /dev/null +++ b/src/plugins/wizard/opensearch_dashboards.json @@ -0,0 +1,16 @@ +{ + "id": "wizard", + "version": "1.0.0", + "opensearchDashboardsVersion": "opensearchDashboards", + "server": true, + "ui": true, + "requiredPlugins": [ + "navigation", + "data", + "opensearchDashboardsReact", + "savedObjects", + "embeddable", + "dashboard" + ], + "optionalPlugins": [] +} diff --git a/src/plugins/wizard/public/application/_variables.scss b/src/plugins/wizard/public/application/_variables.scss new file mode 100644 index 00000000000..c1b3646e8e4 --- /dev/null +++ b/src/plugins/wizard/public/application/_variables.scss @@ -0,0 +1,3 @@ +@import '@elastic/eui/src/global_styling/variables/header'; + +$osdHeaderOffset: $euiHeaderHeightCompensation * 2; \ No newline at end of file diff --git a/src/plugins/wizard/public/application/app.scss b/src/plugins/wizard/public/application/app.scss new file mode 100644 index 00000000000..2e1e93f4431 --- /dev/null +++ b/src/plugins/wizard/public/application/app.scss @@ -0,0 +1,13 @@ +@import "variables"; + +.wizLayout { + padding: 0; + display: grid; + grid-template-rows: min-content 1fr; + grid-template-columns: 420px 1fr; + grid-template-areas: + "topNav topNav" + "sideNav workspace" + ; + height: calc(100vh - #{$osdHeaderOffset}); // TODO: update 190px to correct offset variable +} diff --git a/src/plugins/wizard/public/application/app.tsx b/src/plugins/wizard/public/application/app.tsx new file mode 100644 index 00000000000..37244f16ca5 --- /dev/null +++ b/src/plugins/wizard/public/application/app.tsx @@ -0,0 +1,54 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState, useEffect } from 'react'; +import { I18nProvider } from '@osd/i18n/react'; +import { EuiPage } from '@elastic/eui'; +import { DataPublicPluginStart, IndexPattern } from '../../../data/public'; +import { SideNav } from './components/side_nav'; +import { DragDropProvider } from './utils/drag_drop/drag_drop_context'; +import { useOpenSearchDashboards } from '../../../opensearch_dashboards_react/public'; +import { WizardServices } from '../types'; +import { Workspace } from './components/workspace'; + +import './app.scss'; +import { TopNav } from './components/top_nav'; + +export const WizardApp = () => { + const { + services: { data }, + } = useOpenSearchDashboards(); + + const [indexPattern, setIndexPattern] = useIndexPattern(data); + + // Render the application DOM. + return ( + + + + + + + + + + ); +}; + +// TODO: Temporary. Need to update it fetch the index pattern cohesively +function useIndexPattern(data: DataPublicPluginStart) { + const [indexPattern, setIndexPattern] = useState(null); + useEffect(() => { + const fetchIndexPattern = async () => { + const defaultIndexPattern = await data.indexPatterns.getDefault(); + if (defaultIndexPattern) { + setIndexPattern(defaultIndexPattern); + } + }; + fetchIndexPattern(); + }, [data.indexPatterns]); + + return [indexPattern, setIndexPattern] as const; +} diff --git a/src/plugins/wizard/public/application/components/_util.scss b/src/plugins/wizard/public/application/components/_util.scss new file mode 100644 index 00000000000..9a444c1fe09 --- /dev/null +++ b/src/plugins/wizard/public/application/components/_util.scss @@ -0,0 +1,8 @@ +@mixin scrollNavParent ($template-row: none) { + display: grid; + min-height: 0; + + @if $template-row != 'none' { + grid-template-rows: $template-row; + } +} \ No newline at end of file diff --git a/src/plugins/wizard/public/application/components/data_tab/config_panel.scss b/src/plugins/wizard/public/application/components/data_tab/config_panel.scss new file mode 100644 index 00000000000..7477dcfca81 --- /dev/null +++ b/src/plugins/wizard/public/application/components/data_tab/config_panel.scss @@ -0,0 +1,9 @@ +.wizConfigPanel { + background: #f0f1f3; + border-left: $euiBorderThin; + padding: $euiSizeS; +} + +.wizConfigPanel__title { + margin-left: $euiSizeS; +} diff --git a/src/plugins/wizard/public/application/components/data_tab/config_panel.tsx b/src/plugins/wizard/public/application/components/data_tab/config_panel.tsx new file mode 100644 index 00000000000..262fd4c06e4 --- /dev/null +++ b/src/plugins/wizard/public/application/components/data_tab/config_panel.tsx @@ -0,0 +1,43 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiForm, EuiTitle } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@osd/i18n'; +import { BUCKET_TYPES, METRIC_TYPES } from '../../../../../data/public'; +import { ConfigSection } from './config_section'; + +import './config_panel.scss'; + +// TODO: Temp. Remove once visualizations can be refgistered and editor configs can be passed along +const CONFIG = { + x: { + title: 'X Axis', + allowedAggregation: BUCKET_TYPES.TERMS, + }, + y: { + title: 'Y Axis', + allowedAggregation: METRIC_TYPES.AVG, + }, +}; + +export function ConfigPanel() { + const sections = CONFIG; + + return ( + + +

+ {i18n.translate('wizard.nav.dataTab.configPanel.title', { + defaultMessage: 'Configuration', + })} +

+
+ {Object.entries(sections).map(([sectionId, sectionProps], index) => ( + {}} /> + ))} +
+ ); +} diff --git a/src/plugins/wizard/public/application/components/data_tab/config_section.scss b/src/plugins/wizard/public/application/components/data_tab/config_section.scss new file mode 100644 index 00000000000..79d0d3a913f --- /dev/null +++ b/src/plugins/wizard/public/application/components/data_tab/config_section.scss @@ -0,0 +1,23 @@ +.wizConfigSection { + margin-top: $euiSize; + border-bottom: $euiBorderThin; + padding-bottom: $euiSize; + + &:last-child { + border-bottom: none; + } + + & .euiFormRow__labelWrapper { + margin-left: $euiSizeS; + } +} + +.wizConfigSection__dropTarget { + @include euiSlightShadow; + background: $euiColorEmptyShade; + border: $euiBorderThin; + box-shadow: 0px 2px 2px rgba(152, 162, 179, 0.15); + border-radius: $euiBorderRadius; + padding: $euiSizeS $euiSizeM; + color: $euiColorDarkShade; +} diff --git a/src/plugins/wizard/public/application/components/data_tab/config_section.tsx b/src/plugins/wizard/public/application/components/data_tab/config_section.tsx new file mode 100644 index 00000000000..88511756222 --- /dev/null +++ b/src/plugins/wizard/public/application/components/data_tab/config_section.tsx @@ -0,0 +1,62 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiButtonIcon, EuiFormRow, EuiPanel, EuiText } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import React, { useCallback, useEffect, useState } from 'react'; +import { IndexPatternField } from 'src/plugins/data/common'; +import { useDrop } from '../../utils/drag_drop'; + +import './config_section.scss'; + +interface ConfigSectionProps { + id: string; + title: string; + onChange: Function; +} + +export const ConfigSection = ({ title, id, onChange }: ConfigSectionProps) => { + const [currentField, setCurrentField] = useState(); + + const dropHandler = useCallback((field: IndexPatternField) => { + setCurrentField(field); + }, []); + const [dropProps, { isValidDropTarget, dragData }] = useDrop('dataPlane', dropHandler); + + const dropTargetString = dragData + ? dragData.type + : i18n.translate('wizard.nav.dataTab.configPanel.dropTarget.placeholder', { + defaultMessage: 'Click or drop to add', + }); + + useEffect(() => { + onChange(id, currentField); + }, [id, currentField, onChange]); + + return ( + + {currentField ? ( + + + {currentField.displayName} + + setCurrentField(undefined)} + /> + + ) : ( +
+ {dropTargetString} +
+ )} +
+ ); +}; diff --git a/src/plugins/wizard/public/application/components/data_tab/field_selector.scss b/src/plugins/wizard/public/application/components/data_tab/field_selector.scss new file mode 100644 index 00000000000..c05f75457b0 --- /dev/null +++ b/src/plugins/wizard/public/application/components/data_tab/field_selector.scss @@ -0,0 +1,10 @@ +@import "../util"; + +.wizFieldSelector { + @include scrollNavParent(auto 1fr); + padding: $euiSizeS; +} + +.wizFieldSelector__fieldGroups { + overflow-y: auto; +} diff --git a/src/plugins/wizard/public/application/components/data_tab/field_selector.tsx b/src/plugins/wizard/public/application/components/data_tab/field_selector.tsx new file mode 100644 index 00000000000..53d38594496 --- /dev/null +++ b/src/plugins/wizard/public/application/components/data_tab/field_selector.tsx @@ -0,0 +1,113 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; + +import { + EuiFormLabel, + EuiFlexItem, + EuiAccordion, + EuiSpacer, + EuiNotificationBadge, + EuiTitle, +} from '@elastic/eui'; + +import { + IndexPatternField, + OPENSEARCH_FIELD_TYPES, + OSD_FIELD_TYPES, +} from '../../../../../data/public'; +import { FieldSelectorField } from './field_selector_field'; + +import './field_selector.scss'; + +interface FieldSelectorDeps { + indexFields: IndexPatternField[]; +} + +interface IFieldCategories { + categorical: IndexPatternField[]; + numerical: IndexPatternField[]; + meta: IndexPatternField[]; +} + +const META_FIELDS: string[] = [ + OPENSEARCH_FIELD_TYPES._ID, + OPENSEARCH_FIELD_TYPES._INDEX, + OPENSEARCH_FIELD_TYPES._SOURCE, + OPENSEARCH_FIELD_TYPES._TYPE, +]; + +export const FieldSelector = ({ indexFields }: FieldSelectorDeps) => { + const fields = indexFields?.reduce( + (fieldGroups, currentField) => { + const category = getFieldCategory(currentField); + fieldGroups[category].push(currentField); + + return fieldGroups; + }, + { + categorical: [], + numerical: [], + meta: [], + } + ); + + return ( +
+
+ TODO: Search goes here +
+
+ + + +
+
+ ); +}; + +interface FieldGroupProps { + fields?: IndexPatternField[]; + header: string; + id: string; +} + +const FieldGroup = ({ fields, header, id }: FieldGroupProps) => ( + <> + + {header} + + } + extraAction={ + + {fields?.length || 0} + + } + initialIsOpen + > + {fields?.map((field, i) => ( + + + + ))} + + + +); + +function getFieldCategory(field: IndexPatternField): keyof IFieldCategories { + if (META_FIELDS.includes(field.name)) return 'meta'; + if (field.type === OSD_FIELD_TYPES.NUMBER) return 'numerical'; + + return 'categorical'; +} diff --git a/src/plugins/wizard/public/application/components/data_tab/field_selector_field.scss b/src/plugins/wizard/public/application/components/data_tab/field_selector_field.scss new file mode 100644 index 00000000000..0ace9a914b3 --- /dev/null +++ b/src/plugins/wizard/public/application/components/data_tab/field_selector_field.scss @@ -0,0 +1,12 @@ +.wizFieldSelectorField { + @include euiBottomShadowSmall; + padding: $euiSizeXS; + background-color: $euiColorEmptyShade; + border: $euiBorderThin; + margin-top: $euiSizeS; + + & > button { + align-items: center; + gap: 4px; + } +} diff --git a/src/plugins/wizard/public/application/components/data_tab/field_selector_field.tsx b/src/plugins/wizard/public/application/components/data_tab/field_selector_field.tsx new file mode 100644 index 00000000000..396b89c0bb9 --- /dev/null +++ b/src/plugins/wizard/public/application/components/data_tab/field_selector_field.tsx @@ -0,0 +1,85 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import React, { useState } from 'react'; +import { IndexPatternField } from 'src/plugins/data/public'; +import { FieldButton, FieldIcon } from '../../../../../opensearch_dashboards_react/public'; +import { useDrag } from '../../utils/drag_drop/drag_drop_context'; + +import './field_selector_field.scss'; + +export interface FieldSelectorFieldProps { + field: IndexPatternField; +} + +// TODO: +// 1. Add field sections (Available fields, popular fields from src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx) +// 2. Add popover for fields stats from discover as well +export const FieldSelectorField = ({ field }: FieldSelectorFieldProps) => { + const [infoIsOpen, setOpen] = useState(false); + const [dragProps] = useDrag(field, `dataPlane`); + + function togglePopover() { + setOpen(!infoIsOpen); + } + + function wrapOnDot(str?: string) { + // u200B is a non-width white-space character, which allows + // the browser to efficiently word-wrap right after the dot + // without us having to draw a lot of extra DOM elements, etc + return str ? str.replace(/\./g, '.\u200B') : ''; + } + + const fieldName = ( + + {wrapOnDot(field.displayName)} + + ); + + return ( + } + // fieldAction={actionButton} + fieldName={fieldName} + {...dragProps} + /> + ); +}; diff --git a/src/plugins/wizard/public/application/components/data_tab/index.scss b/src/plugins/wizard/public/application/components/data_tab/index.scss new file mode 100644 index 00000000000..1ba02bcc987 --- /dev/null +++ b/src/plugins/wizard/public/application/components/data_tab/index.scss @@ -0,0 +1,7 @@ +@import "../util"; + +.wizDataTab { + @include scrollNavParent; + display: grid; + grid-template-columns: 50% 50%; +} diff --git a/src/plugins/wizard/public/application/components/data_tab/index.tsx b/src/plugins/wizard/public/application/components/data_tab/index.tsx new file mode 100644 index 00000000000..e7e5f02e6d0 --- /dev/null +++ b/src/plugins/wizard/public/application/components/data_tab/index.tsx @@ -0,0 +1,24 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { IndexPatternField } from '../../../../../data/public'; +import { FieldSelector } from './field_selector'; +import { ConfigPanel } from './config_panel'; + +import './index.scss'; + +interface DataTabDeps { + indexFields: IndexPatternField[]; +} + +export const DataTab = ({ indexFields }: DataTabDeps) => { + return ( +
+ + +
+ ); +}; diff --git a/src/plugins/wizard/public/application/components/side_nav.scss b/src/plugins/wizard/public/application/components/side_nav.scss new file mode 100644 index 00000000000..88ff7ffb0e4 --- /dev/null +++ b/src/plugins/wizard/public/application/components/side_nav.scss @@ -0,0 +1,18 @@ +@import "util"; + +.wizSidenav { + @include scrollNavParent(auto 1fr); + grid-area: sideNav; + border-right: $euiBorderThin; +} + +.wizDatasourceSelector { + padding: $euiSize $euiSize 0 $euiSize; +} + +.wizSidenavTabs { + @include scrollNavParent(min-content 1fr); + &>[role="tabpanel"] { + @include scrollNavParent; + } +} diff --git a/src/plugins/wizard/public/application/components/side_nav.tsx b/src/plugins/wizard/public/application/components/side_nav.tsx new file mode 100644 index 00000000000..a35c039e57f --- /dev/null +++ b/src/plugins/wizard/public/application/components/side_nav.tsx @@ -0,0 +1,110 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState, useEffect } from 'react'; +import { i18n } from '@osd/i18n'; + +import { + EuiFormLabel, + EuiFlexGroup, + EuiFlexItem, + EuiTabbedContent, + EuiTabbedContentTab, +} from '@elastic/eui'; + +import { IndexPattern, IndexPatternField, OSD_FIELD_TYPES } from '../../../../data/public'; +import { DataTab } from './data_tab'; +import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; +import { WizardServices } from '../../types'; +import { StyleTab } from './style_tab'; + +import './side_nav.scss'; + +interface SideNavDeps { + indexPattern: IndexPattern | null; + setIndexPattern: React.Dispatch>; +} + +const ALLOWED_FIELDS: string[] = [OSD_FIELD_TYPES.STRING, OSD_FIELD_TYPES.NUMBER]; + +export const SideNav = ({ indexPattern, setIndexPattern }: SideNavDeps) => { + const { + services: { + data, + savedObjects: { client: savedObjectsClient }, + }, + } = useOpenSearchDashboards(); + const { IndexPatternSelect } = data.ui; + const [indexFields, setIndexFields] = useState([]); + + // Fetch the default index pattern using the `data.indexPatterns` service, as the component is mounted. + useEffect(() => { + const setDefaultIndexPattern = async () => { + const defaultIndexPattern = await data.indexPatterns.getDefault(); + setIndexPattern(defaultIndexPattern); + }; + + setDefaultIndexPattern(); + }, [data, setIndexPattern]); + + // Update the fields list every time the index pattern is modified. + useEffect(() => { + const fields = indexPattern?.fields; + + setIndexFields(fields?.filter(isValidField) || []); + }, [indexPattern]); + + const tabs: EuiTabbedContentTab[] = [ + { + id: 'data-tab', + name: i18n.translate('wizard.nav.dataTab.title', { + defaultMessage: 'Data', + }), + content: , + }, + { + id: 'style-tab', + name: i18n.translate('wizard.nav.styleTab.title', { + defaultMessage: 'Style', + }), + content: , + }, + ]; + + return ( +
+
+ + {i18n.translate('wizard.nav.dataSource.selector.title', { + defaultMessage: 'Index Pattern', + })} + + { + const newIndexPattern = await data.indexPatterns.get(newIndexPatternId); + setIndexPattern(newIndexPattern); + }} + isClearable={false} + /> +
+ +
+ ); +}; + +// TODO: Temporary validate function +// Need to identify hopw to get fieldCounts to use the standard filter and group functions +function isValidField(field: IndexPatternField): boolean { + const isAggregatable = field.aggregatable === true; + const isNotScripted = !field.scripted; + const isAllowed = ALLOWED_FIELDS.includes(field.type); + + return isAggregatable && isNotScripted && isAllowed; +} diff --git a/src/plugins/wizard/public/application/components/style_tab.tsx b/src/plugins/wizard/public/application/components/style_tab.tsx new file mode 100644 index 00000000000..3d1eb0d98b3 --- /dev/null +++ b/src/plugins/wizard/public/application/components/style_tab.tsx @@ -0,0 +1,10 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; + +export const StyleTab = () => { + return
TODO: Layout styles come here.
; +}; diff --git a/src/plugins/wizard/public/application/components/top_nav.scss b/src/plugins/wizard/public/application/components/top_nav.scss new file mode 100644 index 00000000000..f8e1d1d6cfa --- /dev/null +++ b/src/plugins/wizard/public/application/components/top_nav.scss @@ -0,0 +1,4 @@ +.wizTopNav { + grid-area: topNav; + border-bottom: $euiBorderThin; +} \ No newline at end of file diff --git a/src/plugins/wizard/public/application/components/top_nav.tsx b/src/plugins/wizard/public/application/components/top_nav.tsx new file mode 100644 index 00000000000..e0c9d47efef --- /dev/null +++ b/src/plugins/wizard/public/application/components/top_nav.tsx @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useMemo, useState } from 'react'; +import { IndexPattern } from '../../../../data/public'; +import { PLUGIN_ID } from '../../../common'; +import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; +import { getTopNavconfig } from '../utils/get_top_nav_config'; +import { WizardServices } from '../../types'; + +import './top_nav.scss'; + +export const TopNav = () => { + const { services } = useOpenSearchDashboards(); + const { + setHeaderActionMenu, + navigation: { + ui: { TopNavMenu }, + }, + } = services; + + const config = useMemo(() => getTopNavconfig(services), [services]); + // TODO: Set index pattern/data source here. Filters wont show up until you do + const [indexPatterns, setIndexPatterns] = useState([]); + + return ( +
+ +
+ ); +}; diff --git a/src/plugins/wizard/public/application/components/workspace.scss b/src/plugins/wizard/public/application/components/workspace.scss new file mode 100644 index 00000000000..94a97e881bf --- /dev/null +++ b/src/plugins/wizard/public/application/components/workspace.scss @@ -0,0 +1,12 @@ +.wizWorkspace { + display: grid; + grid-template-rows: auto 1fr; + grid-area: workspace; + grid-gap: $euiSizeM; + padding: $euiSizeM; + background-color: $euiColorEmptyShade; +} + +.wizWorkspace__empty { + height: 100%; +} diff --git a/src/plugins/wizard/public/application/components/workspace.tsx b/src/plugins/wizard/public/application/components/workspace.tsx new file mode 100644 index 00000000000..a83201be4cc --- /dev/null +++ b/src/plugins/wizard/public/application/components/workspace.tsx @@ -0,0 +1,37 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import React, { FC } from 'react'; + +import './workspace.scss'; + +export const Workspace: FC = ({ children }) => { + return ( +
+ + + {/* TODO: This is the temporary view of the selected chard, should be replaced by dropdown */} + + Bar + + + + + {children ? ( + children + ) : ( + + Welcome to the wizard!} + body={

Drag some fields onto the panel to visualize some data.

} + /> +
+ )} +
+
+ ); +}; diff --git a/src/plugins/wizard/public/application/index.tsx b/src/plugins/wizard/public/application/index.tsx new file mode 100644 index 00000000000..b778d2e139d --- /dev/null +++ b/src/plugins/wizard/public/application/index.tsx @@ -0,0 +1,30 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { AppMountParameters } from '../../../../core/public'; +import { WizardServices } from '../types'; +import { WizardApp } from './app'; +import { OpenSearchDashboardsContextProvider } from '../../../opensearch_dashboards_react/public'; + +export const renderApp = ( + { appBasePath, element }: AppMountParameters, + services: WizardServices +) => { + ReactDOM.render( + + + + + + + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/src/plugins/wizard/public/application/utils/async_search/index.ts b/src/plugins/wizard/public/application/utils/async_search/index.ts new file mode 100644 index 00000000000..9746cde24e4 --- /dev/null +++ b/src/plugins/wizard/public/application/utils/async_search/index.ts @@ -0,0 +1,48 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { CreateAggConfigParams } from 'src/plugins/data/common'; +import { DataPublicPluginStart, IndexPattern } from 'src/plugins/data/public'; + +interface IDoAsyncSearch { + data: DataPublicPluginStart; + indexPattern: IndexPattern | null; + aggs?: CreateAggConfigParams[]; +} + +export const doAsyncSearch = async ({ data, indexPattern, aggs }: IDoAsyncSearch) => { + if (!indexPattern || !aggs || !aggs.length) return; + + // Constuct the query portion of the search request + const query = data.query.getOpenSearchQuery(indexPattern); + + // Constuct the aggregations portion of the search request by using the `data.search.aggs` service. + // const aggs = [{ type: 'avg', params: { field: field.name } }]; + // const aggs = [ + // { type: 'terms', params: { field: 'day_of_week' } }, + // { type: 'avg', params: { field: field.name } }, + // { type: 'terms', params: { field: 'customer_gender' } }, + // ]; + const aggConfigs = data.search.aggs.createAggConfigs(indexPattern, aggs); + const aggsDsl = aggConfigs.toDsl(); + + const request = { + params: { + index: indexPattern.title, + body: { + aggs: aggsDsl, + query, + }, + }, + }; + + // Submit the search request using the `data.search` service. + const { rawResponse } = await data.search.search(request).toPromise(); + + return { + rawResponse, + aggConfigs, + }; +}; diff --git a/src/plugins/wizard/public/application/utils/drag_drop/drag_drop_context.tsx b/src/plugins/wizard/public/application/utils/drag_drop/drag_drop_context.tsx new file mode 100644 index 00000000000..a89226885d5 --- /dev/null +++ b/src/plugins/wizard/public/application/utils/drag_drop/drag_drop_context.tsx @@ -0,0 +1,113 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { createContext, DragEvent, FC, ReactNode, useContext, useState } from 'react'; + +interface DrapDataType { + namespace: string; + value: any; +} + +// TODO: Replace any with corret type +// TODO: Split into separate files +interface IDragDropContext { + data?: DrapDataType; + setData?: any; + isDragging: boolean; + setIsDragging?: any; +} + +const defaultContextProps = { + isDragging: false, +}; + +const DragDropContext = createContext(defaultContextProps); + +const DragDropProvider: FC = ({ children }) => { + const [isDragging, setIsDragging] = useState(false); + const [data, setData] = useState(); + return ( + + {children} + + ); +}; + +const useDragDropContext = () => useContext(DragDropContext); + +const useDrag = (dragData: any, namespace: string) => { + const { setData, setIsDragging } = useDragDropContext(); + const dragElementProps = { + draggable: true, + onDragStart: (event: DragEvent) => { + setIsDragging(true); + setData({ + namespace, + value: dragData, + }); + }, + onDragEnd: (event: DragEvent) => { + setIsDragging(false); + setData(null); + }, + }; + return [dragElementProps]; +}; + +interface IDropAttributes { + onDragOver: (event: DragEvent) => void; + onDrop: (event: DragEvent) => void; + onDragEnter: (event: DragEvent) => void; + onDragLeave: (event: DragEvent) => void; +} + +interface IDropState { + isDragging: boolean; + canDrop: boolean; + isValidDropTarget: boolean; + dragData: any; +} +const useDrop = (namespace: string, onDropCallback: Function): [IDropAttributes, IDropState] => { + const { data, isDragging, setIsDragging, setData } = useDragDropContext(); + const [canDrop, setCanDrop] = useState(false); + + const dropAttributes: IDropAttributes = { + onDragOver: (event) => { + event.preventDefault(); + }, + onDrop: (event) => { + setIsDragging(false); + onDropCallback(data?.value); + setData(null); + }, + onDragEnter: (event) => { + if (data?.namespace === namespace) { + setCanDrop(true); + } + }, + onDragLeave: (event) => { + setCanDrop(false); + }, + }; + return [ + dropAttributes, + { + isDragging, + canDrop, + isValidDropTarget: isDragging && data?.namespace === namespace, + dragData: data?.value, + }, + ]; +}; + +export { DragDropContext, DragDropProvider, useDragDropContext, useDrag, useDrop }; diff --git a/src/plugins/wizard/public/application/utils/drag_drop/index.ts b/src/plugins/wizard/public/application/utils/drag_drop/index.ts new file mode 100644 index 00000000000..3799a2eb605 --- /dev/null +++ b/src/plugins/wizard/public/application/utils/drag_drop/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './drag_drop_context'; diff --git a/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx new file mode 100644 index 00000000000..4d7c10d49e7 --- /dev/null +++ b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx @@ -0,0 +1,129 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import React from 'react'; +import { i18n } from '@osd/i18n'; +import { TopNavMenuData } from '../../../../navigation/public'; +import { + OnSaveProps, + SavedObjectSaveModalOrigin, + showSaveModal, +} from '../../../../saved_objects/public'; +import { WizardServices } from '../..'; + +export const getTopNavconfig = ({ + savedObjects: { client: savedObjectsClient }, + toastNotifications, + i18n: { Context: I18nContext }, +}: WizardServices) => { + const topNavConfig: TopNavMenuData[] = [ + { + id: 'save', + iconType: 'save', + emphasize: true, + label: 'Save', + testId: 'wizardSaveButton', + run: (anchorElement) => { + const onSave = async ({ + // TODO: Figure out what the other props here do + newTitle, + newCopyOnSave, + isTitleDuplicateConfirmed, + onTitleDuplicate, + newDescription, + returnToOrigin, + }: OnSaveProps & { returnToOrigin: boolean }) => { + // TODO: Save the actual state of the wizard + const wizardSavedObject = await savedObjectsClient.create('wizard', { + title: newTitle, + description: newDescription, + state: JSON.stringify({}), + }); + + try { + const id = await wizardSavedObject.save(); + + if (id) { + toastNotifications.addSuccess({ + title: i18n.translate( + 'wizard.topNavMenu.saveVisualization.successNotificationText', + { + defaultMessage: `Saved '{visTitle}'`, + values: { + visTitle: newTitle, + }, + } + ), + 'data-test-subj': 'saveVisualizationSuccess', + }); + + return { id }; + } + + throw new Error('Saved but no id returned'); + } catch (error: any) { + // eslint-disable-next-line no-console + console.error(error); + + toastNotifications.addDanger({ + title: i18n.translate( + 'visualize.topNavMenu.saveVisualization.failureNotificationText', + { + defaultMessage: `Error on saving '{visTitle}'`, + values: { + visTitle: newTitle, + }, + } + ), + text: error.message, + 'data-test-subj': 'saveVisualizationError', + }); + return { error }; + } + }; + + const saveModal = ( + {}} + /> + ); + + showSaveModal(saveModal, I18nContext); + }, + }, + ]; + + return topNavConfig; +}; diff --git a/src/plugins/wizard/public/index.ts b/src/plugins/wizard/public/index.ts new file mode 100644 index 00000000000..884b83e7765 --- /dev/null +++ b/src/plugins/wizard/public/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { WizardPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, OpenSearch Dashboards Platform `plugin()` initializer. +export function plugin() { + return new WizardPlugin(); +} +export { WizardServices, WizardPluginStartDependencies } from './types'; diff --git a/src/plugins/wizard/public/plugin.ts b/src/plugins/wizard/public/plugin.ts new file mode 100644 index 00000000000..e8cbcbcac22 --- /dev/null +++ b/src/plugins/wizard/public/plugin.ts @@ -0,0 +1,55 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + AppMountParameters, + CoreSetup, + CoreStart, + DEFAULT_APP_CATEGORIES, + Plugin, +} from '../../../core/public'; +import { WizardPluginStartDependencies, WizardServices } from './types'; +import { PLUGIN_NAME } from '../common'; + +export class WizardPlugin implements Plugin { + public setup(core: CoreSetup) { + // Register an application into the side navigation menu + core.application.register({ + id: 'wizard', + title: PLUGIN_NAME, + euiIconType: 'inputOutput', + defaultPath: '#/', + category: DEFAULT_APP_CATEGORIES.opensearchDashboards, + async mount(params: AppMountParameters) { + // Load application bundle + const { renderApp } = await import('./application'); + // Get start services as specified in opensearch_dashboards.json + const [coreStart, pluginsStart] = await core.getStartServices(); + + const services: WizardServices = { + ...coreStart, + toastNotifications: coreStart.notifications.toasts, + data: pluginsStart.data, + savedObjectsPublic: pluginsStart.savedObjects, + navigation: pluginsStart.navigation, + setHeaderActionMenu: params.setHeaderActionMenu, + }; + + // make sure the index pattern list is up to date + pluginsStart.data.indexPatterns.clearCache(); + // make sure a default index pattern exists + // if not, the page will be redirected to management and visualize won't be rendered + await pluginsStart.data.indexPatterns.ensureDefaultIndexPattern(); + + // Render the application + return renderApp(params, services); + }, + }); + } + + public start(core: CoreStart) {} + + public stop() {} +} diff --git a/src/plugins/wizard/public/types.ts b/src/plugins/wizard/public/types.ts new file mode 100644 index 00000000000..f65246aeb1d --- /dev/null +++ b/src/plugins/wizard/public/types.ts @@ -0,0 +1,29 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SavedObjectsStart } from 'src/plugins/saved_objects/public'; +import { AppMountParameters, CoreStart, ToastsStart } from 'opensearch-dashboards/public'; +import { EmbeddableSetup } from 'src/plugins/embeddable/public'; +import { DashboardStart } from 'src/plugins/dashboard/public'; +import { NavigationPublicPluginStart } from '../../navigation/public'; +import { DataPublicPluginStart } from '../../data/public'; + +export interface WizardPluginSetupDependencies { + embeddable: EmbeddableSetup; +} +export interface WizardPluginStartDependencies { + navigation: NavigationPublicPluginStart; + data: DataPublicPluginStart; + savedObjects: SavedObjectsStart; + dashboard: DashboardStart; +} + +export interface WizardServices extends CoreStart { + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; + toastNotifications: ToastsStart; + savedObjectsPublic: SavedObjectsStart; + navigation: NavigationPublicPluginStart; + data: DataPublicPluginStart; +} diff --git a/src/plugins/wizard/public/visualizations/xy_chart.tsx b/src/plugins/wizard/public/visualizations/xy_chart.tsx new file mode 100644 index 00000000000..0215b31b2b4 --- /dev/null +++ b/src/plugins/wizard/public/visualizations/xy_chart.tsx @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +// TODO: This is where we register the visualizations using a registration service provided by the plugin diff --git a/src/plugins/wizard/server/index.ts b/src/plugins/wizard/server/index.ts new file mode 100644 index 00000000000..e995ea17b4a --- /dev/null +++ b/src/plugins/wizard/server/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { PluginInitializerContext } from '../../../core/server'; +import { WizardPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, OpenSearch Dashboards Platform `plugin()` initializer. + +export function plugin(initializerContext: PluginInitializerContext) { + return new WizardPlugin(initializerContext); +} + +export { WizardPluginSetup, WizardPluginStart } from './types'; diff --git a/src/plugins/wizard/server/plugin.ts b/src/plugins/wizard/server/plugin.ts new file mode 100644 index 00000000000..d45e4081cce --- /dev/null +++ b/src/plugins/wizard/server/plugin.ts @@ -0,0 +1,44 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + Logger, +} from '../../../core/server'; + +import { WizardPluginSetup, WizardPluginStart } from './types'; +import { defineRoutes } from './routes'; +import { wizardApp } from './saved_objects'; + +export class WizardPlugin implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup({ http, savedObjects }: CoreSetup) { + this.logger.debug('wizard: Setup'); + const router = http.createRouter(); + + // Register server side APIs + defineRoutes(router); + + // Register saved object types + savedObjects.registerType(wizardApp); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('wizard: Started'); + return {}; + } + + public stop() {} +} diff --git a/src/plugins/wizard/server/routes/index.ts b/src/plugins/wizard/server/routes/index.ts new file mode 100644 index 00000000000..f6268695e83 --- /dev/null +++ b/src/plugins/wizard/server/routes/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { IRouter } from '../../../../core/server'; + +export function defineRoutes(router: IRouter) { + router.get( + { + path: '/api/wizard/example', + validate: false, + }, + async (context, request, response) => { + return response.ok({ + body: { + time: new Date().toISOString(), + }, + }); + } + ); +} diff --git a/src/plugins/wizard/server/saved_objects/index.ts b/src/plugins/wizard/server/saved_objects/index.ts new file mode 100644 index 00000000000..aa90fcea911 --- /dev/null +++ b/src/plugins/wizard/server/saved_objects/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { wizardApp } from './wizard_app'; diff --git a/src/plugins/wizard/server/saved_objects/wizard_app.ts b/src/plugins/wizard/server/saved_objects/wizard_app.ts new file mode 100644 index 00000000000..138bea03b22 --- /dev/null +++ b/src/plugins/wizard/server/saved_objects/wizard_app.ts @@ -0,0 +1,36 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SavedObjectsType } from 'src/core/server'; +import { WIZARD_SAVED_OBJECT } from '../../common'; + +export const wizardApp: SavedObjectsType = { + name: WIZARD_SAVED_OBJECT, + hidden: false, + namespaceType: 'single', + management: { + icon: 'visVisualBuilder', // TODO: Need a custom icon here + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj: { attributes: { title: string } }) => obj.attributes.title, + // getInAppUrl: TODO: Enable once editing is supported + }, + migrations: {}, + mappings: { + properties: { + title: { + type: 'text', + }, + description: { + type: 'text', + }, + // TODO: Determine what needs to be pulled out of state and added directly into the mapping + state: { + type: 'text', + index: false, + }, + }, + }, +}; diff --git a/src/plugins/wizard/server/types.ts b/src/plugins/wizard/server/types.ts new file mode 100644 index 00000000000..5d26185a037 --- /dev/null +++ b/src/plugins/wizard/server/types.ts @@ -0,0 +1,9 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface WizardPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface WizardPluginStart {} From b7907a680dd0ed5eea7706a3b9a1b7aaba42bc48 Mon Sep 17 00:00:00 2001 From: Ashwin Pc Date: Fri, 3 Dec 2021 00:15:34 +0000 Subject: [PATCH 2/7] Adds state management to Drag and Drop Signed-off-by: Ashwin Pc --- package.json | 1 + src/plugins/wizard/public/application/app.tsx | 19 ++--- .../components/data_tab/config_panel.tsx | 20 +---- .../components/data_tab/config_section.tsx | 73 ++++++++++++------- .../components/data_tab/field_selector.tsx | 8 +- .../application/components/data_tab/index.tsx | 9 +-- .../application/components/side_nav.tsx | 56 +++----------- .../public/application/components/top_nav.tsx | 9 +-- .../wizard/public/application/index.tsx | 10 ++- .../utils/state_management/config_slice.ts | 62 ++++++++++++++++ .../state_management/datasource_slice.ts | 45 ++++++++++++ .../utils/state_management/hooks.ts | 11 +++ .../utils/state_management/index.ts | 7 ++ .../utils/state_management/store.ts | 19 +++++ yarn.lock | 17 +++++ 15 files changed, 248 insertions(+), 118 deletions(-) create mode 100644 src/plugins/wizard/public/application/utils/state_management/config_slice.ts create mode 100644 src/plugins/wizard/public/application/utils/state_management/datasource_slice.ts create mode 100644 src/plugins/wizard/public/application/utils/state_management/hooks.ts create mode 100644 src/plugins/wizard/public/application/utils/state_management/index.ts create mode 100644 src/plugins/wizard/public/application/utils/state_management/store.ts diff --git a/package.json b/package.json index 359c2663f2b..ba6240d8c42 100644 --- a/package.json +++ b/package.json @@ -137,6 +137,7 @@ "@osd/std": "1.0.0", "@osd/ui-framework": "1.0.0", "@osd/ui-shared-deps": "1.0.0", + "@reduxjs/toolkit": "^1.6.2", "@types/yauzl": "^2.9.1", "JSONStream": "1.3.5", "abortcontroller-polyfill": "^1.4.0", diff --git a/src/plugins/wizard/public/application/app.tsx b/src/plugins/wizard/public/application/app.tsx index 37244f16ca5..84302c54f51 100644 --- a/src/plugins/wizard/public/application/app.tsx +++ b/src/plugins/wizard/public/application/app.tsx @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { I18nProvider } from '@osd/i18n/react'; import { EuiPage } from '@elastic/eui'; -import { DataPublicPluginStart, IndexPattern } from '../../../data/public'; +import { DataPublicPluginStart } from '../../../data/public'; import { SideNav } from './components/side_nav'; import { DragDropProvider } from './utils/drag_drop/drag_drop_context'; import { useOpenSearchDashboards } from '../../../opensearch_dashboards_react/public'; @@ -15,13 +15,15 @@ import { Workspace } from './components/workspace'; import './app.scss'; import { TopNav } from './components/top_nav'; +import { useTypedDispatch } from './utils/state_management'; +import { setIndexPattern } from './utils/state_management/datasource_slice'; export const WizardApp = () => { const { services: { data }, } = useOpenSearchDashboards(); - const [indexPattern, setIndexPattern] = useIndexPattern(data); + useIndexPattern(data); // Render the application DOM. return ( @@ -29,7 +31,7 @@ export const WizardApp = () => { - + @@ -39,16 +41,15 @@ export const WizardApp = () => { // TODO: Temporary. Need to update it fetch the index pattern cohesively function useIndexPattern(data: DataPublicPluginStart) { - const [indexPattern, setIndexPattern] = useState(null); + const dispatch = useTypedDispatch(); + useEffect(() => { const fetchIndexPattern = async () => { const defaultIndexPattern = await data.indexPatterns.getDefault(); if (defaultIndexPattern) { - setIndexPattern(defaultIndexPattern); + dispatch(setIndexPattern(defaultIndexPattern)); } }; fetchIndexPattern(); - }, [data.indexPatterns]); - - return [indexPattern, setIndexPattern] as const; + }, [data.indexPatterns, dispatch]); } diff --git a/src/plugins/wizard/public/application/components/data_tab/config_panel.tsx b/src/plugins/wizard/public/application/components/data_tab/config_panel.tsx index 262fd4c06e4..ec910b7352d 100644 --- a/src/plugins/wizard/public/application/components/data_tab/config_panel.tsx +++ b/src/plugins/wizard/public/application/components/data_tab/config_panel.tsx @@ -6,25 +6,13 @@ import { EuiForm, EuiTitle } from '@elastic/eui'; import React from 'react'; import { i18n } from '@osd/i18n'; -import { BUCKET_TYPES, METRIC_TYPES } from '../../../../../data/public'; import { ConfigSection } from './config_section'; import './config_panel.scss'; - -// TODO: Temp. Remove once visualizations can be refgistered and editor configs can be passed along -const CONFIG = { - x: { - title: 'X Axis', - allowedAggregation: BUCKET_TYPES.TERMS, - }, - y: { - title: 'Y Axis', - allowedAggregation: METRIC_TYPES.AVG, - }, -}; +import { useTypedSelector } from '../../utils/state_management'; export function ConfigPanel() { - const sections = CONFIG; + const { configSections } = useTypedSelector((state) => state.config); return ( @@ -35,8 +23,8 @@ export function ConfigPanel() { })} - {Object.entries(sections).map(([sectionId, sectionProps], index) => ( - {}} /> + {Object.entries(configSections).map(([sectionId, sectionProps], index) => ( + ))} ); diff --git a/src/plugins/wizard/public/application/components/data_tab/config_section.tsx b/src/plugins/wizard/public/application/components/data_tab/config_section.tsx index 88511756222..64f74824d71 100644 --- a/src/plugins/wizard/public/application/components/data_tab/config_section.tsx +++ b/src/plugins/wizard/public/application/components/data_tab/config_section.tsx @@ -3,26 +3,39 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiButtonIcon, EuiFormRow, EuiPanel, EuiText } from '@elastic/eui'; +import { EuiButtonIcon, EuiPanel, EuiText, EuiTitle } from '@elastic/eui'; import { i18n } from '@osd/i18n'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback } from 'react'; import { IndexPatternField } from 'src/plugins/data/common'; import { useDrop } from '../../utils/drag_drop'; +import { useTypedDispatch, useTypedSelector } from '../../utils/state_management'; +import { + addConfigSectionField, + removeConfigSectionField, +} from '../../utils/state_management/config_slice'; import './config_section.scss'; interface ConfigSectionProps { id: string; title: string; - onChange: Function; } -export const ConfigSection = ({ title, id, onChange }: ConfigSectionProps) => { - const [currentField, setCurrentField] = useState(); +export const ConfigSection = ({ title, id }: ConfigSectionProps) => { + const dispatch = useTypedDispatch(); + const { fields } = useTypedSelector((state) => state.config.configSections[id]); - const dropHandler = useCallback((field: IndexPatternField) => { - setCurrentField(field); - }, []); + const dropHandler = useCallback( + (field: IndexPatternField) => { + dispatch( + addConfigSectionField({ + sectionId: id, + field, + }) + ); + }, + [dispatch, id] + ); const [dropProps, { isValidDropTarget, dragData }] = useDrop('dataPlane', dropHandler); const dropTargetString = dragData @@ -31,24 +44,32 @@ export const ConfigSection = ({ title, id, onChange }: ConfigSectionProps) => { defaultMessage: 'Click or drop to add', }); - useEffect(() => { - onChange(id, currentField); - }, [id, currentField, onChange]); - return ( - - {currentField ? ( - - - {currentField.displayName} - - setCurrentField(undefined)} - /> - +
+ +

{title}

+
+ {fields.length ? ( + fields.map((field, index) => ( + + + {field.displayName} + + + dispatch( + removeConfigSectionField({ + sectionId: id, + field, + }) + ) + } + /> + + )) ) : (
{ {dropTargetString}
)} - +
); }; diff --git a/src/plugins/wizard/public/application/components/data_tab/field_selector.tsx b/src/plugins/wizard/public/application/components/data_tab/field_selector.tsx index 53d38594496..17d56175227 100644 --- a/src/plugins/wizard/public/application/components/data_tab/field_selector.tsx +++ b/src/plugins/wizard/public/application/components/data_tab/field_selector.tsx @@ -22,10 +22,7 @@ import { import { FieldSelectorField } from './field_selector_field'; import './field_selector.scss'; - -interface FieldSelectorDeps { - indexFields: IndexPatternField[]; -} +import { useTypedSelector } from '../../utils/state_management'; interface IFieldCategories { categorical: IndexPatternField[]; @@ -40,7 +37,8 @@ const META_FIELDS: string[] = [ OPENSEARCH_FIELD_TYPES._TYPE, ]; -export const FieldSelector = ({ indexFields }: FieldSelectorDeps) => { +export const FieldSelector = () => { + const indexFields = useTypedSelector((state) => state.dataSource.visualizableFields); const fields = indexFields?.reduce( (fieldGroups, currentField) => { const category = getFieldCategory(currentField); diff --git a/src/plugins/wizard/public/application/components/data_tab/index.tsx b/src/plugins/wizard/public/application/components/data_tab/index.tsx index e7e5f02e6d0..dd062f3a787 100644 --- a/src/plugins/wizard/public/application/components/data_tab/index.tsx +++ b/src/plugins/wizard/public/application/components/data_tab/index.tsx @@ -4,20 +4,15 @@ */ import React from 'react'; -import { IndexPatternField } from '../../../../../data/public'; import { FieldSelector } from './field_selector'; import { ConfigPanel } from './config_panel'; import './index.scss'; -interface DataTabDeps { - indexFields: IndexPatternField[]; -} - -export const DataTab = ({ indexFields }: DataTabDeps) => { +export const DataTab = () => { return (
- +
); diff --git a/src/plugins/wizard/public/application/components/side_nav.tsx b/src/plugins/wizard/public/application/components/side_nav.tsx index a35c039e57f..2f9eab83fad 100644 --- a/src/plugins/wizard/public/application/components/side_nav.tsx +++ b/src/plugins/wizard/public/application/components/side_nav.tsx @@ -3,33 +3,21 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { i18n } from '@osd/i18n'; -import { - EuiFormLabel, - EuiFlexGroup, - EuiFlexItem, - EuiTabbedContent, - EuiTabbedContentTab, -} from '@elastic/eui'; +import { EuiFormLabel, EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui'; -import { IndexPattern, IndexPatternField, OSD_FIELD_TYPES } from '../../../../data/public'; import { DataTab } from './data_tab'; import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; import { WizardServices } from '../../types'; import { StyleTab } from './style_tab'; import './side_nav.scss'; +import { useTypedDispatch, useTypedSelector } from '../utils/state_management'; +import { setIndexPattern } from '../utils/state_management/datasource_slice'; -interface SideNavDeps { - indexPattern: IndexPattern | null; - setIndexPattern: React.Dispatch>; -} - -const ALLOWED_FIELDS: string[] = [OSD_FIELD_TYPES.STRING, OSD_FIELD_TYPES.NUMBER]; - -export const SideNav = ({ indexPattern, setIndexPattern }: SideNavDeps) => { +export const SideNav = () => { const { services: { data, @@ -37,24 +25,8 @@ export const SideNav = ({ indexPattern, setIndexPattern }: SideNavDeps) => { }, } = useOpenSearchDashboards(); const { IndexPatternSelect } = data.ui; - const [indexFields, setIndexFields] = useState([]); - - // Fetch the default index pattern using the `data.indexPatterns` service, as the component is mounted. - useEffect(() => { - const setDefaultIndexPattern = async () => { - const defaultIndexPattern = await data.indexPatterns.getDefault(); - setIndexPattern(defaultIndexPattern); - }; - - setDefaultIndexPattern(); - }, [data, setIndexPattern]); - - // Update the fields list every time the index pattern is modified. - useEffect(() => { - const fields = indexPattern?.fields; - - setIndexFields(fields?.filter(isValidField) || []); - }, [indexPattern]); + const { indexPattern } = useTypedSelector((state) => state.dataSource); + const dispatch = useTypedDispatch(); const tabs: EuiTabbedContentTab[] = [ { @@ -62,7 +34,7 @@ export const SideNav = ({ indexPattern, setIndexPattern }: SideNavDeps) => { name: i18n.translate('wizard.nav.dataTab.title', { defaultMessage: 'Data', }), - content: , + content: , }, { id: 'style-tab', @@ -89,7 +61,7 @@ export const SideNav = ({ indexPattern, setIndexPattern }: SideNavDeps) => { indexPatternId={indexPattern?.id || ''} onChange={async (newIndexPatternId: any) => { const newIndexPattern = await data.indexPatterns.get(newIndexPatternId); - setIndexPattern(newIndexPattern); + dispatch(setIndexPattern(newIndexPattern)); }} isClearable={false} /> @@ -98,13 +70,3 @@ export const SideNav = ({ indexPattern, setIndexPattern }: SideNavDeps) => { ); }; - -// TODO: Temporary validate function -// Need to identify hopw to get fieldCounts to use the standard filter and group functions -function isValidField(field: IndexPatternField): boolean { - const isAggregatable = field.aggregatable === true; - const isNotScripted = !field.scripted; - const isAllowed = ALLOWED_FIELDS.includes(field.type); - - return isAggregatable && isNotScripted && isAllowed; -} diff --git a/src/plugins/wizard/public/application/components/top_nav.tsx b/src/plugins/wizard/public/application/components/top_nav.tsx index e0c9d47efef..5afa39f7baf 100644 --- a/src/plugins/wizard/public/application/components/top_nav.tsx +++ b/src/plugins/wizard/public/application/components/top_nav.tsx @@ -3,14 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useMemo, useState } from 'react'; -import { IndexPattern } from '../../../../data/public'; +import React, { useMemo } from 'react'; import { PLUGIN_ID } from '../../../common'; import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; import { getTopNavconfig } from '../utils/get_top_nav_config'; import { WizardServices } from '../../types'; import './top_nav.scss'; +import { useTypedSelector } from '../utils/state_management'; export const TopNav = () => { const { services } = useOpenSearchDashboards(); @@ -22,8 +22,7 @@ export const TopNav = () => { } = services; const config = useMemo(() => getTopNavconfig(services), [services]); - // TODO: Set index pattern/data source here. Filters wont show up until you do - const [indexPatterns, setIndexPatterns] = useState([]); + const { indexPattern } = useTypedSelector((state) => state.dataSource); return (
@@ -34,7 +33,7 @@ export const TopNav = () => { showSearchBar={true} useDefaultBehaviors={true} screenTitle="Test" - indexPatterns={indexPatterns} + indexPatterns={indexPattern ? [indexPattern] : []} />
); diff --git a/src/plugins/wizard/public/application/index.tsx b/src/plugins/wizard/public/application/index.tsx index b778d2e139d..b48044e8b2e 100644 --- a/src/plugins/wizard/public/application/index.tsx +++ b/src/plugins/wizard/public/application/index.tsx @@ -6,10 +6,12 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter as Router } from 'react-router-dom'; +import { Provider as ReduxProvider } from 'react-redux'; import { AppMountParameters } from '../../../../core/public'; import { WizardServices } from '../types'; import { WizardApp } from './app'; import { OpenSearchDashboardsContextProvider } from '../../../opensearch_dashboards_react/public'; +import { store } from './utils/state_management'; export const renderApp = ( { appBasePath, element }: AppMountParameters, @@ -18,9 +20,11 @@ export const renderApp = ( ReactDOM.render( - - - + + + + + , element diff --git a/src/plugins/wizard/public/application/utils/state_management/config_slice.ts b/src/plugins/wizard/public/application/utils/state_management/config_slice.ts new file mode 100644 index 00000000000..5d890859610 --- /dev/null +++ b/src/plugins/wizard/public/application/utils/state_management/config_slice.ts @@ -0,0 +1,62 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { IndexPatternField } from '../../../../../data/public'; + +interface ConfigSections { + [id: string]: { + title: string; + fields: IndexPatternField[]; + }; +} +interface ConfigState { + configSections: ConfigSections; +} + +// TODO: Temp. Remove once visualizations can be refgistered and editor configs can be passed along +// TODO: this is a placeholder while the config section is iorned out +const initialState: ConfigState = { + configSections: { + x: { + title: 'X Axis', + fields: [], + }, + y: { + title: 'Y Axis', + fields: [], + }, + }, +}; + +interface SectionField { + sectionId: string; + field: IndexPatternField; +} + +export const slice = createSlice({ + name: 'configuration', + initialState, + reducers: { + addConfigSectionField: (state, action: PayloadAction) => { + const { field, sectionId } = action.payload; + if (state.configSections[sectionId]) { + state.configSections[sectionId].fields.push(field); + } + }, + removeConfigSectionField: (state, action: PayloadAction) => { + const { field, sectionId } = action.payload; + if (state.configSections[sectionId]) { + const fieldIndex = state.configSections[sectionId].fields.findIndex( + (configField) => configField === field + ); + if (fieldIndex !== -1) state.configSections[sectionId].fields.splice(fieldIndex, 1); + } + }, + }, +}); + +export const { reducer } = slice; +export const { addConfigSectionField, removeConfigSectionField } = slice.actions; diff --git a/src/plugins/wizard/public/application/utils/state_management/datasource_slice.ts b/src/plugins/wizard/public/application/utils/state_management/datasource_slice.ts new file mode 100644 index 00000000000..9bffb56eded --- /dev/null +++ b/src/plugins/wizard/public/application/utils/state_management/datasource_slice.ts @@ -0,0 +1,45 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { IndexPattern } from 'src/plugins/data/common'; + +import { IndexPatternField, OSD_FIELD_TYPES } from '../../../../../data/public'; + +const ALLOWED_FIELDS: string[] = [OSD_FIELD_TYPES.STRING, OSD_FIELD_TYPES.NUMBER]; + +interface DataSourceState { + indexPattern: IndexPattern | null; + visualizableFields: IndexPatternField[]; +} + +const initialState: DataSourceState = { + indexPattern: null, + visualizableFields: [], +}; + +export const slice = createSlice({ + name: 'dataSource', + initialState, + reducers: { + setIndexPattern: (state, action: PayloadAction) => { + state.indexPattern = action.payload; + state.visualizableFields = action.payload.fields.filter(isVisualizable); + }, + }, +}); + +export const { reducer } = slice; +export const { setIndexPattern } = slice.actions; + +// TODO: Temporary validate function +// Need to identify hopw to get fieldCounts to use the standard filter and group functions +function isVisualizable(field: IndexPatternField): boolean { + const isAggregatable = field.aggregatable === true; + const isNotScripted = !field.scripted; + const isAllowed = ALLOWED_FIELDS.includes(field.type); + + return isAggregatable && isNotScripted && isAllowed; +} diff --git a/src/plugins/wizard/public/application/utils/state_management/hooks.ts b/src/plugins/wizard/public/application/utils/state_management/hooks.ts new file mode 100644 index 00000000000..823c34528c9 --- /dev/null +++ b/src/plugins/wizard/public/application/utils/state_management/hooks.ts @@ -0,0 +1,11 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; +import type { RootState, AppDispatch } from './store'; + +// Use throughout your app instead of plain `useDispatch` and `useSelector` +export const useTypedDispatch = () => useDispatch(); +export const useTypedSelector: TypedUseSelectorHook = useSelector; diff --git a/src/plugins/wizard/public/application/utils/state_management/index.ts b/src/plugins/wizard/public/application/utils/state_management/index.ts new file mode 100644 index 00000000000..edb5c2a1718 --- /dev/null +++ b/src/plugins/wizard/public/application/utils/state_management/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './store'; +export * from './hooks'; diff --git a/src/plugins/wizard/public/application/utils/state_management/store.ts b/src/plugins/wizard/public/application/utils/state_management/store.ts new file mode 100644 index 00000000000..c3c94fa673f --- /dev/null +++ b/src/plugins/wizard/public/application/utils/state_management/store.ts @@ -0,0 +1,19 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { configureStore } from '@reduxjs/toolkit'; +import { reducer as dataSourceReducer } from './datasource_slice'; +import { reducer as configReducer } from './config_slice'; + +export const store = configureStore({ + reducer: { + dataSource: dataSourceReducer, + config: configReducer, + }, +}); + +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; diff --git a/yarn.lock b/yarn.lock index 3403190815c..8092f7fbe4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2392,6 +2392,16 @@ colors "~1.2.1" string-argv "~0.3.1" +"@reduxjs/toolkit@^1.6.2": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.6.2.tgz#2f2b5365df77dd6697da28fdf44f33501ed9ba37" + integrity sha512-HbfI/hOVrAcMGAYsMWxw3UJyIoAS9JTdwddsjlr5w3S50tXhWb+EMyhIw+IAvCVCLETkzdjgH91RjDSYZekVBA== + dependencies: + immer "^9.0.6" + redux "^4.1.0" + redux-thunk "^2.3.0" + reselect "^4.0.0" + "@samverschueren/stream-to-observable@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" @@ -15858,6 +15868,13 @@ redux@^4.0.5: loose-envify "^1.4.0" symbol-observable "^1.2.0" +redux@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104" + integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw== + dependencies: + "@babel/runtime" "^7.9.2" + reflect.ownkeys@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" From 94775224b90ed6f59908437da5548ae0b5022dc6 Mon Sep 17 00:00:00 2001 From: Ashwin Pc Date: Sat, 11 Dec 2021 01:40:09 +0000 Subject: [PATCH 3/7] Moves Drag and Drop to create visualization menu Signed-off-by: Ashwin Pc --- .../__snapshots__/new_vis_modal.test.tsx.snap | 160 +++++++++++------- .../wizard/type_selection/type_selection.tsx | 2 +- src/plugins/wizard/opensearch_dashboards.json | 3 +- src/plugins/wizard/public/index.ts | 5 +- src/plugins/wizard/public/plugin.ts | 49 ++++-- src/plugins/wizard/public/types.ts | 2 + 6 files changed, 141 insertions(+), 80 deletions(-) diff --git a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap index 0c72df9a5fe..4b5089249c8 100644 --- a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -731,14 +731,18 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
- - + + +
+

- - + + +

+

- - + + +

+

- - + + +

+

- - + + +

+

- - + + +

+

- - + + +

+

- - + + +

+

); diff --git a/src/plugins/wizard/opensearch_dashboards.json b/src/plugins/wizard/opensearch_dashboards.json index 93eb9d9a040..8dd00aae389 100644 --- a/src/plugins/wizard/opensearch_dashboards.json +++ b/src/plugins/wizard/opensearch_dashboards.json @@ -10,7 +10,8 @@ "opensearchDashboardsReact", "savedObjects", "embeddable", - "dashboard" + "dashboard", + "visualizations" ], "optionalPlugins": [] } diff --git a/src/plugins/wizard/public/index.ts b/src/plugins/wizard/public/index.ts index 884b83e7765..97f9007549a 100644 --- a/src/plugins/wizard/public/index.ts +++ b/src/plugins/wizard/public/index.ts @@ -3,11 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { PluginInitializerContext } from '../../../core/public'; import { WizardPlugin } from './plugin'; // This exports static code and TypeScript types, // as well as, OpenSearch Dashboards Platform `plugin()` initializer. -export function plugin() { - return new WizardPlugin(); +export function plugin(initializerContext: PluginInitializerContext) { + return new WizardPlugin(initializerContext); } export { WizardServices, WizardPluginStartDependencies } from './types'; diff --git a/src/plugins/wizard/public/plugin.ts b/src/plugins/wizard/public/plugin.ts index e8cbcbcac22..139c4070858 100644 --- a/src/plugins/wizard/public/plugin.ts +++ b/src/plugins/wizard/public/plugin.ts @@ -3,42 +3,53 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { i18n } from '@osd/i18n'; import { AppMountParameters, + AppNavLinkStatus, CoreSetup, CoreStart, - DEFAULT_APP_CATEGORIES, Plugin, + PluginInitializerContext, } from '../../../core/public'; -import { WizardPluginStartDependencies, WizardServices } from './types'; +import { + WizardPluginSetupDependencies, + WizardPluginStartDependencies, + WizardServices, +} from './types'; import { PLUGIN_NAME } from '../common'; -export class WizardPlugin implements Plugin { - public setup(core: CoreSetup) { - // Register an application into the side navigation menu +export class WizardPlugin + implements Plugin { + constructor(public initializerContext: PluginInitializerContext) {} + + public setup( + core: CoreSetup, + { visualizations }: WizardPluginSetupDependencies + ) { + // Register the plugin to core core.application.register({ id: 'wizard', title: PLUGIN_NAME, - euiIconType: 'inputOutput', - defaultPath: '#/', - category: DEFAULT_APP_CATEGORIES.opensearchDashboards, + 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, pluginsStart] = await core.getStartServices(); + const { data, savedObjects, navigation } = pluginsStart; const services: WizardServices = { ...coreStart, toastNotifications: coreStart.notifications.toasts, - data: pluginsStart.data, - savedObjectsPublic: pluginsStart.savedObjects, - navigation: pluginsStart.navigation, + data, + savedObjectsPublic: savedObjects, + navigation, setHeaderActionMenu: params.setHeaderActionMenu, }; // make sure the index pattern list is up to date - pluginsStart.data.indexPatterns.clearCache(); + data.indexPatterns.clearCache(); // make sure a default index pattern exists // if not, the page will be redirected to management and visualize won't be rendered await pluginsStart.data.indexPatterns.ensureDefaultIndexPattern(); @@ -47,6 +58,20 @@ export class WizardPlugin implements Plugin Date: Tue, 4 Jan 2022 15:34:57 -0800 Subject: [PATCH 4/7] Field Search in Data panel (#995) Add ability to search on index fields Signed-off-by: Abbas Hussain --- .../components/data_tab/field_search.tsx | 47 +++++++++++++++++++ .../components/data_tab/field_selector.tsx | 31 +++++++----- .../state_management/datasource_slice.ts | 9 +++- 3 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 src/plugins/wizard/public/application/components/data_tab/field_search.tsx diff --git a/src/plugins/wizard/public/application/components/data_tab/field_search.tsx b/src/plugins/wizard/public/application/components/data_tab/field_search.tsx new file mode 100644 index 00000000000..2db8404c93c --- /dev/null +++ b/src/plugins/wizard/public/application/components/data_tab/field_search.tsx @@ -0,0 +1,47 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { i18n } from '@osd/i18n'; +import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { setSearchField } from '../../utils/state_management/datasource_slice'; +import { useTypedDispatch } from '../../utils/state_management'; + +export interface Props { + /** + * the input value of the user + */ + value?: string; +} + +/** + * Component is Wizard's side bar to search of available fields + * Additionally there's a button displayed that allows the user to show/hide more filter fields + */ +export function FieldSearch({ value }: Props) { + const searchPlaceholder = i18n.translate('wizard.fieldChooser.searchPlaceHolder', { + defaultMessage: 'Search field names', + }); + + const dispatch = useTypedDispatch(); + + return ( + + + + dispatch(setSearchField(event.currentTarget.value))} + placeholder={searchPlaceholder} + value={value} + /> + + + + ); +} diff --git a/src/plugins/wizard/public/application/components/data_tab/field_selector.tsx b/src/plugins/wizard/public/application/components/data_tab/field_selector.tsx index 17d56175227..1464f31aabd 100644 --- a/src/plugins/wizard/public/application/components/data_tab/field_selector.tsx +++ b/src/plugins/wizard/public/application/components/data_tab/field_selector.tsx @@ -3,16 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React from 'react'; - -import { - EuiFormLabel, - EuiFlexItem, - EuiAccordion, - EuiSpacer, - EuiNotificationBadge, - EuiTitle, -} from '@elastic/eui'; +import React, { useCallback, useState, useEffect } from 'react'; +import { EuiFlexItem, EuiAccordion, EuiSpacer, EuiNotificationBadge, EuiTitle } from '@elastic/eui'; +import { FieldSearch } from './field_search'; import { IndexPatternField, @@ -39,7 +32,19 @@ const META_FIELDS: string[] = [ export const FieldSelector = () => { const indexFields = useTypedSelector((state) => state.dataSource.visualizableFields); - const fields = indexFields?.reduce( + const [filteredFields, setFilteredFields] = useState(indexFields); + const fieldSearchValue = useTypedSelector((state) => state.dataSource.searchField); + + useEffect(() => { + const filteredSubset = indexFields.filter((field) => + field.displayName.includes(fieldSearchValue) + ); + + setFilteredFields(filteredSubset); + return; + }, [indexFields, fieldSearchValue]); + + const fields = filteredFields?.reduce( (fieldGroups, currentField) => { const category = getFieldCategory(currentField); fieldGroups[category].push(currentField); @@ -56,7 +61,9 @@ export const FieldSelector = () => { return (

- TODO: Search goes here +
+ +
) => { + state.searchField = action.payload; + }, }, }); export const { reducer } = slice; -export const { setIndexPattern } = slice.actions; +export const { setIndexPattern, setSearchField } = slice.actions; // TODO: Temporary validate function -// Need to identify hopw to get fieldCounts to use the standard filter and group functions +// Need to identify how to get fieldCounts to use the standard filter and group functions function isVisualizable(field: IndexPatternField): boolean { const isAggregatable = field.aggregatable === true; const isNotScripted = !field.scripted; From a68598594a5249cbccbf5d7a0aa395b4366d0091 Mon Sep 17 00:00:00 2001 From: Ashwin P Chandran Date: Tue, 1 Mar 2022 18:51:19 -0800 Subject: [PATCH 5/7] Adds initial type service (#1260) Signed-off-by: Ashwin Pc --- src/plugins/wizard/public/application/app.tsx | 28 +----- .../application/components/workspace.tsx | 88 +++++++++++++++++-- .../wizard/public/application/index.tsx | 5 +- .../state_management/datasource_slice.ts | 13 +++ .../utils/state_management/preload.ts | 22 +++++ .../utils/state_management/store.ts | 31 +++++-- .../state_management/visualization_slice.ts | 39 ++++++++ src/plugins/wizard/public/plugin.ts | 22 ++++- .../public/services/type_service/index.ts | 6 ++ .../services/type_service/type_service.ts | 88 +++++++++++++++++++ .../type_service/visualization_type.ts | 48 ++++++++++ src/plugins/wizard/public/types.ts | 4 + .../public/visualizations/bar_chart/index.ts | 16 ++++ .../wizard/public/visualizations/index.ts | 16 ++++ .../public/visualizations/pie_chart/index.ts | 15 ++++ .../wizard/public/visualizations/xy_chart.tsx | 6 -- 16 files changed, 396 insertions(+), 51 deletions(-) create mode 100644 src/plugins/wizard/public/application/utils/state_management/preload.ts create mode 100644 src/plugins/wizard/public/application/utils/state_management/visualization_slice.ts create mode 100644 src/plugins/wizard/public/services/type_service/index.ts create mode 100644 src/plugins/wizard/public/services/type_service/type_service.ts create mode 100644 src/plugins/wizard/public/services/type_service/visualization_type.ts create mode 100644 src/plugins/wizard/public/visualizations/bar_chart/index.ts create mode 100644 src/plugins/wizard/public/visualizations/index.ts create mode 100644 src/plugins/wizard/public/visualizations/pie_chart/index.ts delete mode 100644 src/plugins/wizard/public/visualizations/xy_chart.tsx diff --git a/src/plugins/wizard/public/application/app.tsx b/src/plugins/wizard/public/application/app.tsx index 84302c54f51..7d578ee77cd 100644 --- a/src/plugins/wizard/public/application/app.tsx +++ b/src/plugins/wizard/public/application/app.tsx @@ -3,28 +3,17 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useEffect } from 'react'; +import React from 'react'; import { I18nProvider } from '@osd/i18n/react'; import { EuiPage } from '@elastic/eui'; -import { DataPublicPluginStart } from '../../../data/public'; import { SideNav } from './components/side_nav'; import { DragDropProvider } from './utils/drag_drop/drag_drop_context'; -import { useOpenSearchDashboards } from '../../../opensearch_dashboards_react/public'; -import { WizardServices } from '../types'; import { Workspace } from './components/workspace'; import './app.scss'; import { TopNav } from './components/top_nav'; -import { useTypedDispatch } from './utils/state_management'; -import { setIndexPattern } from './utils/state_management/datasource_slice'; export const WizardApp = () => { - const { - services: { data }, - } = useOpenSearchDashboards(); - - useIndexPattern(data); - // Render the application DOM. return ( @@ -38,18 +27,3 @@ export const WizardApp = () => { ); }; - -// TODO: Temporary. Need to update it fetch the index pattern cohesively -function useIndexPattern(data: DataPublicPluginStart) { - const dispatch = useTypedDispatch(); - - useEffect(() => { - const fetchIndexPattern = async () => { - const defaultIndexPattern = await data.indexPatterns.getDefault(); - if (defaultIndexPattern) { - dispatch(setIndexPattern(defaultIndexPattern)); - } - }; - fetchIndexPattern(); - }, [data.indexPatterns, dispatch]); -} diff --git a/src/plugins/wizard/public/application/components/workspace.tsx b/src/plugins/wizard/public/application/components/workspace.tsx index a83201be4cc..a6550a58fb8 100644 --- a/src/plugins/wizard/public/application/components/workspace.tsx +++ b/src/plugins/wizard/public/application/components/workspace.tsx @@ -3,8 +3,22 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; -import React, { FC } from 'react'; +import { + EuiButton, + EuiContextMenu, + EuiContextMenuPanelItemDescriptor, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPanel, + EuiPopover, +} from '@elastic/eui'; +import React, { FC, useState, useMemo } from 'react'; +import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; +import { WizardServices } from '../../types'; +import { useTypedDispatch, useTypedSelector } from '../utils/state_management'; +import { setActiveVisualization } from '../utils/state_management/visualization_slice'; import './workspace.scss'; @@ -13,10 +27,7 @@ export const Workspace: FC = ({ children }) => {
- {/* TODO: This is the temporary view of the selected chard, should be replaced by dropdown */} - - Bar - + @@ -35,3 +46,68 @@ export const Workspace: FC = ({ children }) => {
); }; + +const TypeSelectorPopover = () => { + const [isPopoverOpen, setPopover] = useState(false); + const { activeVisualization: activeVisualizationId } = useTypedSelector( + (state) => state.visualization + ); + const { + services: { types }, + } = useOpenSearchDashboards(); + const dispatch = useTypedDispatch(); + + // TODO: Error if no active visualization + const activeVisualization = types.get(activeVisualizationId || ''); + const visualizationTypes = types.all(); + + const onButtonClick = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + const panels = useMemo( + () => [ + { + id: 0, + title: 'Chart types', + items: visualizationTypes.map( + ({ name, title, icon, description }): EuiContextMenuPanelItemDescriptor => ({ + name: title, + icon: , + onClick: () => { + closePopover(); + dispatch(setActiveVisualization(name)); + }, + toolTipContent: description, + toolTipPosition: 'right', + }) + ), + }, + ], + [dispatch, visualizationTypes] + ); + + const button = ( + + {activeVisualization?.title} + + ); + + return ( + + + + ); +}; diff --git a/src/plugins/wizard/public/application/index.tsx b/src/plugins/wizard/public/application/index.tsx index b48044e8b2e..c451d082b15 100644 --- a/src/plugins/wizard/public/application/index.tsx +++ b/src/plugins/wizard/public/application/index.tsx @@ -7,15 +7,16 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter as Router } from 'react-router-dom'; import { Provider as ReduxProvider } from 'react-redux'; +import { Store } from 'redux'; import { AppMountParameters } from '../../../../core/public'; import { WizardServices } from '../types'; import { WizardApp } from './app'; import { OpenSearchDashboardsContextProvider } from '../../../opensearch_dashboards_react/public'; -import { store } from './utils/state_management'; export const renderApp = ( { appBasePath, element }: AppMountParameters, - services: WizardServices + services: WizardServices, + store: Store ) => { ReactDOM.render( diff --git a/src/plugins/wizard/public/application/utils/state_management/datasource_slice.ts b/src/plugins/wizard/public/application/utils/state_management/datasource_slice.ts index a84f1a73ca0..d51d463d68e 100644 --- a/src/plugins/wizard/public/application/utils/state_management/datasource_slice.ts +++ b/src/plugins/wizard/public/application/utils/state_management/datasource_slice.ts @@ -5,6 +5,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { IndexPattern } from 'src/plugins/data/common'; +import { WizardServices } from '../../../types'; import { IndexPatternField, OSD_FIELD_TYPES } from '../../../../../data/public'; @@ -22,6 +23,18 @@ const initialState: DataSourceState = { searchField: '', }; +export const getPreloadedState = async ({ data }: WizardServices): Promise => { + const preloadedState = { ...initialState }; + + const defaultIndexPattern = await data.indexPatterns.getDefault(); + if (defaultIndexPattern) { + preloadedState.indexPattern = defaultIndexPattern; + preloadedState.visualizableFields = defaultIndexPattern.fields.filter(isVisualizable); + } + + return preloadedState; +}; + export const slice = createSlice({ name: 'dataSource', initialState, diff --git a/src/plugins/wizard/public/application/utils/state_management/preload.ts b/src/plugins/wizard/public/application/utils/state_management/preload.ts new file mode 100644 index 00000000000..ad78b642c23 --- /dev/null +++ b/src/plugins/wizard/public/application/utils/state_management/preload.ts @@ -0,0 +1,22 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { PreloadedState } from '@reduxjs/toolkit'; +import { WizardServices } from '../../..'; +import { getPreloadedState as getPreloadedDatasourceState } from './datasource_slice'; +import { getPreloadedState as getPreloadedVisualizationState } from './visualization_slice'; +import { RootState } from './store'; + +export const getPreloadedState = async ( + services: WizardServices +): Promise> => { + const dataSourceState = await getPreloadedDatasourceState(services); + const visualizationState = await getPreloadedVisualizationState(services); + + return { + dataSource: dataSourceState, + visualization: visualizationState, + }; +}; diff --git a/src/plugins/wizard/public/application/utils/state_management/store.ts b/src/plugins/wizard/public/application/utils/state_management/store.ts index c3c94fa673f..4fa56c1a7c9 100644 --- a/src/plugins/wizard/public/application/utils/state_management/store.ts +++ b/src/plugins/wizard/public/application/utils/state_management/store.ts @@ -3,17 +3,32 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { configureStore } from '@reduxjs/toolkit'; +import { combineReducers, configureStore, PreloadedState } from '@reduxjs/toolkit'; import { reducer as dataSourceReducer } from './datasource_slice'; import { reducer as configReducer } from './config_slice'; +import { reducer as visualizationReducer } from './visualization_slice'; +import { WizardServices } from '../../..'; +import { getPreloadedState } from './preload'; -export const store = configureStore({ - reducer: { - dataSource: dataSourceReducer, - config: configReducer, - }, +const rootReducer = combineReducers({ + dataSource: dataSourceReducer, + config: configReducer, + visualization: visualizationReducer, }); +export const configurePreloadedStore = (preloadedState: PreloadedState) => { + return configureStore({ + reducer: rootReducer, + preloadedState, + }); +}; + +export const getPreloadedStore = async (services: WizardServices) => { + const preloadedState = await getPreloadedState(services); + return configurePreloadedStore(preloadedState); +}; + // Infer the `RootState` and `AppDispatch` types from the store itself -export type RootState = ReturnType; -export type AppDispatch = typeof store.dispatch; +export type RootState = ReturnType; +type Store = ReturnType; +export type AppDispatch = Store['dispatch']; diff --git a/src/plugins/wizard/public/application/utils/state_management/visualization_slice.ts b/src/plugins/wizard/public/application/utils/state_management/visualization_slice.ts new file mode 100644 index 00000000000..692f9434c8d --- /dev/null +++ b/src/plugins/wizard/public/application/utils/state_management/visualization_slice.ts @@ -0,0 +1,39 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { WizardServices } from '../../../types'; + +interface VisualizationState { + activeVisualization: string | null; +} + +const initialState: VisualizationState = { + activeVisualization: null, +}; + +export const getPreloadedState = async ({ types }: WizardServices): Promise => { + const preloadedState = { ...initialState }; + + const defaultVisualization = types.all()[0]; + if (defaultVisualization) { + preloadedState.activeVisualization = defaultVisualization.name; + } + + return preloadedState; +}; + +export const slice = createSlice({ + name: 'visualization', + initialState, + reducers: { + setActiveVisualization: (state, action: PayloadAction) => { + state.activeVisualization = action.payload; + }, + }, +}); + +export const { reducer } = slice; +export const { setActiveVisualization } = slice.actions; diff --git a/src/plugins/wizard/public/plugin.ts b/src/plugins/wizard/public/plugin.ts index 139c4070858..5b309080a87 100644 --- a/src/plugins/wizard/public/plugin.ts +++ b/src/plugins/wizard/public/plugin.ts @@ -16,17 +16,24 @@ import { WizardPluginSetupDependencies, WizardPluginStartDependencies, WizardServices, + WizardSetup, } from './types'; import { PLUGIN_NAME } from '../common'; +import { TypeService } from './services/type_service'; +import { getPreloadedStore } from './application/utils/state_management'; export class WizardPlugin - implements Plugin { + implements + Plugin { + private typeService = new TypeService(); + constructor(public initializerContext: PluginInitializerContext) {} public setup( core: CoreSetup, { visualizations }: WizardPluginSetupDependencies ) { + const typeService = this.typeService; // Register the plugin to core core.application.register({ id: 'wizard', @@ -39,6 +46,9 @@ export class WizardPlugin const [coreStart, pluginsStart] = await core.getStartServices(); const { data, savedObjects, navigation } = pluginsStart; + const { registerDefaultTypes } = await import('./visualizations'); + registerDefaultTypes(typeService.setup()); + const services: WizardServices = { ...coreStart, toastNotifications: coreStart.notifications.toasts, @@ -46,16 +56,20 @@ export class WizardPlugin savedObjectsPublic: savedObjects, navigation, setHeaderActionMenu: params.setHeaderActionMenu, + types: typeService.start(), }; // make sure the index pattern list is up to date data.indexPatterns.clearCache(); // make sure a default index pattern exists // if not, the page will be redirected to management and visualize won't be rendered + // TODO: Add the redirect await pluginsStart.data.indexPatterns.ensureDefaultIndexPattern(); + const store = await getPreloadedStore(services); + // Render the application - return renderApp(params, services); + return renderApp(params, services, store); }, }); @@ -72,6 +86,10 @@ export class WizardPlugin aliasApp: 'wizard', aliasPath: '#/', }); + + return { + ...typeService.setup(), + }; } public start(core: CoreStart) {} diff --git a/src/plugins/wizard/public/services/type_service/index.ts b/src/plugins/wizard/public/services/type_service/index.ts new file mode 100644 index 00000000000..1fae953fb9b --- /dev/null +++ b/src/plugins/wizard/public/services/type_service/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './type_service'; diff --git a/src/plugins/wizard/public/services/type_service/type_service.ts b/src/plugins/wizard/public/services/type_service/type_service.ts new file mode 100644 index 00000000000..bdc2a421c0a --- /dev/null +++ b/src/plugins/wizard/public/services/type_service/type_service.ts @@ -0,0 +1,88 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import { VisualizationType, VisualizationTypeOptions } from './visualization_type'; + +/** + * Vis Types Service + * + * @internal + */ +export class TypeService { + private types: Record = {}; + + private registerVisualizationType(visDefinition: VisualizationType) { + if (this.types[visDefinition.name]) { + throw new Error('type already exists!'); + } + this.types[visDefinition.name] = visDefinition; + } + + public setup() { + return { + /** + * registers a visualization type + * @param config - visualization type definition + */ + createVisualizationType: (config: VisualizationTypeOptions): void => { + const vis = new VisualizationType(config); + this.registerVisualizationType(vis); + }, + }; + } + + public start() { + return { + /** + * returns specific visualization or undefined if not found + * @param {string} visualization - id of visualization to return + */ + get: (visualization: string): VisualizationType | undefined => { + return this.types[visualization]; + }, + /** + * returns all registered visualization types + */ + all: (): VisualizationType[] => { + return [...Object.values(this.types)]; + }, + }; + } + + public stop() { + // nothing to do here yet + } +} + +/** @internal */ +export type TypeServiceSetup = ReturnType; +export type TypeServiceStart = ReturnType; diff --git a/src/plugins/wizard/public/services/type_service/visualization_type.ts b/src/plugins/wizard/public/services/type_service/visualization_type.ts new file mode 100644 index 00000000000..cddb000f41d --- /dev/null +++ b/src/plugins/wizard/public/services/type_service/visualization_type.ts @@ -0,0 +1,48 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { IconType } from '@elastic/eui'; + +export interface VisualizationTypeOptions { + readonly name: string; + readonly title: string; + readonly description?: string; + readonly icon: IconType; + readonly stage?: 'beta' | 'production'; + readonly contributions: { + containers?: { + // Define new or override existing view containers + name: string; + title: string; + location: 'panel' | 'toolbar'; + // render: (schemas: ContainerSchema[]) => {}; // recieves an array of items to render within the container + }; + items?: { + 'container-name': any[]; // schema that is used to render the container. Each container is responsible for deciding that for consistency + // 'container-name': ContainerSchema[]; // schema that is used to render the container. Each container is responsible for deciding that for consistency + }; + }; + // pipeline: Expression; +} + +export type IVisualizationType = Required; + +export class VisualizationType implements IVisualizationType { + public readonly name; + public readonly title; + public readonly description; + public readonly icon; + public readonly stage; + public readonly contributions; + + constructor(options: VisualizationTypeOptions) { + this.name = options.name; + this.title = options.title; + this.description = options.description ?? ''; + this.icon = options.icon; + this.stage = options.stage ?? 'production'; + this.contributions = options.contributions; + } +} diff --git a/src/plugins/wizard/public/types.ts b/src/plugins/wizard/public/types.ts index 6c8c08547bb..07b1e5141c6 100644 --- a/src/plugins/wizard/public/types.ts +++ b/src/plugins/wizard/public/types.ts @@ -10,6 +10,9 @@ import { DashboardStart } from 'src/plugins/dashboard/public'; import { VisualizationsSetup } from 'src/plugins/visualizations/public'; import { NavigationPublicPluginStart } from '../../navigation/public'; import { DataPublicPluginStart } from '../../data/public'; +import { TypeServiceSetup, TypeServiceStart } from './services/type_service'; + +export type WizardSetup = TypeServiceSetup; export interface WizardPluginSetupDependencies { embeddable: EmbeddableSetup; @@ -28,4 +31,5 @@ export interface WizardServices extends CoreStart { savedObjectsPublic: SavedObjectsStart; navigation: NavigationPublicPluginStart; data: DataPublicPluginStart; + types: TypeServiceStart; } diff --git a/src/plugins/wizard/public/visualizations/bar_chart/index.ts b/src/plugins/wizard/public/visualizations/bar_chart/index.ts new file mode 100644 index 00000000000..cc05f790993 --- /dev/null +++ b/src/plugins/wizard/public/visualizations/bar_chart/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { VisualizationTypeOptions } from '../../services/type_service/visualization_type'; + +export const createBarChartConfig = (): VisualizationTypeOptions => { + return { + name: 'bar_chart', + title: 'Bar Chart', + icon: 'visBarVertical', + description: 'This is a bar chart', + contributions: {}, + }; +}; diff --git a/src/plugins/wizard/public/visualizations/index.ts b/src/plugins/wizard/public/visualizations/index.ts new file mode 100644 index 00000000000..604de170c8a --- /dev/null +++ b/src/plugins/wizard/public/visualizations/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { TypeServiceSetup } from '../services/type_service'; +import { createBarChartConfig } from './bar_chart'; +import { createPieChartConfig } from './pie_chart'; + +export function registerDefaultTypes(typeServieSetup: TypeServiceSetup) { + const visualizationTypes = [createBarChartConfig, createPieChartConfig]; + + visualizationTypes.forEach((createTypeConfig) => { + typeServieSetup.createVisualizationType(createTypeConfig()); + }); +} diff --git a/src/plugins/wizard/public/visualizations/pie_chart/index.ts b/src/plugins/wizard/public/visualizations/pie_chart/index.ts new file mode 100644 index 00000000000..b47965bc190 --- /dev/null +++ b/src/plugins/wizard/public/visualizations/pie_chart/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { VisualizationTypeOptions } from '../../services/type_service/visualization_type'; + +export const createPieChartConfig = (): VisualizationTypeOptions => { + return { + name: 'pie_chart', + title: 'Pie Chart', + icon: 'visPie', + contributions: {}, + }; +}; diff --git a/src/plugins/wizard/public/visualizations/xy_chart.tsx b/src/plugins/wizard/public/visualizations/xy_chart.tsx deleted file mode 100644 index 0215b31b2b4..00000000000 --- a/src/plugins/wizard/public/visualizations/xy_chart.tsx +++ /dev/null @@ -1,6 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -// TODO: This is where we register the visualizations using a registration service provided by the plugin From 71fa5fea07f2a6eac56b188792f9f15c546a1c1a Mon Sep 17 00:00:00 2001 From: Ashwin Pc Date: Tue, 29 Mar 2022 19:09:59 +0000 Subject: [PATCH 6/7] chore: updates viz modal snapshot Signed-off-by: Ashwin Pc --- .../__snapshots__/new_vis_modal.test.tsx.snap | 72 +++++++++---------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap index 4b5089249c8..2685e7cc8d2 100644 --- a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -264,10 +264,9 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
-

-

-

-

-

-
-
-
-

-

-

-

-

-
-
-
Date: Tue, 29 Mar 2022 21:06:22 +0000 Subject: [PATCH 7/7] fix(License): Fixes license headers Signed-off-by: Ashwin Pc --- .../components/data_tab/field_selector_field.tsx | 8 +++----- .../public/application/utils/get_top_nav_config.tsx | 8 +++----- .../wizard/public/services/type_service/type_service.ts | 8 +++----- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/plugins/wizard/public/application/components/data_tab/field_selector_field.tsx b/src/plugins/wizard/public/application/components/data_tab/field_selector_field.tsx index 396b89c0bb9..e545a6b33a6 100644 --- a/src/plugins/wizard/public/application/components/data_tab/field_selector_field.tsx +++ b/src/plugins/wizard/public/application/components/data_tab/field_selector_field.tsx @@ -4,6 +4,9 @@ * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. */ /* @@ -25,11 +28,6 @@ * under the License. */ -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - import React, { useState } from 'react'; import { IndexPatternField } from 'src/plugins/data/public'; import { FieldButton, FieldIcon } from '../../../../../opensearch_dashboards_react/public'; diff --git a/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx index 4d7c10d49e7..725f7f2baa9 100644 --- a/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx @@ -4,6 +4,9 @@ * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. */ /* @@ -25,11 +28,6 @@ * under the License. */ -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - import React from 'react'; import { i18n } from '@osd/i18n'; import { TopNavMenuData } from '../../../../navigation/public'; diff --git a/src/plugins/wizard/public/services/type_service/type_service.ts b/src/plugins/wizard/public/services/type_service/type_service.ts index bdc2a421c0a..d43d779f75e 100644 --- a/src/plugins/wizard/public/services/type_service/type_service.ts +++ b/src/plugins/wizard/public/services/type_service/type_service.ts @@ -4,6 +4,9 @@ * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. */ /* @@ -25,11 +28,6 @@ * under the License. */ -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - import { VisualizationType, VisualizationTypeOptions } from './visualization_type'; /**