From ed30799dbcc91f1dc9b49f6a781208fd5ba49c31 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Fri, 22 Sep 2023 15:34:44 -0700 Subject: [PATCH 1/6] Add table acceleration flyout Signed-off-by: Shenoy Pratik --- common/constants/index.ts | 22 +++ common/index.ts | 8 - common/types/index.ts | 41 ++++ .../create/caution_banner_callout.tsx | 17 ++ .../create/create_acceleration.tsx | 113 +++++++++++ .../create/create_acceleration_header.tsx | 36 ++++ .../selectors/index_type_selector.tsx | 54 +++++ .../selectors/source_selector.tsx | 124 ++++++++++++ .../covering_index/covering_index_builder.tsx | 94 +++++++++ .../visual_editors/index_setting_options.tsx | 160 +++++++++++++++ .../visual_editors/query_builder.tsx | 65 ++++++ .../visual_editors/query_visual_editor.tsx | 43 ++++ .../skipping_index/add_fields_modal.tsx | 102 ++++++++++ .../skipping_index/delete_fields_modal.tsx | 104 ++++++++++ .../skipping_index/skipping_index_builder.tsx | 185 ++++++++++++++++++ public/plugin.ts | 3 +- 16 files changed, 1161 insertions(+), 10 deletions(-) create mode 100644 common/constants/index.ts delete mode 100644 common/index.ts create mode 100644 common/types/index.ts create mode 100644 public/components/acceleration/create/caution_banner_callout.tsx create mode 100644 public/components/acceleration/create/create_acceleration.tsx create mode 100644 public/components/acceleration/create/create_acceleration_header.tsx create mode 100644 public/components/acceleration/selectors/index_type_selector.tsx create mode 100644 public/components/acceleration/selectors/source_selector.tsx create mode 100644 public/components/acceleration/visual_editors/covering_index/covering_index_builder.tsx create mode 100644 public/components/acceleration/visual_editors/index_setting_options.tsx create mode 100644 public/components/acceleration/visual_editors/query_builder.tsx create mode 100644 public/components/acceleration/visual_editors/query_visual_editor.tsx create mode 100644 public/components/acceleration/visual_editors/skipping_index/add_fields_modal.tsx create mode 100644 public/components/acceleration/visual_editors/skipping_index/delete_fields_modal.tsx create mode 100644 public/components/acceleration/visual_editors/skipping_index/skipping_index_builder.tsx diff --git a/common/constants/index.ts b/common/constants/index.ts new file mode 100644 index 00000000..7083779a --- /dev/null +++ b/common/constants/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const PLUGIN_ID = 'queryWorkbenchDashboards'; +export const PLUGIN_NAME = 'Query Workbench'; +export const OPENSEARCH_ACC_DOCUMENTATION_URL = 'https://opensearch.org/docs/latest'; + +export const ACCELERATION_INDEX_TYPES = [ + { label: 'Skipping Index', value: 'skipping' }, + { label: 'Covering Index', value: 'covering' }, + { label: 'Materialized View', value: 'materialized' }, +]; + +export const ACCELERATION_AGGREGRATION_FUNCTIONS = [ + { label: 'count', value: 'count' }, + { label: 'sum', value: 'sum' }, + { label: 'avg', value: 'avg' }, + { label: 'max', value: 'max' }, + { label: 'min', value: 'min' }, +]; diff --git a/common/index.ts b/common/index.ts deleted file mode 100644 index a3d39b17..00000000 --- a/common/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - - -export const PLUGIN_ID = 'queryWorkbenchDashboards'; -export const PLUGIN_NAME = 'Query Workbench'; diff --git a/common/types/index.ts b/common/types/index.ts new file mode 100644 index 00000000..f07b6cf7 --- /dev/null +++ b/common/types/index.ts @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export interface MaterializedViewColumn { + id: string; + functionName: 'count' | 'sum' | 'avg' | 'min' | 'max'; + functionParam: string; + fieldAlias?: string; +} + +export interface SkippingIndexRowType { + id: string; + fieldName: string; + dataType: string; + accelerationMethod: 'PARTITION' | 'VALUE_SET' | 'MIN_MAX'; +} + +export interface DataTableFieldsType { + id: string; + fieldName: string; + dataType: string; +} + +export interface CreateAccelerationForm { + dataSource: string; + dataTable: string; + dataTableFields: DataTableFieldsType[]; + accelerationIndexType: 'skipping' | 'covering' | 'materialized'; + queryBuilderType: 'visual' | 'code'; + skippingIndexQueryData: SkippingIndexRowType[]; + coveringIndexQueryData: string; + materializedViewQueryData: string; + accelerationIndexName: string; + accelerationIndexAlias: string; + primaryShardsCount: number; + replicaShardsCount: number; + refreshType: 'interval' | 'auto'; + refreshIntervalSeconds: string | undefined; +} diff --git a/public/components/acceleration/create/caution_banner_callout.tsx b/public/components/acceleration/create/caution_banner_callout.tsx new file mode 100644 index 00000000..2c97f82a --- /dev/null +++ b/public/components/acceleration/create/caution_banner_callout.tsx @@ -0,0 +1,17 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiCallOut } from '@elastic/eui'; +import React from 'react'; + +export const CautionBannerCallout = () => { + return ( + +

+ Warning about not indexing personal or sensitive data, something about the cost of indexing. +

+
+ ); +}; diff --git a/public/components/acceleration/create/create_acceleration.tsx b/public/components/acceleration/create/create_acceleration.tsx new file mode 100644 index 00000000..b840f848 --- /dev/null +++ b/public/components/acceleration/create/create_acceleration.tsx @@ -0,0 +1,113 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiSpacer, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiFlyoutFooter, + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { CreateAccelerationHeader } from './create_acceleration_header'; +import { CautionBannerCallout } from './caution_banner_callout'; +import { AccelerationDataSourceSelector } from '../selectors/source_selector'; +import { IndexTypeSelector } from '../selectors/index_type_selector'; +import { CreateAccelerationForm } from '../../../../common/types/'; +import { QueryVisualEditor } from '../visual_editors/query_visual_editor'; +import { accelerationQueryBuilder } from '../visual_editors/query_builder'; + +export interface CreateAccelerationProps { + dataSource: string; + setIsFlyoutVisible(visible: boolean): void; + updateQueries: (query: string) => void; +} + +export const CreateAcceleration = ({ + dataSource, + setIsFlyoutVisible, + updateQueries, +}: CreateAccelerationProps) => { + const [accelerationFormData, setAccelerationFormData] = useState({ + dataSource: '', + dataTable: '', + dataTableFields: [], + accelerationIndexType: 'skipping', + queryBuilderType: 'visual', + skippingIndexQueryData: [], + coveringIndexQueryData: '', + materializedViewQueryData: '', + accelerationIndexName: '', + accelerationIndexAlias: '', + primaryShardsCount: 5, + replicaShardsCount: 1, + refreshType: 'auto', + refreshIntervalSeconds: undefined, + }); + + const copyToEditor = () => { + updateQueries(accelerationQueryBuilder(accelerationFormData)); + }; + + return ( + <> + setIsFlyoutVisible(false)} + aria-labelledby="flyoutTitle" + size="m" + > + + + + + + + + + + + + + + + setIsFlyoutVisible(false)} + flush="left" + > + Close + + + + { + copyToEditor(); + setIsFlyoutVisible(false); + }} + fill + > + Copy Query to Editor + + + + + + + ); +}; diff --git a/public/components/acceleration/create/create_acceleration_header.tsx b/public/components/acceleration/create/create_acceleration_header.tsx new file mode 100644 index 00000000..8c4fc968 --- /dev/null +++ b/public/components/acceleration/create/create_acceleration_header.tsx @@ -0,0 +1,36 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, + EuiSpacer, + EuiText, + EuiLink, +} from '@elastic/eui'; +import React from 'react'; +import { OPENSEARCH_ACC_DOCUMENTATION_URL } from '../../../../common/constants'; + +export const CreateAccelerationHeader = () => { + return ( +
+ + + +

Create Acceleration Index

+
+
+
+ + + Create OpenSearch Indexes from external data connections for better performance.{' '} + + Learn more + + +
+ ); +}; diff --git a/public/components/acceleration/selectors/index_type_selector.tsx b/public/components/acceleration/selectors/index_type_selector.tsx new file mode 100644 index 00000000..33750b52 --- /dev/null +++ b/public/components/acceleration/selectors/index_type_selector.tsx @@ -0,0 +1,54 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiComboBox, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui'; +import React, { useState } from 'react'; +import { ACCELERATION_INDEX_TYPES } from '../../../../common/constants'; +import { CreateAccelerationForm } from '../../../../common/types'; + +interface Indextypes { + label: string; + value: string; +} + +interface IndexTypeSelectorProps { + accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; +} + +export const IndexTypeSelector = ({ + accelerationFormData, + setAccelerationFormData, +}: IndexTypeSelectorProps) => { + const [selectedIndexType, setSelectedIndexType] = useState([ + ACCELERATION_INDEX_TYPES[0], + ]); + return ( + <> + +

Define index

+
+ + + { + setAccelerationFormData({ + ...accelerationFormData, + accelerationIndexType: indexType[0].value, + }); + setSelectedIndexType(indexType); + }} + /> + + + ); +}; diff --git a/public/components/acceleration/selectors/source_selector.tsx b/public/components/acceleration/selectors/source_selector.tsx new file mode 100644 index 00000000..4dfc9d6d --- /dev/null +++ b/public/components/acceleration/selectors/source_selector.tsx @@ -0,0 +1,124 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiComboBox, EuiFormRow, EuiSpacer, EuiText, htmlIdGenerator } from '@elastic/eui'; +import React, { useState } from 'react'; +import { useEffect } from 'react'; +import { CreateAccelerationForm } from '../../../../common/types'; + +interface DataSourceTypes { + label: string; +} + +interface AccelerationDataSourceSelectorProps { + accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; +} + +export const AccelerationDataSourceSelector = ({ + accelerationFormData, + setAccelerationFormData, +}: AccelerationDataSourceSelectorProps) => { + const [dataConnections, setDataConnections] = useState([]); + const [selectedDataConnection, setSelectedDataConnection] = useState([]); + const [tables, setTables] = useState([]); + const [selectedTable, setSelectedTable] = useState([]); + + useEffect(() => { + setDataConnections([ + { + label: 'spark1', + }, + { + label: 'spark2', + }, + ]); + }, []); + + useEffect(() => { + if (accelerationFormData.dataSource !== '') { + setTables([ + { + label: 'Table1', + }, + { + label: 'Table2', + }, + ]); + } + }, [accelerationFormData.dataSource]); + + useEffect(() => { + if (accelerationFormData.dataTable !== '') { + const idPrefix = htmlIdGenerator()(); + setAccelerationFormData({ + ...accelerationFormData, + dataTableFields: [ + { id: `${idPrefix}1`, fieldName: 'Field 1', dataType: 'Integer' }, + { id: `${idPrefix}2`, fieldName: 'Field 2', dataType: 'Integer' }, + { id: `${idPrefix}3`, fieldName: 'Field 3', dataType: 'Integer' }, + { id: `${idPrefix}4`, fieldName: 'Field 4', dataType: 'Integer' }, + { id: `${idPrefix}5`, fieldName: 'Field 5', dataType: 'Integer' }, + { id: `${idPrefix}6`, fieldName: 'Field 6', dataType: 'Integer' }, + { id: `${idPrefix}7`, fieldName: 'Field 7', dataType: 'Integer' }, + { id: `${idPrefix}8`, fieldName: 'Field 8', dataType: 'Integer' }, + { id: `${idPrefix}9`, fieldName: 'Field 9', dataType: 'Integer' }, + { id: `${idPrefix}10`, fieldName: 'Field 10', dataType: 'Integer' }, + { id: `${idPrefix}11`, fieldName: 'Field 11', dataType: 'Integer' }, + ], + }); + } + }, [accelerationFormData.dataTable]); + + return ( + <> + +

Select data connection

+
+ + + Select data connection where the data you want to accelerate resides.{' '} + + + + { + setAccelerationFormData({ + ...accelerationFormData, + dataSource: dataConnectionOptions[0].label, + }); + setSelectedDataConnection(dataConnectionOptions); + }} + /> + + + { + setAccelerationFormData({ + ...accelerationFormData, + dataTable: tableOptions[0].label, + }); + setSelectedTable(tableOptions); + }} + /> + + + + ); +}; diff --git a/public/components/acceleration/visual_editors/covering_index/covering_index_builder.tsx b/public/components/acceleration/visual_editors/covering_index/covering_index_builder.tsx new file mode 100644 index 00000000..bb4d7994 --- /dev/null +++ b/public/components/acceleration/visual_editors/covering_index/covering_index_builder.tsx @@ -0,0 +1,94 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiComboBox, + EuiExpression, + EuiPopover, + EuiPopoverTitle, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import React, { useEffect, useState } from 'react'; +import { CreateAccelerationForm } from '../../../../../common/types'; +import _ from 'lodash'; + +interface CoveringIndexBuilderProps { + accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; +} + +export const CoveringIndexBuilder = ({ + accelerationFormData, + setAccelerationFormData, +}: CoveringIndexBuilderProps) => { + const [isPopOverOpen, setIsPopOverOpen] = useState(false); + const [columnsValue, setColumnsValue] = useState(''); + const [selectedOptions, setSelected] = useState([]); + + const onChange = (selectedOptions) => { + setSelected(selectedOptions); + }; + + useEffect(() => { + let expresseionValue = '(add columns here)'; + if (selectedOptions.length > 0) { + expresseionValue = + '(' + + _.reduce( + selectedOptions, + function (columns, n, index) { + const columnValue = columns + `${n.label}`; + if (index !== selectedOptions.length - 1) return `${columnValue}, `; + else return columnValue; + }, + '' + ) + + ')'; + } + setColumnsValue(expresseionValue); + }, [selectedOptions]); + + return ( + <> + +

Covering index definition

+
+ + + + setIsPopOverOpen(true)} + /> + } + isOpen={isPopOverOpen} + closePopover={() => setIsPopOverOpen(false)} + panelPaddingSize="s" + anchorPosition="downLeft" + > + <> + Columns + { + return { label: x.fieldName }; + })} + selectedOptions={selectedOptions} + onChange={onChange} + /> + + + + ); +}; diff --git a/public/components/acceleration/visual_editors/index_setting_options.tsx b/public/components/acceleration/visual_editors/index_setting_options.tsx new file mode 100644 index 00000000..ee458392 --- /dev/null +++ b/public/components/acceleration/visual_editors/index_setting_options.tsx @@ -0,0 +1,160 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState, ChangeEvent } from 'react'; +import { CreateAccelerationForm } from '../../../../common/types'; +import { + EuiFieldNumber, + EuiFieldText, + EuiFormRow, + EuiRadioGroup, + htmlIdGenerator, +} from '@elastic/eui'; + +const idPrefix = htmlIdGenerator()(); + +interface IndexSettingOptionsProps { + accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; +} + +export const IndexSettingOptions = ({ + accelerationFormData, + setAccelerationFormData, +}: IndexSettingOptionsProps) => { + const refreshOptions = [ + { + id: `${idPrefix}0`, + label: 'Auto Refresh', + }, + { + id: `${idPrefix}1`, + label: 'Refresh by interval', + }, + ]; + const [indexName, setIndexName] = useState(''); + const [indexAlias, setIndexAlias] = useState(''); + const [primaryShards, setPrimaryShards] = useState('5'); + const [replicaCount, setReplicaCount] = useState('1'); + const [refreshTypeSelected, setRefreshTypeSelected] = useState(`${idPrefix}0`); + const [refreshIntervalSeconds, setRefreshIntervalSeconds] = useState('1'); + + const onChangeIndexName = (e: ChangeEvent) => { + setAccelerationFormData({ ...accelerationFormData, accelerationIndexName: e.target.value }); + setIndexName(e.target.value); + }; + + const onChangeindexAlias = (e: ChangeEvent) => { + setAccelerationFormData({ ...accelerationFormData, accelerationIndexAlias: e.target.value }); + setIndexAlias(e.target.value); + }; + + const onChangePrimaryShards = (e: ChangeEvent) => { + setAccelerationFormData({ ...accelerationFormData, primaryShardsCount: +e.target.value }); + setPrimaryShards(e.target.value); + }; + + const onChangeReplicaCount = (e: ChangeEvent) => { + setAccelerationFormData({ ...accelerationFormData, replicaShardsCount: +e.target.value }); + setReplicaCount(e.target.value); + }; + + const onChangeRefreshType = (optionId: React.SetStateAction) => { + setAccelerationFormData({ + ...accelerationFormData, + refreshType: optionId === `${idPrefix}0` ? 'auto' : 'interval', + }); + setRefreshTypeSelected(optionId); + }; + + const onChangeRefreshIntervalSeconds = (e: ChangeEvent) => { + setAccelerationFormData({ + ...accelerationFormData, + refreshIntervalSeconds: e.target.value + 's', + }); + setRefreshIntervalSeconds(e.target.value); + }; + + return ( + <> + + onChangeIndexName(e)} + aria-label="Enter Index Name" + /> + + + onChangeindexAlias(e)} + aria-label="Enter Index alias" + /> + + + onChangePrimaryShards(e)} + aria-label="Number of primary shards" + min={1} + max={100} + /> + + + onChangeReplicaCount(e)} + aria-label="Number of replicas" + min={0} + max={100} + /> + + + onChangeRefreshType(id)} + name="refresh type radio group" + /> + + + {refreshTypeSelected === `${idPrefix}1` && ( + + onChangeRefreshIntervalSeconds(e)} + aria-label="Refresh interval" + append={'second(s)'} + min={1} + /> + + )} + + ); +}; diff --git a/public/components/acceleration/visual_editors/query_builder.tsx b/public/components/acceleration/visual_editors/query_builder.tsx new file mode 100644 index 00000000..a366a5f9 --- /dev/null +++ b/public/components/acceleration/visual_editors/query_builder.tsx @@ -0,0 +1,65 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import _ from 'lodash'; +import { CreateAccelerationForm, SkippingIndexRowType } from '../../../../common/types'; + +const buildSkippingIndexColumns = (skippingIndexQueryData: SkippingIndexRowType[]) => { + return _.reduce( + skippingIndexQueryData, + function (columns, n, index) { + const columnValue = columns + ` ${n.fieldName} ${n.accelerationMethod}`; + if (index !== skippingIndexQueryData.length - 1) return `${columnValue}, \n`; + else return `${columnValue} \n`; + }, + '' + ); +}; + +const skippingIndexQueryBuilder = (accelerationformData: CreateAccelerationForm) => { + /* + * Skipping Index Example + * + * CREATE SKIPPING INDEX ON table_name + * FOR COLUMNS ( + * field1 VALUE_SET, + * field2 PARTITION, + * field3 MIN_MAX, + * ) + */ + console.log('index builder started'); + let codeQuery = 'CREATE SKIPPING INDEX ON ' + accelerationformData.dataTable; + codeQuery = codeQuery + '\n FOR COLUMNS ( \n'; + codeQuery = codeQuery + buildSkippingIndexColumns(accelerationformData.skippingIndexQueryData); + codeQuery = codeQuery + ')'; + console.log('index builder finished', codeQuery); + return codeQuery; +}; + +const coveringIndexQueryBuilder = (accelerationformData: CreateAccelerationForm) => { + return ''; +}; + +const materializedQueryViewBuilder = (accelerationformData: CreateAccelerationForm) => { + return ''; +}; + +export const accelerationQueryBuilder = (accelerationformData: CreateAccelerationForm) => { + console.log('accelerationQueryBuilder started', accelerationformData); + switch (accelerationformData.accelerationIndexType) { + case 'skipping': { + return skippingIndexQueryBuilder(accelerationformData); + } + case 'covering': { + return coveringIndexQueryBuilder(accelerationformData); + } + case 'materialized': { + return materializedQueryViewBuilder(accelerationformData); + } + default: { + return ''; + } + } +}; diff --git a/public/components/acceleration/visual_editors/query_visual_editor.tsx b/public/components/acceleration/visual_editors/query_visual_editor.tsx new file mode 100644 index 00000000..d8f933f8 --- /dev/null +++ b/public/components/acceleration/visual_editors/query_visual_editor.tsx @@ -0,0 +1,43 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiSpacer } from '@elastic/eui'; +import { CreateAccelerationForm } from '../../../../common/types'; +import { IndexSettingOptions } from './index_setting_options'; +import { SkippingIndexBuilder } from './skipping_index/skipping_index_builder'; +import { CoveringIndexBuilder } from './covering_index/covering_index_builder'; + +interface QueryVisualEditorProps { + accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; +} + +export const QueryVisualEditor = ({ + accelerationFormData, + setAccelerationFormData, +}: QueryVisualEditorProps) => { + return ( + <> + + + {accelerationFormData.accelerationIndexType === 'skipping' && ( + + )} + {accelerationFormData.accelerationIndexType === 'covering' && ( + + )} + + ); +}; diff --git a/public/components/acceleration/visual_editors/skipping_index/add_fields_modal.tsx b/public/components/acceleration/visual_editors/skipping_index/add_fields_modal.tsx new file mode 100644 index 00000000..d8318dee --- /dev/null +++ b/public/components/acceleration/visual_editors/skipping_index/add_fields_modal.tsx @@ -0,0 +1,102 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, + EuiButton, + EuiInMemoryTable, + EuiTableFieldDataColumnType, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { CreateAccelerationForm, DataTableFieldsType } from '../../../../../common/types'; +import _ from 'lodash'; + +interface AddFieldsModalProps { + setIsAddModalVisible: React.Dispatch>; + accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; +} + +export const AddFieldsModal = ({ + setIsAddModalVisible, + accelerationFormData, + setAccelerationFormData, +}: AddFieldsModalProps) => { + const [selectedFields, setSelectedFields] = useState([]); + + const tableColumns = [ + { + field: 'fieldName', + name: 'Field name', + sortable: true, + truncateText: true, + }, + { + field: 'dataType', + name: 'Datatype', + sortable: true, + truncateText: true, + }, + ] as Array>; + + const pagination = { + initialPageSize: 20, + pageSizeOptions: [10, 20, 50], + }; + + return ( + setIsAddModalVisible(false)}> + + +

Add fields

+
+
+ + + setSelectedFields(items), + }} + /> + + + + setIsAddModalVisible(false)}>Cancel + { + setAccelerationFormData({ + ...accelerationFormData, + skippingIndexQueryData: [ + ...accelerationFormData.skippingIndexQueryData, + ...selectedFields.map((x) => { + return { ...x, accelerationMethod: 'PARTITION' }; + }), + ], + }); + setIsAddModalVisible(false); + }} + fill + > + Add + + +
+ ); +}; diff --git a/public/components/acceleration/visual_editors/skipping_index/delete_fields_modal.tsx b/public/components/acceleration/visual_editors/skipping_index/delete_fields_modal.tsx new file mode 100644 index 00000000..5b078bca --- /dev/null +++ b/public/components/acceleration/visual_editors/skipping_index/delete_fields_modal.tsx @@ -0,0 +1,104 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState } from 'react'; +import { + EuiTableFieldDataColumnType, + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiInMemoryTable, + EuiModalFooter, + EuiButton, +} from '@elastic/eui'; +import _ from 'lodash'; +import { CreateAccelerationForm, SkippingIndexRowType } from '../../../../../common/types'; + +interface AddFieldsModalProps { + setIsDeleteModalVisible: React.Dispatch>; + accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; +} + +export const DeleteFieldsModal = ({ + setIsDeleteModalVisible, + accelerationFormData, + setAccelerationFormData, +}: AddFieldsModalProps) => { + const [selectedFields, setSelectedFields] = useState([]); + + const tableColumns = [ + { + field: 'fieldName', + name: 'Field name', + sortable: true, + truncateText: true, + }, + { + field: 'dataType', + name: 'Datatype', + sortable: true, + truncateText: true, + }, + { + field: 'accelerationMethod', + name: 'Acceleration method', + sortable: true, + truncateText: true, + }, + ] as Array>; + + const pagination = { + initialPageSize: 20, + pageSizeOptions: [10, 20, 50], + }; + + return ( + setIsDeleteModalVisible(false)}> + + +

Bulk delete

+
+
+ + + setSelectedFields(items), + }} + /> + + + + setIsDeleteModalVisible(false)}>Cancel + { + setAccelerationFormData({ + ...accelerationFormData, + skippingIndexQueryData: _.differenceBy( + accelerationFormData.skippingIndexQueryData, + selectedFields, + 'id' + ), + }); + setIsDeleteModalVisible(false); + }} + color="danger" + fill + > + Delete + + +
+ ); +}; diff --git a/public/components/acceleration/visual_editors/skipping_index/skipping_index_builder.tsx b/public/components/acceleration/visual_editors/skipping_index/skipping_index_builder.tsx new file mode 100644 index 00000000..0c22baf2 --- /dev/null +++ b/public/components/acceleration/visual_editors/skipping_index/skipping_index_builder.tsx @@ -0,0 +1,185 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useEffect, useState } from 'react'; +import { + EuiBasicTable, + EuiSelect, + EuiSpacer, + EuiText, + EuiButtonIcon, + EuiButton, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import _ from 'lodash'; +import { CreateAccelerationForm, SkippingIndexRowType } from '../../../../../common/types'; +import { AddFieldsModal } from './add_fields_modal'; +import { DeleteFieldsModal } from './delete_fields_modal'; + +interface SkippingIndexBuilderProps { + accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; +} + +export const SkippingIndexBuilder = ({ + accelerationFormData, + setAccelerationFormData, +}: SkippingIndexBuilderProps) => { + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(20); + const [totalItemCount, setTotalItemCount] = useState(0); + + const [isAddModalVisible, setIsAddModalVisible] = useState(false); + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + + const accelerationMethods = [ + { value: 'PARTITION', text: 'Partition' }, + { value: 'VALUE_SET', text: 'Value Set' }, + { value: 'MIN_MAX', text: 'Min Max' }, + ]; + + let modal; + + if (isAddModalVisible) + modal = ( + + ); + + if (isDeleteModalVisible) + modal = ( + + ); + + const onTableChange = (page: { index: number; size: number }) => { + setPageIndex(page.index); + setPageSize(page.size); + }; + + const onChangeAccelerationMethod = ( + e: { target: { value: 'PARTITION' | 'VALUE_SET' | 'MIN_MAX' } }, + updateRow: SkippingIndexRowType + ) => { + setAccelerationFormData({ + ...accelerationFormData, + skippingIndexQueryData: _.map(accelerationFormData.skippingIndexQueryData, (row) => + row.id === updateRow.id ? { ...row, accelerationMethod: e.target.value } : row + ), + }); + }; + + const columns = [ + { + field: 'fieldName', + name: 'Field name', + sortable: true, + truncateText: true, + }, + { + field: 'dataType', + name: 'Datatype', + sortable: true, + truncateText: true, + }, + { + name: 'Acceleration method', + render: (item: SkippingIndexRowType) => ( + onChangeAccelerationMethod(e, item)} + aria-label="Use aria labels when no actual label is in use" + /> + ), + }, + { + name: 'Delete', + render: (item: SkippingIndexRowType) => { + return ( + { + setAccelerationFormData({ + ...accelerationFormData, + skippingIndexQueryData: _.filter( + accelerationFormData.skippingIndexQueryData, + (o) => item.id !== o.id + ), + }); + }} + iconType="trash" + color="danger" + /> + ); + }, + }, + ]; + + const pagination = { + pageIndex, + pageSize, + totalItemCount, + pageSizeOptions: [10, 20, 50], + }; + + useEffect(() => { + if (accelerationFormData.dataTableFields.length > 0) { + const tableRows: SkippingIndexRowType[] = [ + { + ...accelerationFormData.dataTableFields[0], + accelerationMethod: 'PARTITION', + }, + ]; + setAccelerationFormData({ ...accelerationFormData, skippingIndexQueryData: tableRows }); + } else { + setAccelerationFormData({ ...accelerationFormData, skippingIndexQueryData: [] }); + } + }, [accelerationFormData.dataTableFields]); + + useEffect(() => { + setTotalItemCount(accelerationFormData.skippingIndexQueryData.length); + }, [accelerationFormData.skippingIndexQueryData]); + + return ( + <> + +

Skipping Index Builder

+
+ + onTableChange(page)} + hasActions={true} + /> + + + setIsAddModalVisible(true)}> + Add fields + + + + setIsDeleteModalVisible(true)} color="danger"> + Bulk delete + + + + {modal} + + ); +}; diff --git a/public/plugin.ts b/public/plugin.ts index 11a082df..10836ae1 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -3,10 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ - import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '../../../src/core/public'; import { WorkbenchPluginSetup, WorkbenchPluginStart, AppPluginStartDependencies } from './types'; -import { PLUGIN_NAME } from '../common'; +import { PLUGIN_NAME } from '../common/constants'; export class WorkbenchPlugin implements Plugin { public setup(core: CoreSetup): WorkbenchPluginSetup { From 4e48568488b03d0e0d8acde86ce92a3da1d3ccfd Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Fri, 22 Sep 2023 15:57:00 -0700 Subject: [PATCH 2/6] comment on hardcoded elements Signed-off-by: Shenoy Pratik --- public/components/acceleration/selectors/source_selector.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/components/acceleration/selectors/source_selector.tsx b/public/components/acceleration/selectors/source_selector.tsx index 4dfc9d6d..150fd0fb 100644 --- a/public/components/acceleration/selectors/source_selector.tsx +++ b/public/components/acceleration/selectors/source_selector.tsx @@ -28,6 +28,7 @@ export const AccelerationDataSourceSelector = ({ useEffect(() => { setDataConnections([ + // TODO: remove hardcoded responses { label: 'spark1', }, @@ -40,6 +41,7 @@ export const AccelerationDataSourceSelector = ({ useEffect(() => { if (accelerationFormData.dataSource !== '') { setTables([ + // TODO: remove hardcoded responses { label: 'Table1', }, From 4c461dcbc13830f18305b77afeca12632bca2a0f Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Fri, 22 Sep 2023 15:58:29 -0700 Subject: [PATCH 3/6] additional comment on hardcoded Signed-off-by: Shenoy Pratik --- public/components/acceleration/selectors/source_selector.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/public/components/acceleration/selectors/source_selector.tsx b/public/components/acceleration/selectors/source_selector.tsx index 150fd0fb..1a82cff6 100644 --- a/public/components/acceleration/selectors/source_selector.tsx +++ b/public/components/acceleration/selectors/source_selector.tsx @@ -57,6 +57,7 @@ export const AccelerationDataSourceSelector = ({ const idPrefix = htmlIdGenerator()(); setAccelerationFormData({ ...accelerationFormData, + // TODO: remove hardcoded responses dataTableFields: [ { id: `${idPrefix}1`, fieldName: 'Field 1', dataType: 'Integer' }, { id: `${idPrefix}2`, fieldName: 'Field 2', dataType: 'Integer' }, From f483d9baa30488793a3de6217091937a8285030c Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Fri, 22 Sep 2023 16:21:40 -0700 Subject: [PATCH 4/6] remove console logs Signed-off-by: Shenoy Pratik --- .../components/acceleration/visual_editors/query_builder.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/public/components/acceleration/visual_editors/query_builder.tsx b/public/components/acceleration/visual_editors/query_builder.tsx index a366a5f9..c4e5b47f 100644 --- a/public/components/acceleration/visual_editors/query_builder.tsx +++ b/public/components/acceleration/visual_editors/query_builder.tsx @@ -29,12 +29,10 @@ const skippingIndexQueryBuilder = (accelerationformData: CreateAccelerationForm) * field3 MIN_MAX, * ) */ - console.log('index builder started'); let codeQuery = 'CREATE SKIPPING INDEX ON ' + accelerationformData.dataTable; codeQuery = codeQuery + '\n FOR COLUMNS ( \n'; codeQuery = codeQuery + buildSkippingIndexColumns(accelerationformData.skippingIndexQueryData); codeQuery = codeQuery + ')'; - console.log('index builder finished', codeQuery); return codeQuery; }; @@ -47,7 +45,6 @@ const materializedQueryViewBuilder = (accelerationformData: CreateAccelerationFo }; export const accelerationQueryBuilder = (accelerationformData: CreateAccelerationForm) => { - console.log('accelerationQueryBuilder started', accelerationformData); switch (accelerationformData.accelerationIndexType) { case 'skipping': { return skippingIndexQueryBuilder(accelerationformData); From 046d3e5747b60a3f71fa34c2eb87aeeec47235dd Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Mon, 25 Sep 2023 14:26:12 -0700 Subject: [PATCH 5/6] review fixes Signed-off-by: Shenoy Pratik --- opensearch_dashboards.json | 6 +++--- package.json | 2 +- .../covering_index/covering_index_builder.tsx | 13 +++++-------- .../visual_editors/index_setting_options.tsx | 16 ++++++++-------- .../skipping_index/add_fields_modal.tsx | 4 ++-- 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json index 8ce2b4f6..56b515ba 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -1,11 +1,11 @@ { "id": "queryWorkbenchDashboards", - "version": "3.0.0.0", - "opensearchDashboardsVersion": "3.0.0", + "version": "2.9.0.0", + "opensearchDashboardsVersion": "2.9.0", "server": true, "ui": true, "requiredPlugins": [ "navigation" ], "optionalPlugins": [] -} \ No newline at end of file +} diff --git a/package.json b/package.json index a083a0aa..789e2cb9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opensearch-query-workbench", - "version": "3.0.0.0", + "version": "2.9.0.0", "description": "Query Workbench", "main": "index.js", "license": "Apache-2.0", diff --git a/public/components/acceleration/visual_editors/covering_index/covering_index_builder.tsx b/public/components/acceleration/visual_editors/covering_index/covering_index_builder.tsx index bb4d7994..1e980650 100644 --- a/public/components/acceleration/visual_editors/covering_index/covering_index_builder.tsx +++ b/public/components/acceleration/visual_editors/covering_index/covering_index_builder.tsx @@ -11,7 +11,7 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { CreateAccelerationForm } from '../../../../../common/types'; import _ from 'lodash'; @@ -25,14 +25,10 @@ export const CoveringIndexBuilder = ({ setAccelerationFormData, }: CoveringIndexBuilderProps) => { const [isPopOverOpen, setIsPopOverOpen] = useState(false); - const [columnsValue, setColumnsValue] = useState(''); + const [columnsValue, setColumnsValue] = useState('(add columns here)'); const [selectedOptions, setSelected] = useState([]); - const onChange = (selectedOptions) => { - setSelected(selectedOptions); - }; - - useEffect(() => { + const onChange = (selectedOptions: any[]) => { let expresseionValue = '(add columns here)'; if (selectedOptions.length > 0) { expresseionValue = @@ -49,7 +45,8 @@ export const CoveringIndexBuilder = ({ ')'; } setColumnsValue(expresseionValue); - }, [selectedOptions]); + setSelected(selectedOptions); + }; return ( <> diff --git a/public/components/acceleration/visual_editors/index_setting_options.tsx b/public/components/acceleration/visual_editors/index_setting_options.tsx index ee458392..e895c862 100644 --- a/public/components/acceleration/visual_editors/index_setting_options.tsx +++ b/public/components/acceleration/visual_editors/index_setting_options.tsx @@ -36,8 +36,8 @@ export const IndexSettingOptions = ({ ]; const [indexName, setIndexName] = useState(''); const [indexAlias, setIndexAlias] = useState(''); - const [primaryShards, setPrimaryShards] = useState('5'); - const [replicaCount, setReplicaCount] = useState('1'); + const [primaryShards, setPrimaryShards] = useState(5); + const [replicaCount, setReplicaCount] = useState(1); const [refreshTypeSelected, setRefreshTypeSelected] = useState(`${idPrefix}0`); const [refreshIntervalSeconds, setRefreshIntervalSeconds] = useState('1'); @@ -53,12 +53,12 @@ export const IndexSettingOptions = ({ const onChangePrimaryShards = (e: ChangeEvent) => { setAccelerationFormData({ ...accelerationFormData, primaryShardsCount: +e.target.value }); - setPrimaryShards(e.target.value); + setPrimaryShards(+e.target.value); }; const onChangeReplicaCount = (e: ChangeEvent) => { setAccelerationFormData({ ...accelerationFormData, replicaShardsCount: +e.target.value }); - setReplicaCount(e.target.value); + setReplicaCount(+e.target.value); }; const onChangeRefreshType = (optionId: React.SetStateAction) => { @@ -86,7 +86,7 @@ export const IndexSettingOptions = ({ onChangeIndexName(e)} + onChange={onChangeIndexName} aria-label="Enter Index Name" /> @@ -122,7 +122,7 @@ export const IndexSettingOptions = ({ onChangeReplicaCount(e)} + onChange={onChangeReplicaCount} aria-label="Number of replicas" min={0} max={100} @@ -135,7 +135,7 @@ export const IndexSettingOptions = ({ onChangeRefreshType(id)} + onChange={onChangeRefreshType} name="refresh type radio group" /> @@ -148,7 +148,7 @@ export const IndexSettingOptions = ({ onChangeRefreshIntervalSeconds(e)} + onChange={onChangeRefreshIntervalSeconds} aria-label="Refresh interval" append={'second(s)'} min={1} diff --git a/public/components/acceleration/visual_editors/skipping_index/add_fields_modal.tsx b/public/components/acceleration/visual_editors/skipping_index/add_fields_modal.tsx index d8318dee..92e7c498 100644 --- a/public/components/acceleration/visual_editors/skipping_index/add_fields_modal.tsx +++ b/public/components/acceleration/visual_editors/skipping_index/add_fields_modal.tsx @@ -30,7 +30,7 @@ export const AddFieldsModal = ({ }: AddFieldsModalProps) => { const [selectedFields, setSelectedFields] = useState([]); - const tableColumns = [ + const tableColumns: Array> = [ { field: 'fieldName', name: 'Field name', @@ -43,7 +43,7 @@ export const AddFieldsModal = ({ sortable: true, truncateText: true, }, - ] as Array>; + ]; const pagination = { initialPageSize: 20, From b0f24f30380e3940f537c14bce27139664b71327 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Mon, 25 Sep 2023 14:45:19 -0700 Subject: [PATCH 6/6] revert version changes, inline type declare Signed-off-by: Shenoy Pratik --- opensearch_dashboards.json | 8 +++----- package.json | 2 +- .../visual_editors/skipping_index/delete_fields_modal.tsx | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json index 56b515ba..972bedd4 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -1,11 +1,9 @@ { "id": "queryWorkbenchDashboards", - "version": "2.9.0.0", - "opensearchDashboardsVersion": "2.9.0", + "version": "3.0.0.0", + "opensearchDashboardsVersion": "3.0.0", "server": true, "ui": true, - "requiredPlugins": [ - "navigation" - ], + "requiredPlugins": ["navigation"], "optionalPlugins": [] } diff --git a/package.json b/package.json index 789e2cb9..a083a0aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opensearch-query-workbench", - "version": "2.9.0.0", + "version": "3.0.0.0", "description": "Query Workbench", "main": "index.js", "license": "Apache-2.0", diff --git a/public/components/acceleration/visual_editors/skipping_index/delete_fields_modal.tsx b/public/components/acceleration/visual_editors/skipping_index/delete_fields_modal.tsx index 5b078bca..58a6fa79 100644 --- a/public/components/acceleration/visual_editors/skipping_index/delete_fields_modal.tsx +++ b/public/components/acceleration/visual_editors/skipping_index/delete_fields_modal.tsx @@ -30,7 +30,7 @@ export const DeleteFieldsModal = ({ }: AddFieldsModalProps) => { const [selectedFields, setSelectedFields] = useState([]); - const tableColumns = [ + const tableColumns: Array> = [ { field: 'fieldName', name: 'Field name', @@ -49,7 +49,7 @@ export const DeleteFieldsModal = ({ sortable: true, truncateText: true, }, - ] as Array>; + ]; const pagination = { initialPageSize: 20,