From 307a7c0ecfe677af4d916880d054714f438af67b Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Fri, 29 Sep 2023 10:34:54 -0700 Subject: [PATCH 1/2] add materialized view visual builder and query builders Signed-off-by: Shenoy Pratik --- common/constants/index.ts | 35 +++- common/types/index.ts | 34 +++- .../create/create_acceleration.tsx | 27 ++- .../create/create_acceleration_header.tsx | 2 +- .../components/acceleration/create/utils.tsx | 30 +++ .../selectors/define_index_options.tsx | 130 ++++++++++++ .../selectors/index_setting_options.tsx | 188 +++++++++++++++++ .../selectors/index_type_selector.tsx | 35 ++-- .../selectors/source_selector.tsx | 109 +++++++--- .../covering_index/covering_index_builder.tsx | 105 +++++----- .../visual_editors/index_setting_options.tsx | 160 --------------- .../materialized_view/add_column_popover.tsx | 146 +++++++++++++ .../materialized_view/column_expression.tsx | 180 +++++++++++++++++ .../group_by_tumble_expression.tsx | 118 +++++++++++ .../materialized_view_builder.tsx | 113 +++++++++++ .../visual_editors/query_builder.tsx | 191 +++++++++++++++--- .../visual_editors/query_visual_editor.tsx | 12 +- 17 files changed, 1307 insertions(+), 308 deletions(-) create mode 100644 public/components/acceleration/create/utils.tsx create mode 100644 public/components/acceleration/selectors/define_index_options.tsx create mode 100644 public/components/acceleration/selectors/index_setting_options.tsx delete mode 100644 public/components/acceleration/visual_editors/index_setting_options.tsx create mode 100644 public/components/acceleration/visual_editors/materialized_view/add_column_popover.tsx create mode 100644 public/components/acceleration/visual_editors/materialized_view/column_expression.tsx create mode 100644 public/components/acceleration/visual_editors/materialized_view/group_by_tumble_expression.tsx create mode 100644 public/components/acceleration/visual_editors/materialized_view/materialized_view_builder.tsx diff --git a/common/constants/index.ts b/common/constants/index.ts index 7083779a..3ca8dd41 100644 --- a/common/constants/index.ts +++ b/common/constants/index.ts @@ -6,6 +6,7 @@ export const PLUGIN_ID = 'queryWorkbenchDashboards'; export const PLUGIN_NAME = 'Query Workbench'; export const OPENSEARCH_ACC_DOCUMENTATION_URL = 'https://opensearch.org/docs/latest'; +export const ACC_INDEX_TYPE_DOCUMENTATION_URL = 'https://opensearch.org/docs/latest'; export const ACCELERATION_INDEX_TYPES = [ { label: 'Skipping Index', value: 'skipping' }, @@ -14,9 +15,33 @@ export const ACCELERATION_INDEX_TYPES = [ ]; 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' }, + { label: 'count' }, + { label: 'sum' }, + { label: 'avg' }, + { label: 'max' }, + { label: 'min' }, ]; + +export const ACCELERATION_TIME_INTERVAL = [ + { text: 'millisecond(s)', value: 'millisecond' }, + { text: 'second(s)', value: 'second' }, + { text: 'hour(s)', value: 'hour' }, + { text: 'day(s)', value: 'day' }, + { text: 'week(s)', value: 'week' }, +]; + +export const ACCELERATION_ADD_FIELDS_TEXT = '(add fields here)'; + +export const ACCELERATION_INDEX_NAME_INFO = `All OpenSearch acceleration indices have a naming format of pattern: \`prefix__suffix\`. They share a common prefix structure, which is \`flint____\`. Additionally, they may have a suffix that varies based on the index type. +##### Skipping Index +- For 'Skipping' indices, a fixed index name 'skipping' is used, and this name cannot be modified by the user. The suffix added to this type is \`_index\`. + - An example of a 'Skipping' index name would be: \`flint_mydatasource_mydb_mytable_skipping_index\`. +##### Covering Index +- 'Covering' indices allow users to specify their index name. The suffix added to this type is \`_index\`. + - For instance, a 'Covering' index name could be: \`flint_mydatasource_mydb_mytable_myindexname_index\`. +##### Materialized View Index +- 'Materialized View' indices also enable users to define their index name, but they do not have a suffix. + - An example of a 'Materialized View' index name might look like: \`flint_mydatasource_mydb_mytable_myindexname\`. +##### Note: +- All user given index names must be in lowercase letters. Cannot begin with underscores or hyphens. Spaces, commas, and characters :, ", *, +, /, \, |, ?, #, >, or < are not allowed. + `; diff --git a/common/types/index.ts b/common/types/index.ts index f07b6cf7..ed1783a9 100644 --- a/common/types/index.ts +++ b/common/types/index.ts @@ -3,9 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ +export type AggregationFunctionType = 'count' | 'sum' | 'avg' | 'max' | 'min'; + export interface MaterializedViewColumn { id: string; - functionName: 'count' | 'sum' | 'avg' | 'min' | 'max'; + functionName: AggregationFunctionType; functionParam: string; fieldAlias?: string; } @@ -23,19 +25,37 @@ export interface DataTableFieldsType { dataType: string; } +export interface RefreshIntervalType { + refreshWindow: number; + refreshInterval: string; +} + +export type AccelerationIndexType = 'skipping' | 'covering' | 'materialized'; + +export interface GroupByTumbleType { + timeField: string; + tumbleWindow: number; + tumbleInterval: string; +} + +export interface materializedViewQueryType { + columnsValues: MaterializedViewColumn[]; + groupByTumbleValue: GroupByTumbleType; +} + export interface CreateAccelerationForm { dataSource: string; + database: string; dataTable: string; dataTableFields: DataTableFieldsType[]; - accelerationIndexType: 'skipping' | 'covering' | 'materialized'; - queryBuilderType: 'visual' | 'code'; + accelerationIndexType: AccelerationIndexType; skippingIndexQueryData: SkippingIndexRowType[]; - coveringIndexQueryData: string; - materializedViewQueryData: string; + coveringIndexQueryData: string[]; + materializedViewQueryData: materializedViewQueryType; accelerationIndexName: string; - accelerationIndexAlias: string; primaryShardsCount: number; replicaShardsCount: number; refreshType: 'interval' | 'auto'; - refreshIntervalSeconds: string | undefined; + checkpointLocation: string | undefined; + refreshIntervalOptions: RefreshIntervalType | undefined; } diff --git a/public/components/acceleration/create/create_acceleration.tsx b/public/components/acceleration/create/create_acceleration.tsx index b840f848..767c390f 100644 --- a/public/components/acceleration/create/create_acceleration.tsx +++ b/public/components/acceleration/create/create_acceleration.tsx @@ -18,10 +18,12 @@ 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 { CreateAccelerationForm, materializedViewQueryType } from '../../../../common/types/'; import { QueryVisualEditor } from '../visual_editors/query_visual_editor'; import { accelerationQueryBuilder } from '../visual_editors/query_builder'; +import { IndexSettingOptions } from '../selectors/index_setting_options'; +import { DefineIndexOptions } from '../selectors/define_index_options'; +import { ACCELERATION_TIME_INTERVAL } from '../../../../common/constants'; export interface CreateAccelerationProps { dataSource: string; @@ -37,18 +39,21 @@ export const CreateAcceleration = ({ const [accelerationFormData, setAccelerationFormData] = useState({ dataSource: '', dataTable: '', + database: '', dataTableFields: [], accelerationIndexType: 'skipping', - queryBuilderType: 'visual', skippingIndexQueryData: [], - coveringIndexQueryData: '', - materializedViewQueryData: '', + coveringIndexQueryData: [], + materializedViewQueryData: {} as materializedViewQueryType, accelerationIndexName: '', - accelerationIndexAlias: '', primaryShardsCount: 5, replicaShardsCount: 1, refreshType: 'auto', - refreshIntervalSeconds: undefined, + checkpointLocation: undefined, + refreshIntervalOptions: { + refreshWindow: 1, + refreshInterval: ACCELERATION_TIME_INTERVAL[1].value, + }, }); const copyToEditor = () => { @@ -73,7 +78,13 @@ export const CreateAcceleration = ({ accelerationFormData={accelerationFormData} setAccelerationFormData={setAccelerationFormData} /> - + + + diff --git a/public/components/acceleration/create/create_acceleration_header.tsx b/public/components/acceleration/create/create_acceleration_header.tsx index 8c4fc968..44bdd788 100644 --- a/public/components/acceleration/create/create_acceleration_header.tsx +++ b/public/components/acceleration/create/create_acceleration_header.tsx @@ -20,7 +20,7 @@ export const CreateAccelerationHeader = () => { -

Create Acceleration Index

+

Accelerate data

diff --git a/public/components/acceleration/create/utils.tsx b/public/components/acceleration/create/utils.tsx new file mode 100644 index 00000000..3fe9f98a --- /dev/null +++ b/public/components/acceleration/create/utils.tsx @@ -0,0 +1,30 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const isTimePlural = (timeWindow: number) => { + return timeWindow > 1 ? 's' : ''; +}; + +export const validateIndexName = (value: string) => { + const lowercaseUnderscoreHyphenRegex = /^[a-z_\-]+$/; + + if (!lowercaseUnderscoreHyphenRegex.test(value)) { + return false; + } + + // Check if the value does not begin with underscores or hyphens + if (value.startsWith('_') || value.startsWith('-')) { + return false; + } + + // Check if the value does not contain disallowed characters + const disallowedCharacters = /[\s,:"*+\/|?#><]/; + if (disallowedCharacters.test(value)) { + return false; + } + + // If all checks pass, the value is valid + return true; +}; diff --git a/public/components/acceleration/selectors/define_index_options.tsx b/public/components/acceleration/selectors/define_index_options.tsx new file mode 100644 index 00000000..8d30b0c8 --- /dev/null +++ b/public/components/acceleration/selectors/define_index_options.tsx @@ -0,0 +1,130 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { ChangeEvent, useEffect, useState } from 'react'; +import { + EuiButton, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiIconTip, + EuiLink, + EuiMarkdownFormat, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiSpacer, + EuiText, + EuiHorizontalRule, +} from '@elastic/eui'; +import { CreateAccelerationForm } from '../../../../common/types'; +import { validateIndexName } from '../create/utils'; +import { ACCELERATION_INDEX_NAME_INFO } from '../../../../common/constants'; + +interface DefineIndexOptionsProps { + accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; +} + +export const DefineIndexOptions = ({ + accelerationFormData, + setAccelerationFormData, +}: DefineIndexOptionsProps) => { + const [indexName, setIndexName] = useState(''); + const [isModalVisible, setIsModalVisible] = useState(false); + + let modal; + + if (isModalVisible) { + modal = ( + setIsModalVisible(false)}> + + +

Acceleration index naming

+
+
+ + + + {ACCELERATION_INDEX_NAME_INFO} + + + + + setIsModalVisible(false)} fill> + Close + + +
+ ); + } + + const onChangeIndexName = (e: ChangeEvent) => { + setAccelerationFormData({ ...accelerationFormData, accelerationIndexName: e.target.value }); + setIndexName(e.target.value); + }; + + useEffect(() => { + accelerationFormData.accelerationIndexType === 'skipping' + ? setIndexName('skipping') + : setIndexName(''); + }, [accelerationFormData.accelerationIndexType]); + + const getPreprend = () => { + const dataSource = + accelerationFormData.dataSource !== '' + ? accelerationFormData.dataSource + : '{Datasource Name}'; + const database = + accelerationFormData.database !== '' ? accelerationFormData.database : '{Database Name}'; + const dataTable = + accelerationFormData.dataTable !== '' ? accelerationFormData.dataTable : '{Table Name}'; + const prependValue = `flint_${dataSource}_${database}_${dataTable}_`; + return [ + prependValue, + , + ]; + }; + + const getAppend = () => { + const appendValue = + accelerationFormData.accelerationIndexType === 'materialized' ? '' : '_index'; + return appendValue; + }; + + return ( + <> + +

Index settings

+
+ + + setIsModalVisible(true)}>Help + + } + > + + + {modal} + + ); +}; diff --git a/public/components/acceleration/selectors/index_setting_options.tsx b/public/components/acceleration/selectors/index_setting_options.tsx new file mode 100644 index 00000000..3f28d4ec --- /dev/null +++ b/public/components/acceleration/selectors/index_setting_options.tsx @@ -0,0 +1,188 @@ +/* + * 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, + EuiSelect, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { IndexTypeSelector } from './index_type_selector'; +import { ACCELERATION_TIME_INTERVAL } from '../../../../common/constants'; + +interface IndexSettingOptionsProps { + accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; +} + +export const IndexSettingOptions = ({ + accelerationFormData, + setAccelerationFormData, +}: IndexSettingOptionsProps) => { + const autoRefreshId = 'refresh-option-1'; + const intervalRefreshId = 'refresh-option-2'; + const refreshOptions = [ + { + id: autoRefreshId, + label: 'Auto Refresh', + }, + { + id: intervalRefreshId, + label: 'Refresh by interval', + }, + ]; + + const [primaryShards, setPrimaryShards] = useState(5); + const [replicaCount, setReplicaCount] = useState(1); + const [refreshTypeSelected, setRefreshTypeSelected] = useState(autoRefreshId); + const [refreshWindow, setRefreshWindow] = useState(1); + const [refreshInterval, setRefreshInterval] = useState(ACCELERATION_TIME_INTERVAL[1].value); + const [checkpoint, setCheckpoint] = useState(''); + + const onChangePrimaryShards = (e: ChangeEvent) => { + const countPrimaryShards = +e.target.value; + setAccelerationFormData({ ...accelerationFormData, primaryShardsCount: countPrimaryShards }); + setPrimaryShards(countPrimaryShards); + }; + + const onChangeReplicaCount = (e: ChangeEvent) => { + const replicaCount = +e.target.value; + setAccelerationFormData({ ...accelerationFormData, replicaShardsCount: replicaCount }); + setReplicaCount(replicaCount); + }; + + const onChangeRefreshType = (optionId: React.SetStateAction) => { + setAccelerationFormData({ + ...accelerationFormData, + refreshType: optionId === autoRefreshId ? 'auto' : 'interval', + }); + setRefreshTypeSelected(optionId); + }; + + const onChangeRefreshWindow = (e: ChangeEvent) => { + const windowCount = +e.target.value; + setAccelerationFormData({ + ...accelerationFormData, + refreshIntervalOptions: { + ...accelerationFormData.refreshIntervalOptions, + refreshWindow: windowCount, + }, + }); + setRefreshWindow(windowCount); + }; + + const onChangeRefreshInterval = (e: React.ChangeEvent) => { + const refreshIntervalValue = e.target.value; + setAccelerationFormData({ + ...accelerationFormData, + refreshIntervalOptions: { + ...accelerationFormData.refreshIntervalOptions, + refreshInterval: refreshIntervalValue, + }, + }); + setRefreshInterval(refreshIntervalValue); + }; + + const onChangeCheckpoint = (e: ChangeEvent) => { + const checkpointLocation = e.target.value; + setAccelerationFormData({ ...accelerationFormData, checkpointLocation: checkpointLocation }); + setCheckpoint(checkpointLocation); + }; + + return ( + <> + +

Index settings

+
+ + + + + + + + + + + + {refreshTypeSelected === intervalRefreshId && ( + + + } + /> + + )} + + + + + ); +}; diff --git a/public/components/acceleration/selectors/index_type_selector.tsx b/public/components/acceleration/selectors/index_type_selector.tsx index 33750b52..7210d818 100644 --- a/public/components/acceleration/selectors/index_type_selector.tsx +++ b/public/components/acceleration/selectors/index_type_selector.tsx @@ -3,15 +3,13 @@ * 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; -} +import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow, EuiLink, EuiText } from '@elastic/eui'; +import { + ACCELERATION_INDEX_TYPES, + ACC_INDEX_TYPE_DOCUMENTATION_URL, +} from '../../../../common/constants'; +import { AccelerationIndexType, CreateAccelerationForm } from '../../../../common/types'; interface IndexTypeSelectorProps { accelerationFormData: CreateAccelerationForm; @@ -22,31 +20,36 @@ export const IndexTypeSelector = ({ accelerationFormData, setAccelerationFormData, }: IndexTypeSelectorProps) => { - const [selectedIndexType, setSelectedIndexType] = useState([ + const [selectedIndexType, setSelectedIndexType] = useState[]>([ ACCELERATION_INDEX_TYPES[0], ]); return ( <> - -

Define index

-
- + + Help + + + } > { setAccelerationFormData({ ...accelerationFormData, - accelerationIndexType: indexType[0].value, + accelerationIndexType: indexType[0].value as AccelerationIndexType, }); setSelectedIndexType(indexType); }} + isInvalid={selectedIndexType.length === 0} + isClearable={false} /> diff --git a/public/components/acceleration/selectors/source_selector.tsx b/public/components/acceleration/selectors/source_selector.tsx index 1a82cff6..2cd59383 100644 --- a/public/components/acceleration/selectors/source_selector.tsx +++ b/public/components/acceleration/selectors/source_selector.tsx @@ -3,15 +3,18 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiComboBox, EuiFormRow, EuiSpacer, EuiText, htmlIdGenerator } from '@elastic/eui'; +import { + EuiComboBox, + EuiComboBoxOptionOption, + 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>; @@ -21,14 +24,18 @@ export const AccelerationDataSourceSelector = ({ accelerationFormData, setAccelerationFormData, }: AccelerationDataSourceSelectorProps) => { - const [dataConnections, setDataConnections] = useState([]); - const [selectedDataConnection, setSelectedDataConnection] = useState([]); - const [tables, setTables] = useState([]); - const [selectedTable, setSelectedTable] = useState([]); + const [dataConnections, setDataConnections] = useState[]>([]); + const [selectedDataConnection, setSelectedDataConnection] = useState< + EuiComboBoxOptionOption[] + >([]); + const [databases, setDatabases] = useState[]>([]); + const [selectedDatabase, setSelectedDatabase] = useState[]>([]); + const [tables, setTables] = useState[]>([]); + const [selectedTable, setSelectedTable] = useState[]>([]); useEffect(() => { + // TODO: remove hardcoded responses setDataConnections([ - // TODO: remove hardcoded responses { label: 'spark1', }, @@ -39,9 +46,23 @@ export const AccelerationDataSourceSelector = ({ }, []); useEffect(() => { + // TODO: remove hardcoded responses if (accelerationFormData.dataSource !== '') { + setDatabases([ + { + label: 'Database1', + }, + { + label: 'Database2', + }, + ]); + } + }, [accelerationFormData.dataSource]); + + useEffect(() => { + // TODO: remove hardcoded responses + if (accelerationFormData.database !== '') { setTables([ - // TODO: remove hardcoded responses { label: 'Table1', }, @@ -50,26 +71,27 @@ export const AccelerationDataSourceSelector = ({ }, ]); } - }, [accelerationFormData.dataSource]); + }, [accelerationFormData.database]); useEffect(() => { + // TODO: remove hardcoded responses 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' }, + { id: `${idPrefix}1`, fieldName: 'Field1', dataType: 'Integer' }, + { id: `${idPrefix}2`, fieldName: 'Field2', dataType: 'Integer' }, + { id: `${idPrefix}3`, fieldName: 'Field3', dataType: 'Integer' }, + { id: `${idPrefix}4`, fieldName: 'Field4', dataType: 'Integer' }, + { id: `${idPrefix}5`, fieldName: 'Field5', dataType: 'Integer' }, + { id: `${idPrefix}6`, fieldName: 'Field6', dataType: 'Integer' }, + { id: `${idPrefix}7`, fieldName: 'Field7', dataType: 'Integer' }, + { id: `${idPrefix}8`, fieldName: 'Field8', dataType: 'Integer' }, + { id: `${idPrefix}9`, fieldName: 'Field9', dataType: 'Integer' }, + { id: `${idPrefix}10`, fieldName: 'Field10', dataType: 'Integer' }, + { id: `${idPrefix}11`, fieldName: 'Field11', dataType: 'Integer' }, + { id: `${idPrefix}12`, fieldName: 'Field12', dataType: 'TimestampType' }, ], }); } @@ -78,19 +100,19 @@ export const AccelerationDataSourceSelector = ({ return ( <> -

Select data connection

+

Select data source

- Select data connection where the data you want to accelerate resides.{' '} + Select data connection where the data you want to accelerate resides. + + + { + setAccelerationFormData({ + ...accelerationFormData, + database: tableOptions[0].label, + }); + setSelectedDatabase(tableOptions); + }} + isInvalid={selectedDatabase.length === 0} + isClearable={false} /> - ); }; 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 1e980650..f2e528bc 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 @@ -10,10 +10,14 @@ import { EuiPopoverTitle, EuiSpacer, EuiText, + EuiFlexItem, + EuiFlexGroup, + EuiComboBoxOptionOption, } from '@elastic/eui'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { CreateAccelerationForm } from '../../../../../common/types'; import _ from 'lodash'; +import { ACCELERATION_ADD_FIELDS_TEXT } from '../../../../../common/constants'; interface CoveringIndexBuilderProps { accelerationFormData: CreateAccelerationForm; @@ -25,27 +29,20 @@ export const CoveringIndexBuilder = ({ setAccelerationFormData, }: CoveringIndexBuilderProps) => { const [isPopOverOpen, setIsPopOverOpen] = useState(false); - const [columnsValue, setColumnsValue] = useState('(add columns here)'); - const [selectedOptions, setSelected] = useState([]); + const [columnsValue, setColumnsValue] = useState(ACCELERATION_ADD_FIELDS_TEXT); + const [selectedOptions, setSelectedOptions] = useState([]); - const onChange = (selectedOptions: any[]) => { - let expresseionValue = '(add columns here)'; + const onChange = (selectedOptions: EuiComboBoxOptionOption[]) => { + let expressionValue = ACCELERATION_ADD_FIELDS_TEXT; 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; - }, - '' - ) + - ')'; + expressionValue = `(${selectedOptions.map((option) => option.label).join(', ')})`; } - setColumnsValue(expresseionValue); - setSelected(selectedOptions); + setAccelerationFormData({ + ...accelerationFormData, + coveringIndexQueryData: selectedOptions.map((option) => option.label), + }); + setColumnsValue(expressionValue); + setSelectedOptions(selectedOptions); }; return ( @@ -54,38 +51,50 @@ export const CoveringIndexBuilder = ({

Covering index definition

- - - + setIsPopOverOpen(true)} + description="CREATE INDEX" + value={accelerationFormData.accelerationIndexName} /> - } - isOpen={isPopOverOpen} - closePopover={() => setIsPopOverOpen(false)} - panelPaddingSize="s" - anchorPosition="downLeft" - > - <> - Columns - { - return { label: x.fieldName }; - })} - selectedOptions={selectedOptions} - onChange={onChange} + + + + + + - - + setIsPopOverOpen(true)} + isInvalid={columnsValue === ACCELERATION_ADD_FIELDS_TEXT} + /> + } + isOpen={isPopOverOpen} + closePopover={() => setIsPopOverOpen(false)} + panelPaddingSize="s" + anchorPosition="downLeft" + > + <> + Columns + ({ 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 deleted file mode 100644 index e895c862..00000000 --- a/public/components/acceleration/visual_editors/index_setting_options.tsx +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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/materialized_view/add_column_popover.tsx b/public/components/acceleration/visual_editors/materialized_view/add_column_popover.tsx new file mode 100644 index 00000000..f9194a0c --- /dev/null +++ b/public/components/acceleration/visual_editors/materialized_view/add_column_popover.tsx @@ -0,0 +1,146 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { ChangeEvent, useState } from 'react'; +import { + EuiPopover, + EuiButtonEmpty, + EuiPopoverTitle, + EuiPopoverFooter, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiFieldText, + EuiFormRow, + EuiComboBox, + htmlIdGenerator, + EuiSpacer, +} from '@elastic/eui'; + +import { + AggregationFunctionType, + CreateAccelerationForm, + MaterializedViewColumn, +} from '../../../../../common/types'; +import { ACCELERATION_AGGREGRATION_FUNCTIONS } from '../../../../../common/constants'; +import { useEffect } from 'react'; +import _ from 'lodash'; + +interface AddColumnPopOverProps { + isColumnPopOverOpen: boolean; + setIsColumnPopOverOpen: React.Dispatch>; + columnExpressionValues: MaterializedViewColumn[]; + setColumnExpressionValues: React.Dispatch>; + accelerationFormData: CreateAccelerationForm; +} + +export const AddColumnPopOver = ({ + isColumnPopOverOpen, + setIsColumnPopOverOpen, + columnExpressionValues, + setColumnExpressionValues, + accelerationFormData, +}: AddColumnPopOverProps) => { + const [selectedFunction, setSelectedFunction] = useState([ + ACCELERATION_AGGREGRATION_FUNCTIONS[0], + ]); + const [selectedField, setSelectedField] = useState([]); + const [selectedAlias, setSeletedAlias] = useState(''); + + const resetSelectedField = () => { + if (accelerationFormData.dataTableFields.length > 0) { + const defaultFieldName = accelerationFormData.dataTableFields[0].fieldName; + setSelectedField([{ label: defaultFieldName }]); + } + }; + + const resetValues = () => { + setSelectedFunction([ACCELERATION_AGGREGRATION_FUNCTIONS[0]]); + resetSelectedField(); + setSeletedAlias(''); + }; + + const onChangeAlias = (e: ChangeEvent) => { + setSeletedAlias(e.target.value); + }; + + useEffect(() => { + resetSelectedField(); + }, []); + + return ( + { + resetValues(); + setIsColumnPopOverOpen(!isColumnPopOverOpen); + }} + size="xs" + > + Add Column + + } + isOpen={isColumnPopOverOpen} + closePopover={() => setIsColumnPopOverOpen(false)} + > + Add Column + <> + + + + + + + + + ({ label: x.fieldName })), + ]} + selectedOptions={selectedField} + onChange={setSelectedField} + /> + + + + + + + + + + { + setColumnExpressionValues([ + ...columnExpressionValues, + { + id: htmlIdGenerator()(), + functionName: selectedFunction[0].label as AggregationFunctionType, + functionParam: selectedField[0].label, + fieldAlias: selectedAlias, + }, + ]); + setIsColumnPopOverOpen(false); + }} + > + Add + + + + ); +}; diff --git a/public/components/acceleration/visual_editors/materialized_view/column_expression.tsx b/public/components/acceleration/visual_editors/materialized_view/column_expression.tsx new file mode 100644 index 00000000..99e152c4 --- /dev/null +++ b/public/components/acceleration/visual_editors/materialized_view/column_expression.tsx @@ -0,0 +1,180 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useEffect, useState } from 'react'; +import { ACCELERATION_AGGREGRATION_FUNCTIONS } from '../../../../../common/constants'; +import { + MaterializedViewColumn, + CreateAccelerationForm, + AggregationFunctionType, +} from '../../../../../common/types'; +import _ from 'lodash'; +import { + EuiButtonIcon, + EuiComboBox, + EuiExpression, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiPopover, +} from '@elastic/eui'; + +interface ColumnExpressionProps { + index: number; + currentColumnExpressionValue: MaterializedViewColumn; + columnExpressionValues: MaterializedViewColumn[]; + setColumnExpressionValues: React.Dispatch>; + accelerationFormData: CreateAccelerationForm; +} + +export const ColumnExpression = ({ + index, + currentColumnExpressionValue, + columnExpressionValues, + setColumnExpressionValues, + accelerationFormData, +}: ColumnExpressionProps) => { + const [isFunctionPopOverOpen, setIsFunctionPopOverOpen] = useState(false); + const [isAliasPopOverOpen, setIsAliasPopOverOpen] = useState(false); + + const updateColumnExpressionValue = (newValue: MaterializedViewColumn, index: number) => { + const updatedArray = [...columnExpressionValues]; + updatedArray[index] = newValue; + setColumnExpressionValues(updatedArray); + }; + + return ( + + + + { + setIsAliasPopOverOpen(false); + setIsFunctionPopOverOpen(true); + }} + /> + } + isOpen={isFunctionPopOverOpen} + closePopover={() => setIsFunctionPopOverOpen(false)} + panelPaddingSize="s" + anchorPosition="downLeft" + > + <> + + + + + updateColumnExpressionValue( + { + ...currentColumnExpressionValue, + functionName: functionOption[0].label as AggregationFunctionType, + }, + index + ) + } + /> + + + + + ({ + label: x.fieldName, + })), + ]} + selectedOptions={[ + { + label: currentColumnExpressionValue.functionParam, + }, + ]} + onChange={(fieldOption) => + updateColumnExpressionValue( + { ...currentColumnExpressionValue, functionParam: fieldOption[0].label }, + index + ) + } + /> + + + + + + + {currentColumnExpressionValue.fieldAlias !== '' && ( + + { + setIsFunctionPopOverOpen(false); + setIsAliasPopOverOpen(true); + }} + /> + } + isOpen={isAliasPopOverOpen} + closePopover={() => setIsAliasPopOverOpen(false)} + panelPaddingSize="s" + anchorPosition="downLeft" + > + + + updateColumnExpressionValue( + { ...currentColumnExpressionValue, fieldAlias: e.target.value }, + index + ) + } + /> + + + + )} + + { + setColumnExpressionValues([ + ..._.filter( + columnExpressionValues, + (o) => o.id !== currentColumnExpressionValue.id + ), + ]); + }} + iconType="trash" + aria-label="delete-column-expression" + /> + + + + ); +}; diff --git a/public/components/acceleration/visual_editors/materialized_view/group_by_tumble_expression.tsx b/public/components/acceleration/visual_editors/materialized_view/group_by_tumble_expression.tsx new file mode 100644 index 00000000..6ff8a3ec --- /dev/null +++ b/public/components/acceleration/visual_editors/materialized_view/group_by_tumble_expression.tsx @@ -0,0 +1,118 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState, useEffect } from 'react'; +import { + EuiComboBox, + EuiExpression, + EuiFieldNumber, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiPopover, + EuiSelect, + EuiComboBoxOptionOption, +} from '@elastic/eui'; +import { ACCELERATION_TIME_INTERVAL } from '../../../../../common/constants'; +import { CreateAccelerationForm, GroupByTumbleType } from '../../../../../common/types'; +import _ from 'lodash'; +import { isTimePlural } from '../../create/utils'; + +interface GroupByTumbleExpressionProps { + accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; +} + +export const GroupByTumbleExpression = ({ + accelerationFormData, + setAccelerationFormData, +}: GroupByTumbleExpressionProps) => { + const [IsGroupPopOverOpen, setIsGroupPopOverOpen] = useState(false); + const [groupbyValues, setGroupByValues] = useState({ + timeField: '', + tumbleWindow: 1, + tumbleInterval: ACCELERATION_TIME_INTERVAL[0].value, + }); + + const onChangeTumbleWindow = (e: React.ChangeEvent) => { + setGroupByValues({ ...groupbyValues, tumbleWindow: +e.target.value }); + }; + + const onChangeTumbleInterval = (e: React.ChangeEvent) => { + setGroupByValues({ ...groupbyValues, tumbleInterval: e.target.value }); + }; + + const onChangeTimeField = (selectedOptions: EuiComboBoxOptionOption[]) => { + if (selectedOptions.length > 0) + setGroupByValues({ ...groupbyValues, timeField: selectedOptions[0].label }); + }; + + useEffect(() => { + setAccelerationFormData({ + ...accelerationFormData, + materializedViewQueryData: { + ...accelerationFormData.materializedViewQueryData, + groupByTumbleValue: groupbyValues, + }, + }); + }, [groupbyValues]); + + return ( + + setIsGroupPopOverOpen(true)} + isInvalid={groupbyValues.timeField === ''} + /> + } + isOpen={IsGroupPopOverOpen} + closePopover={() => setIsGroupPopOverOpen(false)} + panelPaddingSize="s" + anchorPosition="downLeft" + > + + + + value.dataType.includes('TimestampType')) + .map((value) => ({ label: value.fieldName }))} + selectedOptions={[{ label: groupbyValues.timeField }]} + onChange={onChangeTimeField} + /> + + + + + + + + + + + + + + + + ); +}; diff --git a/public/components/acceleration/visual_editors/materialized_view/materialized_view_builder.tsx b/public/components/acceleration/visual_editors/materialized_view/materialized_view_builder.tsx new file mode 100644 index 00000000..77c99fb3 --- /dev/null +++ b/public/components/acceleration/visual_editors/materialized_view/materialized_view_builder.tsx @@ -0,0 +1,113 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState } from 'react'; +import { CreateAccelerationForm, MaterializedViewColumn } from '../../../../../common/types'; +import _ from 'lodash'; +import { + EuiSpacer, + EuiText, + EuiExpression, + EuiFlexGroup, + EuiFlexItem, + htmlIdGenerator, +} from '@elastic/eui'; +import { AddColumnPopOver } from './add_column_popover'; +import { ColumnExpression } from './column_expression'; +import { GroupByTumbleExpression } from './group_by_tumble_expression'; +import { useEffect } from 'react'; + +interface MaterializedViewBuilderProps { + accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; +} + +export const MaterializedViewBuilder = ({ + accelerationFormData, + setAccelerationFormData, +}: MaterializedViewBuilderProps) => { + const [isColumnPopOverOpen, setIsColumnPopOverOpen] = useState(false); + const [columnExpressionValues, setColumnExpressionValues] = useState( + [] + ); + + useEffect(() => { + if (accelerationFormData.dataTableFields.length > 0) { + setColumnExpressionValues([ + { + id: htmlIdGenerator()(), + functionName: 'count', + functionParam: accelerationFormData.dataTableFields[0].fieldName, + fieldAlias: 'counter1', + }, + ]); + } + }, [accelerationFormData.dataTableFields]); + + useEffect(() => { + setAccelerationFormData({ + ...accelerationFormData, + materializedViewQueryData: { + ...accelerationFormData.materializedViewQueryData, + columnsValues: columnExpressionValues, + }, + }); + }, [columnExpressionValues]); + + return ( + <> + +

Materialized view definition

+
+ + + + + + + + + + + + + + + + + {_.map(columnExpressionValues, (_, i) => { + return ( + + ); + })} + + + + + + + ); +}; diff --git a/public/components/acceleration/visual_editors/query_builder.tsx b/public/components/acceleration/visual_editors/query_builder.tsx index c4e5b47f..29c57b58 100644 --- a/public/components/acceleration/visual_editors/query_builder.tsx +++ b/public/components/acceleration/visual_editors/query_builder.tsx @@ -4,46 +4,185 @@ */ import _ from 'lodash'; -import { CreateAccelerationForm, SkippingIndexRowType } from '../../../../common/types'; +import { + CreateAccelerationForm, + GroupByTumbleType, + MaterializedViewColumn, + SkippingIndexRowType, +} from '../../../../common/types'; +import { isTimePlural } from '../create/utils'; -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`; - }, - '' +/* Add index options to query */ +const buildIndexOptions = (accelerationformData: CreateAccelerationForm) => { + const { + primaryShardsCount, + replicaShardsCount, + refreshType, + checkpointLocation, + } = accelerationformData; + const indexOptions: string[] = []; + + // Add index settings option + indexOptions.push( + `index_settings = '{"number_of_shards":${primaryShardsCount},"number_of_replicas":${replicaShardsCount}}'` ); + + // Add auto refresh option + indexOptions.push(`auto_refresh = ${refreshType === 'auto'}`); + + // Add refresh interval option + if (refreshType === 'interval') { + const { refreshWindow, refreshInterval } = accelerationformData.refreshIntervalOptions; + indexOptions.push( + `refresh_interval = '${refreshWindow} ${refreshInterval}${isTimePlural(refreshWindow)}'` + ); + } + + // Add checkpoint location option + if (checkpointLocation) { + indexOptions.push(`checkpoint_location = '${checkpointLocation}'`); + } + + // Combine all options with commas and return as a single string + return `WITH (\n${indexOptions.join(',\n')}\n)`; }; +/* Add skipping index columns to query */ +const buildSkippingIndexColumns = (skippingIndexQueryData: SkippingIndexRowType[]) => { + return skippingIndexQueryData + .map((n) => ` ${n.fieldName} ${n.accelerationMethod}`) + .join(', \n'); +}; + +/* + * Builds create skipping index query + * Skipping Index create query example: + * + * CREATE SKIPPING INDEX + * [IF NOT EXISTS] + * ON datasource.database.table + * FOR COLUMNS ( + * field1 VALUE_SET, + * field2 PARTITION, + * field3 MIN_MAX, + * ) WITH ( + * auto_refresh = false, + * refresh_interval = '1 minute', + * checkpoint_location = 's3://test/', + * index_settings = '{"number_of_shards":9,"number_of_replicas":2}' + * ) + */ 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 + ')'; + const { dataSource, database, dataTable, skippingIndexQueryData } = accelerationformData; + + const codeQuery = `CREATE SKIPPING INDEX +[IF NOT EXISTS] +ON ${dataSource}.${database}.${dataTable} + FOR COLUMNS ( +${buildSkippingIndexColumns(skippingIndexQueryData)} + ) ${buildIndexOptions(accelerationformData)}`; + return codeQuery; }; +/* Add covering index columns to query */ +const buildCoveringIndexColumns = (coveringIndexQueryData: string[]) => { + return coveringIndexQueryData.map((field) => ` ${field}`).join(', \n'); +}; + +/* + * Builds create covering index query + * Covering Index create query example: + * + * CREATE INDEX index_name + * [IF NOT EXISTS] + * ON datasource.database.table + * FOR COLUMNS ( + * field1, + * field2, + * field3, + * ) WITH ( + * auto_refresh = false, + * refresh_interval = '1 minute', + * checkpoint_location = 's3://test/', + * index_settings = '{"number_of_shards":9,"number_of_replicas":2}' + * ) + */ const coveringIndexQueryBuilder = (accelerationformData: CreateAccelerationForm) => { - return ''; + const { + dataSource, + database, + dataTable, + accelerationIndexName, + coveringIndexQueryData, + } = accelerationformData; + + const codeQuery = `CREATE INDEX ${accelerationIndexName} +[IF NOT EXISTS] +ON ${dataSource}.${database}.${dataTable} + FOR COLUMNS ( +${buildCoveringIndexColumns(coveringIndexQueryData)} + ) ${buildIndexOptions(accelerationformData)}`; + + return codeQuery; +}; + +const buildMaterializedViewColumns = (columnsValues: MaterializedViewColumn[]) => { + return columnsValues + .map( + (column) => + ` ${column.functionName}(${column.functionParam}) ${ + column.fieldAlias && `AS ${column.fieldAlias}` + }` + ) + .join(', \n'); }; +/* Build group by tumble values */ +const buildTumbleValue = (GroupByTumbleValue: GroupByTumbleType) => { + const { timeField, tumbleWindow, tumbleInterval } = GroupByTumbleValue; + return `(${timeField}, '${tumbleWindow} ${tumbleInterval}${isTimePlural(tumbleWindow)}')`; +}; + +/* + * Builds create materialized view query + * Materialized View create query example: + * + * CREATE MATERIALIZED VIEW datasource.database.index_name + * [IF NOT EXISTS] + * AS SELECT + * count(field) as counter, + * count(*) as counter1, + * sum(field2), + * avg(field3) as average + * WITH ( + * auto_refresh = false, + * refresh_interval = '1 minute', + * checkpoint_location = 's3://test/', + * index_settings = '{"number_of_shards":9,"number_of_replicas":2}' + * ) + */ const materializedQueryViewBuilder = (accelerationformData: CreateAccelerationForm) => { - return ''; + const { + dataSource, + database, + dataTable, + accelerationIndexName, + materializedViewQueryData, + } = accelerationformData; + + const codeQuery = `CREATE MATERIALIZED VIEW ${dataSource}.${database}.${accelerationIndexName} +[IF NOT EXISTS] +AS SELECT +${buildMaterializedViewColumns(materializedViewQueryData.columnsValues)} +FROM ${dataSource}.${database}.${dataTable} +GROUP BY TUMBLE ${buildTumbleValue(materializedViewQueryData.groupByTumbleValue)} + ${buildIndexOptions(accelerationformData)}`; + + return codeQuery; }; +/* Builds create acceleration index query */ export const accelerationQueryBuilder = (accelerationformData: CreateAccelerationForm) => { switch (accelerationformData.accelerationIndexType) { case 'skipping': { diff --git a/public/components/acceleration/visual_editors/query_visual_editor.tsx b/public/components/acceleration/visual_editors/query_visual_editor.tsx index d8f933f8..0110b7cd 100644 --- a/public/components/acceleration/visual_editors/query_visual_editor.tsx +++ b/public/components/acceleration/visual_editors/query_visual_editor.tsx @@ -6,9 +6,9 @@ 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'; +import { MaterializedViewBuilder } from './materialized_view/materialized_view_builder'; interface QueryVisualEditorProps { accelerationFormData: CreateAccelerationForm; @@ -21,10 +21,6 @@ export const QueryVisualEditor = ({ }: QueryVisualEditorProps) => { return ( <> - {accelerationFormData.accelerationIndexType === 'skipping' && ( )} + {accelerationFormData.accelerationIndexType === 'materialized' && ( + + )} ); }; From d71444ce16b7ada19814b74ab96152db16535cce Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Fri, 29 Sep 2023 15:08:49 -0700 Subject: [PATCH 2/2] organize header and PR comments Signed-off-by: Shenoy Pratik --- .../create/create_acceleration.tsx | 33 ++++++---- .../create/create_acceleration_header.tsx | 4 +- .../components/acceleration/create/utils.tsx | 25 ++----- .../selectors/define_index_options.tsx | 65 ++++++++----------- .../selectors/index_setting_options.tsx | 6 +- .../selectors/index_type_selector.tsx | 2 +- .../selectors/source_selector.tsx | 3 +- .../covering_index/covering_index_builder.tsx | 11 ++-- .../materialized_view/add_column_popover.tsx | 21 +++--- .../materialized_view/column_expression.tsx | 16 ++--- .../group_by_tumble_expression.tsx | 9 ++- .../materialized_view_builder.tsx | 15 +++-- .../visual_editors/query_builder.tsx | 7 +- .../visual_editors/query_visual_editor.tsx | 4 +- .../skipping_index/add_fields_modal.tsx | 10 +-- .../skipping_index/delete_fields_modal.tsx | 12 ++-- .../skipping_index/skipping_index_builder.tsx | 10 +-- 17 files changed, 114 insertions(+), 139 deletions(-) diff --git a/public/components/acceleration/create/create_acceleration.tsx b/public/components/acceleration/create/create_acceleration.tsx index 767c390f..e726adf0 100644 --- a/public/components/acceleration/create/create_acceleration.tsx +++ b/public/components/acceleration/create/create_acceleration.tsx @@ -4,26 +4,26 @@ */ import { - EuiSpacer, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutHeader, - EuiFlyoutFooter, EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiSpacer, } from '@elastic/eui'; import React, { useState } from 'react'; -import { CreateAccelerationHeader } from './create_acceleration_header'; -import { CautionBannerCallout } from './caution_banner_callout'; +import { ACCELERATION_TIME_INTERVAL } from '../../../../common/constants'; +import { CreateAccelerationForm } from '../../../../common/types/'; +import { DefineIndexOptions } from '../selectors/define_index_options'; +import { IndexSettingOptions } from '../selectors/index_setting_options'; import { AccelerationDataSourceSelector } from '../selectors/source_selector'; -import { CreateAccelerationForm, materializedViewQueryType } from '../../../../common/types/'; -import { QueryVisualEditor } from '../visual_editors/query_visual_editor'; import { accelerationQueryBuilder } from '../visual_editors/query_builder'; -import { IndexSettingOptions } from '../selectors/index_setting_options'; -import { DefineIndexOptions } from '../selectors/define_index_options'; -import { ACCELERATION_TIME_INTERVAL } from '../../../../common/constants'; +import { QueryVisualEditor } from '../visual_editors/query_visual_editor'; +import { CautionBannerCallout } from './caution_banner_callout'; +import { CreateAccelerationHeader } from './create_acceleration_header'; export interface CreateAccelerationProps { dataSource: string; @@ -44,7 +44,14 @@ export const CreateAcceleration = ({ accelerationIndexType: 'skipping', skippingIndexQueryData: [], coveringIndexQueryData: [], - materializedViewQueryData: {} as materializedViewQueryType, + materializedViewQueryData: { + columnsValues: [], + groupByTumbleValue: { + timeField: '', + tumbleWindow: 0, + tumbleInterval: '', + }, + }, accelerationIndexName: '', primaryShardsCount: 5, replicaShardsCount: 1, diff --git a/public/components/acceleration/create/create_acceleration_header.tsx b/public/components/acceleration/create/create_acceleration_header.tsx index 44bdd788..d21f87d6 100644 --- a/public/components/acceleration/create/create_acceleration_header.tsx +++ b/public/components/acceleration/create/create_acceleration_header.tsx @@ -4,12 +4,12 @@ */ import { + EuiLink, EuiPageHeader, EuiPageHeaderSection, - EuiTitle, EuiSpacer, EuiText, - EuiLink, + EuiTitle, } from '@elastic/eui'; import React from 'react'; import { OPENSEARCH_ACC_DOCUMENTATION_URL } from '../../../../common/constants'; diff --git a/public/components/acceleration/create/utils.tsx b/public/components/acceleration/create/utils.tsx index 3fe9f98a..798a94ae 100644 --- a/public/components/acceleration/create/utils.tsx +++ b/public/components/acceleration/create/utils.tsx @@ -3,28 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -export const isTimePlural = (timeWindow: number) => { +import { ACCELERATION_INDEX_NAME_REGEX } from '../../../../common/constants'; + +export const pluralizeTime = (timeWindow: number) => { return timeWindow > 1 ? 's' : ''; }; export const validateIndexName = (value: string) => { - const lowercaseUnderscoreHyphenRegex = /^[a-z_\-]+$/; - - if (!lowercaseUnderscoreHyphenRegex.test(value)) { - return false; - } - - // Check if the value does not begin with underscores or hyphens - if (value.startsWith('_') || value.startsWith('-')) { - return false; - } - - // Check if the value does not contain disallowed characters - const disallowedCharacters = /[\s,:"*+\/|?#><]/; - if (disallowedCharacters.test(value)) { - return false; - } - - // If all checks pass, the value is valid - return true; + // Check if the value does not begin with underscores or hyphens and all characters are lower case + return ACCELERATION_INDEX_NAME_REGEX.test(value); }; diff --git a/public/components/acceleration/selectors/define_index_options.tsx b/public/components/acceleration/selectors/define_index_options.tsx index 8d30b0c8..e43f7258 100644 --- a/public/components/acceleration/selectors/define_index_options.tsx +++ b/public/components/acceleration/selectors/define_index_options.tsx @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { ChangeEvent, useEffect, useState } from 'react'; import { EuiButton, EuiFieldText, @@ -20,11 +19,11 @@ import { EuiModalHeaderTitle, EuiSpacer, EuiText, - EuiHorizontalRule, } from '@elastic/eui'; +import React, { ChangeEvent, useState } from 'react'; +import { ACCELERATION_INDEX_NAME_INFO } from '../../../../common/constants'; import { CreateAccelerationForm } from '../../../../common/types'; import { validateIndexName } from '../create/utils'; -import { ACCELERATION_INDEX_NAME_INFO } from '../../../../common/constants'; interface DefineIndexOptionsProps { accelerationFormData: CreateAccelerationForm; @@ -36,45 +35,35 @@ export const DefineIndexOptions = ({ setAccelerationFormData, }: DefineIndexOptionsProps) => { const [indexName, setIndexName] = useState(''); - const [isModalVisible, setIsModalVisible] = useState(false); - - let modal; + const [modalComponent, setModalComponent] = useState(<>); - if (isModalVisible) { - modal = ( - setIsModalVisible(false)}> - - -

Acceleration index naming

-
-
- - - - {ACCELERATION_INDEX_NAME_INFO} - - - - - setIsModalVisible(false)} fill> - Close - - -
- ); - } + const modalValue = ( + setModalComponent(<>)}> + + +

Acceleration index naming

+
+
+ + + + {ACCELERATION_INDEX_NAME_INFO} + + + + + setModalComponent(<>)} fill> + Close + + +
+ ); const onChangeIndexName = (e: ChangeEvent) => { setAccelerationFormData({ ...accelerationFormData, accelerationIndexName: e.target.value }); setIndexName(e.target.value); }; - useEffect(() => { - accelerationFormData.accelerationIndexType === 'skipping' - ? setIndexName('skipping') - : setIndexName(''); - }, [accelerationFormData.accelerationIndexType]); - const getPreprend = () => { const dataSource = accelerationFormData.dataSource !== '' @@ -109,13 +98,13 @@ export const DefineIndexOptions = ({ Prefix and suffix are added to the name of generated OpenSearch index.' labelAppend={ - setIsModalVisible(true)}>Help + setModalComponent(modalValue)}>Help } > - {modal} + {modalComponent} ); }; diff --git a/public/components/acceleration/selectors/index_setting_options.tsx b/public/components/acceleration/selectors/index_setting_options.tsx index 3f28d4ec..02ec6a2a 100644 --- a/public/components/acceleration/selectors/index_setting_options.tsx +++ b/public/components/acceleration/selectors/index_setting_options.tsx @@ -3,8 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState, ChangeEvent } from 'react'; -import { CreateAccelerationForm } from '../../../../common/types'; import { EuiFieldNumber, EuiFieldText, @@ -14,8 +12,10 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; -import { IndexTypeSelector } from './index_type_selector'; +import React, { ChangeEvent, useState } from 'react'; import { ACCELERATION_TIME_INTERVAL } from '../../../../common/constants'; +import { CreateAccelerationForm } from '../../../../common/types'; +import { IndexTypeSelector } from './index_type_selector'; interface IndexSettingOptionsProps { accelerationFormData: CreateAccelerationForm; diff --git a/public/components/acceleration/selectors/index_type_selector.tsx b/public/components/acceleration/selectors/index_type_selector.tsx index 7210d818..fe822322 100644 --- a/public/components/acceleration/selectors/index_type_selector.tsx +++ b/public/components/acceleration/selectors/index_type_selector.tsx @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState } from 'react'; import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow, EuiLink, EuiText } from '@elastic/eui'; +import React, { useState } from 'react'; import { ACCELERATION_INDEX_TYPES, ACC_INDEX_TYPE_DOCUMENTATION_URL, diff --git a/public/components/acceleration/selectors/source_selector.tsx b/public/components/acceleration/selectors/source_selector.tsx index 2cd59383..02ffc251 100644 --- a/public/components/acceleration/selectors/source_selector.tsx +++ b/public/components/acceleration/selectors/source_selector.tsx @@ -11,8 +11,7 @@ import { EuiText, htmlIdGenerator, } from '@elastic/eui'; -import React, { useState } from 'react'; -import { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { CreateAccelerationForm } from '../../../../common/types'; interface AccelerationDataSourceSelectorProps { 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 f2e528bc..0335f498 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 @@ -5,19 +5,18 @@ import { EuiComboBox, + EuiComboBoxOptionOption, EuiExpression, + EuiFlexGroup, + EuiFlexItem, EuiPopover, EuiPopoverTitle, EuiSpacer, EuiText, - EuiFlexItem, - EuiFlexGroup, - EuiComboBoxOptionOption, } from '@elastic/eui'; -import React, { useEffect, useState } from 'react'; -import { CreateAccelerationForm } from '../../../../../common/types'; -import _ from 'lodash'; +import React, { useState } from 'react'; import { ACCELERATION_ADD_FIELDS_TEXT } from '../../../../../common/constants'; +import { CreateAccelerationForm } from '../../../../../common/types'; interface CoveringIndexBuilderProps { accelerationFormData: CreateAccelerationForm; diff --git a/public/components/acceleration/visual_editors/materialized_view/add_column_popover.tsx b/public/components/acceleration/visual_editors/materialized_view/add_column_popover.tsx index f9194a0c..f61bdf99 100644 --- a/public/components/acceleration/visual_editors/materialized_view/add_column_popover.tsx +++ b/public/components/acceleration/visual_editors/materialized_view/add_column_popover.tsx @@ -3,30 +3,27 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { ChangeEvent, useState } from 'react'; import { - EuiPopover, - EuiButtonEmpty, - EuiPopoverTitle, - EuiPopoverFooter, EuiButton, + EuiButtonEmpty, + EuiComboBox, + EuiFieldText, EuiFlexGroup, EuiFlexItem, - EuiFieldText, EuiFormRow, - EuiComboBox, - htmlIdGenerator, + EuiPopover, + EuiPopoverFooter, + EuiPopoverTitle, EuiSpacer, + htmlIdGenerator, } from '@elastic/eui'; - +import React, { ChangeEvent, useEffect, useState } from 'react'; +import { ACCELERATION_AGGREGRATION_FUNCTIONS } from '../../../../../common/constants'; import { AggregationFunctionType, CreateAccelerationForm, MaterializedViewColumn, } from '../../../../../common/types'; -import { ACCELERATION_AGGREGRATION_FUNCTIONS } from '../../../../../common/constants'; -import { useEffect } from 'react'; -import _ from 'lodash'; interface AddColumnPopOverProps { isColumnPopOverOpen: boolean; diff --git a/public/components/acceleration/visual_editors/materialized_view/column_expression.tsx b/public/components/acceleration/visual_editors/materialized_view/column_expression.tsx index 99e152c4..0da3d65b 100644 --- a/public/components/acceleration/visual_editors/materialized_view/column_expression.tsx +++ b/public/components/acceleration/visual_editors/materialized_view/column_expression.tsx @@ -3,14 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useEffect, useState } from 'react'; -import { ACCELERATION_AGGREGRATION_FUNCTIONS } from '../../../../../common/constants'; -import { - MaterializedViewColumn, - CreateAccelerationForm, - AggregationFunctionType, -} from '../../../../../common/types'; -import _ from 'lodash'; import { EuiButtonIcon, EuiComboBox, @@ -21,6 +13,14 @@ import { EuiFormRow, EuiPopover, } from '@elastic/eui'; +import _ from 'lodash'; +import React, { useState } from 'react'; +import { ACCELERATION_AGGREGRATION_FUNCTIONS } from '../../../../../common/constants'; +import { + AggregationFunctionType, + CreateAccelerationForm, + MaterializedViewColumn, +} from '../../../../../common/types'; interface ColumnExpressionProps { index: number; diff --git a/public/components/acceleration/visual_editors/materialized_view/group_by_tumble_expression.tsx b/public/components/acceleration/visual_editors/materialized_view/group_by_tumble_expression.tsx index 6ff8a3ec..dc853414 100644 --- a/public/components/acceleration/visual_editors/materialized_view/group_by_tumble_expression.tsx +++ b/public/components/acceleration/visual_editors/materialized_view/group_by_tumble_expression.tsx @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState, useEffect } from 'react'; import { EuiComboBox, + EuiComboBoxOptionOption, EuiExpression, EuiFieldNumber, EuiFlexGroup, @@ -13,12 +13,11 @@ import { EuiFormRow, EuiPopover, EuiSelect, - EuiComboBoxOptionOption, } from '@elastic/eui'; +import React, { useEffect, useState } from 'react'; import { ACCELERATION_TIME_INTERVAL } from '../../../../../common/constants'; import { CreateAccelerationForm, GroupByTumbleType } from '../../../../../common/types'; -import _ from 'lodash'; -import { isTimePlural } from '../../create/utils'; +import { pluralizeTime } from '../../create/utils'; interface GroupByTumbleExpressionProps { accelerationFormData: CreateAccelerationForm; @@ -68,7 +67,7 @@ export const GroupByTumbleExpression = ({ description="GROUP BY" value={`TUMBLE(${groupbyValues.timeField}, '${groupbyValues.tumbleWindow} ${ groupbyValues.tumbleInterval - }${isTimePlural(groupbyValues.tumbleWindow)}')`} + }${pluralizeTime(groupbyValues.tumbleWindow)}')`} isActive={IsGroupPopOverOpen} onClick={() => setIsGroupPopOverOpen(true)} isInvalid={groupbyValues.timeField === ''} diff --git a/public/components/acceleration/visual_editors/materialized_view/materialized_view_builder.tsx b/public/components/acceleration/visual_editors/materialized_view/materialized_view_builder.tsx index 77c99fb3..0f3bdeea 100644 --- a/public/components/acceleration/visual_editors/materialized_view/materialized_view_builder.tsx +++ b/public/components/acceleration/visual_editors/materialized_view/materialized_view_builder.tsx @@ -3,27 +3,28 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState } from 'react'; -import { CreateAccelerationForm, MaterializedViewColumn } from '../../../../../common/types'; -import _ from 'lodash'; import { - EuiSpacer, - EuiText, EuiExpression, EuiFlexGroup, EuiFlexItem, + EuiSpacer, + EuiText, htmlIdGenerator, } from '@elastic/eui'; +import _ from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { CreateAccelerationForm, MaterializedViewColumn } from '../../../../../common/types'; import { AddColumnPopOver } from './add_column_popover'; import { ColumnExpression } from './column_expression'; import { GroupByTumbleExpression } from './group_by_tumble_expression'; -import { useEffect } from 'react'; interface MaterializedViewBuilderProps { accelerationFormData: CreateAccelerationForm; setAccelerationFormData: React.Dispatch>; } +const newColumnExpressionId = htmlIdGenerator()(); + export const MaterializedViewBuilder = ({ accelerationFormData, setAccelerationFormData, @@ -37,7 +38,7 @@ export const MaterializedViewBuilder = ({ if (accelerationFormData.dataTableFields.length > 0) { setColumnExpressionValues([ { - id: htmlIdGenerator()(), + id: newColumnExpressionId, functionName: 'count', functionParam: accelerationFormData.dataTableFields[0].fieldName, fieldAlias: 'counter1', diff --git a/public/components/acceleration/visual_editors/query_builder.tsx b/public/components/acceleration/visual_editors/query_builder.tsx index 29c57b58..5be5e6df 100644 --- a/public/components/acceleration/visual_editors/query_builder.tsx +++ b/public/components/acceleration/visual_editors/query_builder.tsx @@ -3,14 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -import _ from 'lodash'; import { CreateAccelerationForm, GroupByTumbleType, MaterializedViewColumn, SkippingIndexRowType, } from '../../../../common/types'; -import { isTimePlural } from '../create/utils'; +import { pluralizeTime } from '../create/utils'; /* Add index options to query */ const buildIndexOptions = (accelerationformData: CreateAccelerationForm) => { @@ -34,7 +33,7 @@ const buildIndexOptions = (accelerationformData: CreateAccelerationForm) => { if (refreshType === 'interval') { const { refreshWindow, refreshInterval } = accelerationformData.refreshIntervalOptions; indexOptions.push( - `refresh_interval = '${refreshWindow} ${refreshInterval}${isTimePlural(refreshWindow)}'` + `refresh_interval = '${refreshWindow} ${refreshInterval}${pluralizeTime(refreshWindow)}'` ); } @@ -141,7 +140,7 @@ const buildMaterializedViewColumns = (columnsValues: MaterializedViewColumn[]) = /* Build group by tumble values */ const buildTumbleValue = (GroupByTumbleValue: GroupByTumbleType) => { const { timeField, tumbleWindow, tumbleInterval } = GroupByTumbleValue; - return `(${timeField}, '${tumbleWindow} ${tumbleInterval}${isTimePlural(tumbleWindow)}')`; + return `(${timeField}, '${tumbleWindow} ${tumbleInterval}${pluralizeTime(tumbleWindow)}')`; }; /* diff --git a/public/components/acceleration/visual_editors/query_visual_editor.tsx b/public/components/acceleration/visual_editors/query_visual_editor.tsx index 0110b7cd..737f5d1e 100644 --- a/public/components/acceleration/visual_editors/query_visual_editor.tsx +++ b/public/components/acceleration/visual_editors/query_visual_editor.tsx @@ -3,12 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React from 'react'; import { EuiSpacer } from '@elastic/eui'; +import React from 'react'; import { CreateAccelerationForm } from '../../../../common/types'; -import { SkippingIndexBuilder } from './skipping_index/skipping_index_builder'; import { CoveringIndexBuilder } from './covering_index/covering_index_builder'; import { MaterializedViewBuilder } from './materialized_view/materialized_view_builder'; +import { SkippingIndexBuilder } from './skipping_index/skipping_index_builder'; interface QueryVisualEditorProps { accelerationFormData: CreateAccelerationForm; 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 92e7c498..e771c1b1 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 @@ -4,18 +4,18 @@ */ import { + EuiButton, + EuiInMemoryTable, EuiModal, - EuiModalHeader, - EuiModalHeaderTitle, EuiModalBody, EuiModalFooter, - EuiButton, - EuiInMemoryTable, + EuiModalHeader, + EuiModalHeaderTitle, EuiTableFieldDataColumnType, } from '@elastic/eui'; +import _ from 'lodash'; import React, { useState } from 'react'; import { CreateAccelerationForm, DataTableFieldsType } from '../../../../../common/types'; -import _ from 'lodash'; interface AddFieldsModalProps { setIsAddModalVisible: React.Dispatch>; 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 58a6fa79..adfa29b0 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 @@ -3,18 +3,18 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState } from 'react'; import { - EuiTableFieldDataColumnType, + EuiButton, + EuiInMemoryTable, EuiModal, - EuiModalHeader, - EuiModalHeaderTitle, EuiModalBody, - EuiInMemoryTable, EuiModalFooter, - EuiButton, + EuiModalHeader, + EuiModalHeaderTitle, + EuiTableFieldDataColumnType, } from '@elastic/eui'; import _ from 'lodash'; +import React, { useState } from 'react'; import { CreateAccelerationForm, SkippingIndexRowType } from '../../../../../common/types'; interface AddFieldsModalProps { 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 index 0c22baf2..f38b120b 100644 --- a/public/components/acceleration/visual_editors/skipping_index/skipping_index_builder.tsx +++ b/public/components/acceleration/visual_editors/skipping_index/skipping_index_builder.tsx @@ -3,18 +3,18 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useEffect, useState } from 'react'; import { EuiBasicTable, - EuiSelect, - EuiSpacer, - EuiText, - EuiButtonIcon, EuiButton, + EuiButtonIcon, EuiFlexGroup, EuiFlexItem, + EuiSelect, + EuiSpacer, + EuiText, } from '@elastic/eui'; import _ from 'lodash'; +import React, { useEffect, useState } from 'react'; import { CreateAccelerationForm, SkippingIndexRowType } from '../../../../../common/types'; import { AddFieldsModal } from './add_fields_modal'; import { DeleteFieldsModal } from './delete_fields_modal';