From 09c937377ccee2593447b73dd422e3fe7382ea46 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Mon, 25 Sep 2023 18:45:23 -0400 Subject: [PATCH] Add table acceleration flyout (#128) * Add table acceleration flyout Signed-off-by: Shenoy Pratik * comment on hardcoded elements Signed-off-by: Shenoy Pratik * additional comment on hardcoded Signed-off-by: Shenoy Pratik * remove console logs Signed-off-by: Shenoy Pratik * review fixes Signed-off-by: Shenoy Pratik * revert version changes, inline type declare Signed-off-by: Shenoy Pratik --------- Signed-off-by: Shenoy Pratik --- common/constants/index.ts | 22 +++ common/index.ts | 8 - common/types/index.ts | 41 ++++ opensearch_dashboards.json | 6 +- .../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 | 127 ++++++++++++ .../covering_index/covering_index_builder.tsx | 91 +++++++++ .../visual_editors/index_setting_options.tsx | 160 +++++++++++++++ .../visual_editors/query_builder.tsx | 62 ++++++ .../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 +- 17 files changed, 1160 insertions(+), 14 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/opensearch_dashboards.json b/opensearch_dashboards.json index 8ce2b4f6..972bedd4 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -4,8 +4,6 @@ "opensearchDashboardsVersion": "3.0.0", "server": true, "ui": true, - "requiredPlugins": [ - "navigation" - ], + "requiredPlugins": ["navigation"], "optionalPlugins": [] -} \ No newline at end of file +} 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..1a82cff6 --- /dev/null +++ b/public/components/acceleration/selectors/source_selector.tsx @@ -0,0 +1,127 @@ +/* + * 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([ + // TODO: remove hardcoded responses + { + label: 'spark1', + }, + { + label: 'spark2', + }, + ]); + }, []); + + useEffect(() => { + if (accelerationFormData.dataSource !== '') { + setTables([ + // TODO: remove hardcoded responses + { + label: 'Table1', + }, + { + label: 'Table2', + }, + ]); + } + }, [accelerationFormData.dataSource]); + + useEffect(() => { + if (accelerationFormData.dataTable !== '') { + 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' }, + { 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..1e980650 --- /dev/null +++ b/public/components/acceleration/visual_editors/covering_index/covering_index_builder.tsx @@ -0,0 +1,91 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiComboBox, + EuiExpression, + EuiPopover, + EuiPopoverTitle, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import React, { 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('(add columns here)'); + const [selectedOptions, setSelected] = useState([]); + + const onChange = (selectedOptions: any[]) => { + 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); + setSelected(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..e895c862 --- /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 ( + <> + + + + + onChangeindexAlias(e)} + aria-label="Enter Index alias" + /> + + + onChangePrimaryShards(e)} + aria-label="Number of primary shards" + min={1} + max={100} + /> + + + + + + + + + {refreshTypeSelected === `${idPrefix}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..c4e5b47f --- /dev/null +++ b/public/components/acceleration/visual_editors/query_builder.tsx @@ -0,0 +1,62 @@ +/* + * 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, + * ) + */ + let codeQuery = 'CREATE SKIPPING INDEX ON ' + accelerationformData.dataTable; + codeQuery = codeQuery + '\n FOR COLUMNS ( \n'; + codeQuery = codeQuery + buildSkippingIndexColumns(accelerationformData.skippingIndexQueryData); + codeQuery = codeQuery + ')'; + return codeQuery; +}; + +const coveringIndexQueryBuilder = (accelerationformData: CreateAccelerationForm) => { + return ''; +}; + +const materializedQueryViewBuilder = (accelerationformData: CreateAccelerationForm) => { + return ''; +}; + +export const accelerationQueryBuilder = (accelerationformData: CreateAccelerationForm) => { + 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..92e7c498 --- /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: Array> = [ + { + field: 'fieldName', + name: 'Field name', + sortable: true, + truncateText: true, + }, + { + field: 'dataType', + name: 'Datatype', + sortable: true, + truncateText: true, + }, + ]; + + 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..58a6fa79 --- /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: Array> = [ + { + 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, + }, + ]; + + 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 {